diff --git a/cms/docs/docs.go b/cms/docs/docs.go index 558499a..2512bb4 100644 --- a/cms/docs/docs.go +++ b/cms/docs/docs.go @@ -78,8 +78,14 @@ const docTemplate = `{ "$ref": "#/definitions/api.ProblemDetails" } }, + "404": { + "description": "categoryIds referenced a category that does not exist", + "schema": { + "$ref": "#/definitions/api.ProblemDetails" + } + }, "422": { - "description": "title was empty, key was missing or not a key issued by this API, or categoryIds was empty or contained an id that does not exist", + "description": "title was empty, key was missing or not a key issued by this API, or categoryIds was empty", "schema": { "$ref": "#/definitions/api.ProblemDetails" } diff --git a/cms/docs/swagger.json b/cms/docs/swagger.json index c1306a5..bf5dd1e 100644 --- a/cms/docs/swagger.json +++ b/cms/docs/swagger.json @@ -71,8 +71,14 @@ "$ref": "#/definitions/api.ProblemDetails" } }, + "404": { + "description": "categoryIds referenced a category that does not exist", + "schema": { + "$ref": "#/definitions/api.ProblemDetails" + } + }, "422": { - "description": "title was empty, key was missing or not a key issued by this API, or categoryIds was empty or contained an id that does not exist", + "description": "title was empty, key was missing or not a key issued by this API, or categoryIds was empty", "schema": { "$ref": "#/definitions/api.ProblemDetails" } diff --git a/cms/docs/swagger.yaml b/cms/docs/swagger.yaml index 6e2489a..7dd67fd 100644 --- a/cms/docs/swagger.yaml +++ b/cms/docs/swagger.yaml @@ -186,9 +186,13 @@ paths: description: Request body was not valid JSON schema: $ref: '#/definitions/api.ProblemDetails' + "404": + description: categoryIds referenced a category that does not exist + schema: + $ref: '#/definitions/api.ProblemDetails' "422": description: title was empty, key was missing or not a key issued by this - API, or categoryIds was empty or contained an id that does not exist + API, or categoryIds was empty schema: $ref: '#/definitions/api.ProblemDetails' "500": diff --git a/cms/internal/handlers/errors.go b/cms/internal/handlers/errors.go new file mode 100644 index 0000000..6e3d744 --- /dev/null +++ b/cms/internal/handlers/errors.go @@ -0,0 +1,16 @@ +package handlers + +import "errors" + +// Sentinel errors the handlers branch on with errors.Is to decide which +// problem response a failure deserves. They exist to separate a client's +// mistake from a server fault where the code that detects both is the same +// code — the repositories return plain errors, so without these every failure +// out of a lookup would have to be answered as a 500. + +// errUnknownCatagoryID reports that a submitted category id is not in the +// category table. It is kept distinct from the error the lookup itself +// returns because the two are not the same kind of failure: an id nobody has +// is the client's mistake and answers 404, while a lookup that could not run +// is the server's and answers 500. +var errUnknownCatagoryID = errors.New("unknown catagory id") diff --git a/cms/internal/handlers/videos.go b/cms/internal/handlers/videos.go index 8c2884c..1c16b7f 100644 --- a/cms/internal/handlers/videos.go +++ b/cms/internal/handlers/videos.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "encoding/hex" "encoding/json" + "errors" "fmt" "log" "net/http" @@ -111,7 +112,8 @@ func PresignVideoUpload(w http.ResponseWriter, r *http.Request) { // @Param request body api.CompleteRequest true "Video metadata plus the storage key from the presign step. title, key and categoryIds are required; categoryIds must hold at least one id from GET /api/categories." // @Success 201 {object} api.CompleteResponse // @Failure 400 {object} api.ProblemDetails "Request body was not valid JSON" -// @Failure 422 {object} api.ProblemDetails "title was empty, key was missing or not a key issued by this API, or categoryIds was empty or contained an id that does not exist" +// @Failure 404 {object} api.ProblemDetails "categoryIds referenced a category that does not exist" +// @Failure 422 {object} api.ProblemDetails "title was empty, key was missing or not a key issued by this API, or categoryIds was empty" // @Failure 500 {object} api.ProblemDetails "Categories could not be read, the transcoding job could not be queued, or the video record could not be saved" // @Router /api/videos [post] func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) { @@ -151,7 +153,12 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) { return } - err := validateCatagoryIDS(r.Context(), req.CategoryIDs) + unknownCatagoryID, err := validateCatagoryIDS(r.Context(), req.CategoryIDs) + if errors.Is(err, errUnknownCatagoryID) { + writeProblem(w, http.StatusNotFound, "Catagory Does Not Exist", + fmt.Sprintf("The 'categoryIds' field referenced the id %d, which no category has. Send only ids taken from the 'id' field of GET /api/categories. No video record was created and no transcoding job was queued; the uploaded file is still in storage, so retry this request with the same 'key'.", unknownCatagoryID)) + return + } if err != nil { log.Printf("Something Went Wrong Loading Categories: %s", err) writeProblem(w, http.StatusInternalServerError, "Something Went Wrong Loading Categories", @@ -201,19 +208,22 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) { }) } -func validateCatagoryIDS(ctx context.Context, submitted []int16) (err error) { +// validateCatagoryIDS checks every submitted id against the category table. On +// the first id that has no category it returns that id alongside +// errUnknownCatagoryID, so the handler can name it in the response. +func validateCatagoryIDS(ctx context.Context, submitted []int16) (int16, error) { categoryIDS, err := repositories.CatagoriesRepo.ListCategoriesIDs(ctx) if err != nil { - return err + return 0, err } for _, submittedID := range submitted { if !slices.Contains(categoryIDS, submittedID) { - return fmt.Errorf("Incorrect Category IDs") + return submittedID, errUnknownCatagoryID } } - return nil + return 0, nil } func randomFilename(original string) (string, error) {