diff --git a/django_etebase/exceptions.py b/django_etebase/exceptions.py new file mode 100644 index 0000000..d05c4e5 --- /dev/null +++ b/django_etebase/exceptions.py @@ -0,0 +1,10 @@ +from rest_framework import serializers, status + + +class EtebaseValidationError(serializers.ValidationError): + def __init__(self, code, detail, status_code=status.HTTP_400_BAD_REQUEST): + super().__init__({ + 'code': code, + 'detail': detail, + }) + self.status_code = status_code diff --git a/django_etebase/serializers.py b/django_etebase/serializers.py index bde8095..c8e93cb 100644 --- a/django_etebase/serializers.py +++ b/django_etebase/serializers.py @@ -18,10 +18,12 @@ from django.core.files.base import ContentFile from django.core import exceptions as django_exceptions from django.contrib.auth import get_user_model from django.db import transaction -from rest_framework import serializers +from rest_framework import serializers, status from . import models from .utils import get_user_queryset, create_user +from .exceptions import EtebaseValidationError + User = get_user_model() @@ -40,7 +42,7 @@ def process_revisions_for_item(item, revision_data): chunk_obj.save() else: if chunk_obj is None: - raise serializers.ValidationError('Tried to create a new chunk without content') + raise EtebaseValidationError('chunk_no_content', 'Tried to create a new chunk without content') chunks_objs.append(chunk_obj) @@ -115,7 +117,7 @@ class ChunksField(serializers.RelatedField): def to_internal_value(self, data): if data[0] is None or data[1] is None: - raise serializers.ValidationError('null is not allowed') + raise EtebaseValidationError('null is not allowed') return (data[0], b64decode_or_bytes(data[1])) @@ -161,7 +163,7 @@ class CollectionItemSerializer(serializers.ModelSerializer): cur_etag = instance.etag if not created else None if validate_etag and cur_etag != etag: - raise serializers.ValidationError('Wrong etag. Expected {} got {}'.format(cur_etag, etag)) + raise EtebaseValidationError('wrong_etag', 'Wrong etag. Expected {} got {}'.format(cur_etag, etag), status_code=status.HTTP_409_CONFLICT) if not created: # We don't have to use select_for_update here because the unique constraint on current guards against @@ -190,7 +192,7 @@ class CollectionItemDepSerializer(serializers.ModelSerializer): item = self.__class__.Meta.model.objects.get(uid=data['uid']) etag = data['etag'] if item.etag != etag: - raise serializers.ValidationError('Wrong etag. Expected {} got {}'.format(item.etag, etag)) + raise EtebaseValidationError('wrong_etag', 'Wrong etag. Expected {} got {}'.format(item.etag, etag), status_code=status.HTTP_409_CONFLICT) return data @@ -232,7 +234,7 @@ class CollectionSerializer(serializers.ModelSerializer): with transaction.atomic(): if etag is not None: - raise serializers.ValidationError('etag is not None') + raise EtebaseValidationError('bad_etag', 'etag is not null') instance.save() main_item = models.CollectionItem.objects.create(**main_item_data, collection=instance) @@ -297,7 +299,7 @@ class CollectionInvitationSerializer(serializers.ModelSerializer): request = self.context['request'] if request.user.username == value.lower(): - raise serializers.ValidationError('Inviting yourself is not allowed') + raise EtebaseValidationError('no_self_invite', 'Inviting yourself is not allowed') return value def create(self, validated_data): @@ -393,15 +395,15 @@ class AuthenticationSignupSerializer(serializers.Serializer): try: instance = create_user(**user_data, password=None, first_name=user_data['username'], view=view) except Exception as e: - raise serializers.ValidationError(e) + raise EtebaseValidationError('generic', str(e)) if hasattr(instance, 'userinfo'): - raise serializers.ValidationError('User already exists') + raise EtebaseValidationError('user_exists', 'User already exists', status_code=status.HTTP_409_CONFLICT) try: instance.clean_fields() except django_exceptions.ValidationError as e: - raise serializers.ValidationError(e) + raise EtebaseValidationError('generic', str(e)) # FIXME: send email verification models.UserInfo.objects.create(**validated_data, owner=instance) diff --git a/django_etebase/views.py b/django_etebase/views.py index 9a71eee..eb04e4b 100644 --- a/django_etebase/views.py +++ b/django_etebase/views.py @@ -72,7 +72,7 @@ from .serializers import ( UserSerializer, ) from .utils import get_user_queryset - +from .exceptions import EtebaseValidationError User = get_user_model() @@ -111,7 +111,14 @@ class BaseViewSet(viewsets.ModelViewSet): stoken = self.get_stoken_obj_id(request) if stoken is not None: - return get_object_or_404(Stoken.objects.all(), uid=stoken) + try: + return Stoken.objects.get(uid=stoken) + except Stoken.DoesNotExist: + raise EtebaseValidationError({ + 'code': 'bad_stoken', + 'detail': 'Invalid stoken.', + }, + status_code=status.HTTP_400_BAD_REQUEST) return None @@ -363,7 +370,7 @@ class CollectionItemViewSet(BaseViewSet): if stoken is not None and stoken != collection_object.stoken: content = {'code': 'stale_stoken', 'detail': 'Stoken is too old'} - return Response(content, status=status.HTTP_400_BAD_REQUEST) + return Response(content, status=status.HTTP_409_CONFLICT) items = request.data.get('items') deps = request.data.get('deps', None) @@ -387,7 +394,7 @@ class CollectionItemViewSet(BaseViewSet): "items": serializer.errors, "deps": deps_serializer.errors if deps is not None else [], }, - status=status.HTTP_400_BAD_REQUEST) + status=status.HTTP_409_CONFLICT) class CollectionItemChunkViewSet(viewsets.ViewSet):