Skip to content

Add Sinatra integration (OpenapiFirst::Sinatra)#483

Draft
ahx wants to merge 2 commits into
mainfrom
sinatra-extension
Draft

Add Sinatra integration (OpenapiFirst::Sinatra)#483
ahx wants to merge 2 commits into
mainfrom
sinatra-extension

Conversation

@ahx

@ahx ahx commented Jun 3, 2026

Copy link
Copy Markdown
Owner

A Sinatra extension to define routes by referencing OpenAPI operations, so URLs and HTTP methods live only in the API description:

require 'openapi_first/sinatra'

class PetsApp < Sinatra::Base
  register OpenapiFirst::Sinatra
  openapi 'openapi.yaml'

  operation(:list_pets) do |params|
    json list_pets(params[:filter])
  end
end

The HTTP method and path for each route come from the operationId. Request validation is called automatically for these operations.

Why?

  • Performance: Use only Sinatra's routing instead of routing in the middleware once, then again in Sinatra
  • No redundant path definition in OAD and code
  • This does not add a middleware. It allows us to run request validation for some and not for other routes (good? bad?)

Related to #365

@ahx ahx force-pushed the sinatra-extension branch 2 times, most recently from 640794f to de8bce8 Compare June 3, 2026 13:23

@jzobel jzobel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nicely done — reads clean and is unusually well-tested and documented for an experimental extension. Inline notes below; two more worth flagging beyond those:

1 (design) — routing is delegated to Sinatra/Mustermann, but validation uses the OAD template. The two matchers can diverge at the edges (trailing slash, dot-in-segment, encoding, greediness): a request Mustermann matches is validated against the template it was defined with, using the params Mustermann extracted — not openapi_first's own path matching. Fine for the covered cases; worth a docs note that the two routers' path semantics should be kept aligned. (Mustermann itself is healthy — 4.0.0, Apr 2026 — so this is about semantic divergence, not dependency risk.)

5 (minor) — build_operation_index is first-wins on duplicate operationIds (index[operation_id] ||= …): a duplicate is silently dropped. Invalid OAD, but a warn/raise would beat silent.

Comment thread lib/openapi_first/sinatra.rb Outdated
# The parsed request body
# @return [Sinatra::IndifferentHash]
def parsed_body
::Sinatra::IndifferentHash[openapi_request.parsed_body]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

parsed_body assumes an object body: Sinatra::IndifferentHash[openapi_request.parsed_body] raises for a top-level JSON array/scalar body (and for a bodyless request, where parsed_body is nil). Consider guarding for non-Hash bodies, or documenting it as object-only and pointing to openapi_request.parsed_body for arrays/scalars.

Comment thread lib/openapi_first/request.rb Outdated

def validate(request, route_params:)
parsed_request, error = parse_request(request, route_params:)
def validate(request, path_params:)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Naming flag: route_params:path_params: is clearer (they are path params). If Request#validate / parse_request count as public / manual-use API, this is a breaking keyword rename — worth a CHANGELOG line; ignore if they're internal.

Comment thread CHANGELOG.md Outdated
```

The HTTP method and path for each route come from the operationId.
Request validation is called automatically for these operations

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Formatting nits: add a blank line before ## 3.4.3, give this line a trailing period, and drop the trailing whitespace on the closing ``` fence a few lines up. (README also has trailing whitespace on the blank line inside the create_pet example, ~L442.)

ahx and others added 2 commits July 6, 2026 10:33
Resolve the path for an operation by operationId, filling in path
parameters. Extracted from the Sinatra integration, where it backs the
operation_url helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A Sinatra extension to define routes by referencing OpenAPI operations,
so URLs and HTTP methods live only in the description:

    require 'openapi_first/sinatra'

    class PetsApi < Sinatra::Base
    register OpenapiFirst::Sinatra
    openapi 'openapi.yaml'

    operation :index_pets do |params|
    json index_pets(params[:filter])
    end
    end

The HTTP method and path for each route come from the operationId.
Request validation is called automatically for these operations.
@ahx ahx force-pushed the sinatra-extension branch from de8bce8 to 49f9d9c Compare July 6, 2026 08:36
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