base
This commit is contained in:
@@ -0,0 +1,490 @@
|
||||
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# See the LICENSE file for details.
|
||||
|
||||
"""
|
||||
Common OpenAPI responses for drf-spectacular.
|
||||
|
||||
This module provides reusable response definitions for common HTTP status codes
|
||||
and scenarios that occur across multiple API endpoints.
|
||||
"""
|
||||
|
||||
from drf_spectacular.utils import OpenApiResponse, OpenApiExample, inline_serializer
|
||||
from rest_framework import serializers
|
||||
from .examples import get_sample_for_schema
|
||||
|
||||
|
||||
# Authentication & Authorization Responses
|
||||
UNAUTHORIZED_RESPONSE = OpenApiResponse(
|
||||
description="Authentication credentials were not provided or are invalid.",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Unauthorized",
|
||||
value={
|
||||
"error": "Authentication credentials were not provided",
|
||||
"error_code": "AUTHENTICATION_REQUIRED",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
FORBIDDEN_RESPONSE = OpenApiResponse(
|
||||
description="Permission denied. User lacks required permissions.",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Forbidden",
|
||||
value={
|
||||
"error": "You do not have permission to perform this action",
|
||||
"error_code": "PERMISSION_DENIED",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Resource Responses
|
||||
NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="The requested resource was not found.",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Not Found",
|
||||
value={"error": "Not found", "error_code": "RESOURCE_NOT_FOUND"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
VALIDATION_ERROR_RESPONSE = OpenApiResponse(
|
||||
description="Validation error occurred with the provided data.",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Validation Error",
|
||||
value={
|
||||
"error": "Validation failed",
|
||||
"details": {"field_name": ["This field is required."]},
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Generic Success Responses
|
||||
DELETED_RESPONSE = OpenApiResponse(
|
||||
description="Resource deleted successfully",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Deleted Successfully",
|
||||
value={"message": "Resource deleted successfully"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
ARCHIVED_RESPONSE = OpenApiResponse(
|
||||
description="Resource archived successfully",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Archived Successfully",
|
||||
value={"message": "Resource archived successfully"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
UNARCHIVED_RESPONSE = OpenApiResponse(
|
||||
description="Resource unarchived successfully",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Unarchived Successfully",
|
||||
value={"message": "Resource unarchived successfully"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Specific Error Responses
|
||||
INVALID_REQUEST_RESPONSE = OpenApiResponse(
|
||||
description="Invalid request data provided",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Invalid Request",
|
||||
value={
|
||||
"error": "Invalid request data",
|
||||
"details": "Specific validation errors",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
CONFLICT_RESPONSE = OpenApiResponse(
|
||||
description="Resource conflict - duplicate or constraint violation",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Resource Conflict",
|
||||
value={
|
||||
"error": "Resource with the same identifier already exists",
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
ADMIN_ONLY_RESPONSE = OpenApiResponse(
|
||||
description="Only admin or creator can perform this action",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Admin Only",
|
||||
value={"error": "Only admin or creator can perform this action"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
CANNOT_DELETE_RESPONSE = OpenApiResponse(
|
||||
description="Resource cannot be deleted due to constraints",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Cannot Delete",
|
||||
value={"error": "Resource cannot be deleted", "reason": "Has dependencies"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
CANNOT_ARCHIVE_RESPONSE = OpenApiResponse(
|
||||
description="Resource cannot be archived in current state",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Cannot Archive",
|
||||
value={
|
||||
"error": "Resource cannot be archived",
|
||||
"reason": "Not in valid state",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
REQUIRED_FIELDS_RESPONSE = OpenApiResponse(
|
||||
description="Required fields are missing",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Required Fields Missing",
|
||||
value={"error": "Required fields are missing", "fields": ["name", "type"]},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Project-specific Responses
|
||||
PROJECT_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Project not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Project Not Found",
|
||||
value={"error": "Project not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
WORKSPACE_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Workspace not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Workspace Not Found",
|
||||
value={"error": "Workspace not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
PROJECT_NAME_TAKEN_RESPONSE = OpenApiResponse(
|
||||
description="Project name already taken",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Project Name Taken",
|
||||
value={"error": "Project name already taken"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Issue-specific Responses
|
||||
ISSUE_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Issue not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Issue Not Found",
|
||||
value={"error": "Issue not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
WORK_ITEM_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Work item not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Work Item Not Found",
|
||||
value={"error": "Work item not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
EXTERNAL_ID_EXISTS_RESPONSE = OpenApiResponse(
|
||||
description="Resource with same external ID already exists",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="External ID Exists",
|
||||
value={
|
||||
"error": "Resource with the same external id and external source already exists", # noqa: E501
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Label-specific Responses
|
||||
LABEL_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Label not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Label Not Found",
|
||||
value={"error": "Label not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
LABEL_NAME_EXISTS_RESPONSE = OpenApiResponse(
|
||||
description="Label with the same name already exists",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Label Name Exists",
|
||||
value={"error": "Label with the same name already exists in the project"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Module-specific Responses
|
||||
MODULE_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Module not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Module Not Found",
|
||||
value={"error": "Module not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
MODULE_ISSUE_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Module issue not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Module Issue Not Found",
|
||||
value={"error": "Module issue not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Cycle-specific Responses
|
||||
CYCLE_CANNOT_ARCHIVE_RESPONSE = OpenApiResponse(
|
||||
description="Cycle cannot be archived",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Cycle Cannot Archive",
|
||||
value={"error": "Only completed cycles can be archived"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# State-specific Responses
|
||||
STATE_NAME_EXISTS_RESPONSE = OpenApiResponse(
|
||||
description="State with the same name already exists",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="State Name Exists",
|
||||
value={"error": "State with the same name already exists"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
STATE_CANNOT_DELETE_RESPONSE = OpenApiResponse(
|
||||
description="State cannot be deleted",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="State Cannot Delete",
|
||||
value={
|
||||
"error": "State cannot be deleted",
|
||||
"reason": "Default state or has issues",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Comment-specific Responses
|
||||
COMMENT_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Comment not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Comment Not Found",
|
||||
value={"error": "Comment not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Link-specific Responses
|
||||
LINK_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Link not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Link Not Found",
|
||||
value={"error": "Link not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Attachment-specific Responses
|
||||
ATTACHMENT_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Attachment not found",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Attachment Not Found",
|
||||
value={"error": "Attachment not found"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Search-specific Responses
|
||||
BAD_SEARCH_REQUEST_RESPONSE = OpenApiResponse(
|
||||
description="Bad request - invalid search parameters",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Bad Search Request",
|
||||
value={"error": "Invalid search parameters"},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Pagination Response Templates
|
||||
def create_paginated_response(
|
||||
item_schema,
|
||||
schema_name,
|
||||
description="Paginated results",
|
||||
example_name="Paginated Response",
|
||||
):
|
||||
"""Create a paginated response with the specified item schema"""
|
||||
|
||||
return OpenApiResponse(
|
||||
description=description,
|
||||
response=inline_serializer(
|
||||
name=schema_name,
|
||||
fields={
|
||||
"grouped_by": serializers.CharField(allow_null=True),
|
||||
"sub_grouped_by": serializers.CharField(allow_null=True),
|
||||
"total_count": serializers.IntegerField(),
|
||||
"next_cursor": serializers.CharField(),
|
||||
"prev_cursor": serializers.CharField(),
|
||||
"next_page_results": serializers.BooleanField(),
|
||||
"prev_page_results": serializers.BooleanField(),
|
||||
"count": serializers.IntegerField(),
|
||||
"total_pages": serializers.IntegerField(),
|
||||
"total_results": serializers.IntegerField(),
|
||||
"extra_stats": serializers.CharField(allow_null=True),
|
||||
"results": serializers.ListField(child=item_schema()),
|
||||
},
|
||||
),
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name=example_name,
|
||||
value={
|
||||
"grouped_by": "state",
|
||||
"sub_grouped_by": "priority",
|
||||
"total_count": 150,
|
||||
"next_cursor": "20:1:0",
|
||||
"prev_cursor": "20:0:0",
|
||||
"next_page_results": True,
|
||||
"prev_page_results": False,
|
||||
"count": 20,
|
||||
"total_pages": 8,
|
||||
"total_results": 150,
|
||||
"extra_stats": None,
|
||||
"results": [get_sample_for_schema(schema_name)],
|
||||
},
|
||||
summary=example_name,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Asset-specific Responses
|
||||
PRESIGNED_URL_SUCCESS_RESPONSE = OpenApiResponse(description="Presigned URL generated successfully")
|
||||
|
||||
GENERIC_ASSET_UPLOAD_SUCCESS_RESPONSE = OpenApiResponse(
|
||||
description="Presigned URL generated successfully",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Generic Asset Upload Response",
|
||||
value={
|
||||
"upload_data": {
|
||||
"url": "https://s3.amazonaws.com/bucket-name",
|
||||
"fields": {
|
||||
"key": "workspace-id/uuid-filename.pdf",
|
||||
"AWSAccessKeyId": "AKIA...",
|
||||
"policy": "eyJ...",
|
||||
"signature": "abc123...",
|
||||
},
|
||||
},
|
||||
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"asset_url": "https://cdn.example.com/workspace-id/uuid-filename.pdf",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
GENERIC_ASSET_VALIDATION_ERROR_RESPONSE = OpenApiResponse(
|
||||
description="Validation error",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Missing required fields",
|
||||
value={"error": "Name and size are required fields.", "status": False},
|
||||
),
|
||||
OpenApiExample(
|
||||
name="Invalid file type",
|
||||
value={"error": "Invalid file type.", "status": False},
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
ASSET_CONFLICT_RESPONSE = OpenApiResponse(
|
||||
description="Asset with same external ID already exists",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Duplicate external asset",
|
||||
value={
|
||||
"message": "Asset with same external id and source already exists",
|
||||
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"asset_url": "https://cdn.example.com/existing-file.pdf",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
ASSET_DOWNLOAD_SUCCESS_RESPONSE = OpenApiResponse(
|
||||
description="Presigned download URL generated successfully",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Asset Download Response",
|
||||
value={
|
||||
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"asset_url": "https://s3.amazonaws.com/bucket/file.pdf?signed-url",
|
||||
"asset_name": "document.pdf",
|
||||
"asset_type": "application/pdf",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
ASSET_DOWNLOAD_ERROR_RESPONSE = OpenApiResponse(
|
||||
description="Bad request",
|
||||
examples=[
|
||||
OpenApiExample(name="Asset not uploaded", value={"error": "Asset not yet uploaded"}),
|
||||
],
|
||||
)
|
||||
|
||||
ASSET_UPDATED_RESPONSE = OpenApiResponse(description="Asset updated successfully")
|
||||
|
||||
ASSET_DELETED_RESPONSE = OpenApiResponse(description="Asset deleted successfully")
|
||||
|
||||
ASSET_NOT_FOUND_RESPONSE = OpenApiResponse(
|
||||
description="Asset not found",
|
||||
examples=[OpenApiExample(name="Asset not found", value={"error": "Asset not found"})],
|
||||
)
|
||||
Reference in New Issue
Block a user