Skip to content

Default POST success responses to 201 - #72

Open
olivier-thatch wants to merge 7 commits into
numbata:mainfrom
olivier-thatch:post-201-default
Open

olivier-thatch wants to merge 7 commits into
numbata:mainfrom
olivier-thatch:post-201-default

Conversation

@olivier-thatch

@olivier-thatch olivier-thatch commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Problem

POST routes without response documentation already infer 201, but adding an entity through success: Kitten, a success hash without a code, or an entity combined with response documentation can incorrectly change the documented status to 200. Some entity paths also ignore default_status:, including on GET and PUT routes.

Fix

Use the same success-status inference across response parsers: an explicit response code wins, followed by default_status:, then 201 for POST or 200 for other methods. This applies to the shared response model used by OAS 2.0, 3.0, and 3.1. An entity without an explicit code or default_status: does not append a method-inferred response when a 2xx response is already declared. Explicit entity codes and default_status: remain authoritative. DELETE retains its existing default. Entries under failure: and generic http_codes: retain their previous default_status: or 200 fallback, without POST inference.

Example

class Kitten < Grape::Entity
  expose :name, documentation: { type: String }
end

class API < Grape::API
  format :json
  post("kittens", success: Kitten) { { name: "Milo" } }
end

The same inference applies to a success hash without an entity, such as success: { message: "Created" }, as well as success: { model: Kitten }, entity: { model: Kitten }, and desc block entity responses. For an appended entity response, default_status: 202 now overrides the previous hardcoded 200.

Schema before / after

OAS 3.0/3.1 success-response excerpt for POST /kittens (other responses are unchanged):

# Before
responses:
  '200':
    description: Success
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Kitten'

# After
responses:
  '201':
    description: Success
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Kitten'

The code-free success-hash and entity-hash forms produce the same change. With entity: { model: Kitten }, default_status: 202, the response key changes from '200' to '202' instead, preserving the same response content.

OAS 2.0 makes the same response-key changes, retaining its response schema:

# Before
responses:
  '200':
    description: Success
    schema:
      $ref: '#/definitions/Kitten'

# After
responses:
  '201':
    description: Success
    schema:
      $ref: '#/definitions/Kitten'

Backward compatibility

Public API signatures are unchanged. Honoring default_status: for entity responses is intentional for every HTTP method: for example, GET/PUT with entity: { model: Kitten }, default_status: 204 now emits a bodyless 204 instead of 200 with an entity schema. Choose a status that permits a body, such as 200 or 202, when the response should include the entity. Affected generated responses change from 200 to 201, or to the configured default_status: where it was previously ignored; this can affect generated clients and schema snapshots. APIs intentionally returning 200 should document success: { code: 200, model: Kitten } or set default_status: 200. Explicit response codes keep precedence. Emitted output for inputs outside these inference paths is unchanged. No dependencies are added.

@github-actions

github-actions Bot commented Apr 21, 2026

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@olivier-thatch
olivier-thatch marked this pull request as ready for review April 21, 2026 16:41
end

def mock_route(options: {}, settings: {})
def mock_route(options: {}, settings: {}, request_method: "GET")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mock now supports request_method, but there are no tests in this file for a POST route + desc block combination. That's a valid real-world scenario (e.g. desc "Create thing" { success: SomeEntity } on a POST endpoint). Worth adding one test to verify it returns 201. 🤷

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — added test_desc_block_plain_entity_on_post_infers_201 in cea069b. Uses a real entity class in a desc block on a POST route and asserts the inferred code is 201.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, I asked Claude to handle your comment and expected it to write the test. I didn't think it would write the test, commit it, push it, and reply to you using my GitHub account. All hail our new robot overlords 🤖

Anyway, is the new test what you had in mind?

Comment thread lib/grape_oas/api_model_builders/response_parsers/base.rb Outdated
@olivier-thatch
olivier-thatch force-pushed the post-201-default branch 2 times, most recently from cbfa96a to 9a33812 Compare April 27, 2026 16:19
@olivier-thatch
olivier-thatch requested a review from numbata April 28, 2026 16:44
olivier-thatch and others added 4 commits September 6, 2026 03:08
When only an entity is given (e.g. `success: Entities::Kitten`), infer
201 for POST routes and 200 for all other methods. Explicit `code:` and
`default_status:` still take precedence.

Extract a shared `default_success_code` helper on the parsers' Base
module and use it from DefaultResponseParser and the four spots in
HttpCodesParser that previously hardcoded 200.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers `desc "..." { success: SomeEntity }` on a POST endpoint — the
inferred code should be 201.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants