Code Monkey home page Code Monkey logo

openapi_first's Introduction

openapi_first

OpenapiFirst helps to implement HTTP APIs based on an OpenAPI API description. It supports OpenAPI 3.0 and 3.1. It offers request and response validation and it ensures that your implementation follows exactly the API description.

Contents

Rack Middlewares

All middlewares add a request object to the current Rack env at env[OpenapiFirst::REQUEST]), which is in an instance of OpenapiFirst::RuntimeRequest that responds to .params, .parsed_body etc.

This gives you access to the converted request parameters and body exaclty as described in your API description instead of relying on Rack alone to parse the request. This only includes query parameters that are defined in the API description. It supports every style and explode value as described in the OpenAPI 3.0 and 3.1 specs.

Request validation

The request validation middleware returns a 4xx if the request is invalid or not defined in the API description.

use OpenapiFirst::Middlewares::RequestValidation, spec: 'openapi.yaml'

Options

Name Possible values Description
spec: The path to the spec file or spec loaded via OpenapiFirst.load
raise_error: false (default), true If set to true the middleware raises OpenapiFirst::RequestInvalidError or OpenapiFirst::NotFoundError instead of returning 4xx.
error_response: :default (default), :jsonapi, Your implementation of ErrorResponse

Error responses

openapi_first produces a useful machine readable error response that can be customized. The default response looks like this. See also RFC 9457.

http-status: 400
content-type: "application/problem+json"

{
  "title": "Bad Request Body",
  "status": 400,
  "errors": [
    {
      "message": "value at `/data/name` is not a string",
      "pointer": "/data/name",
      "code": "string"
    },
    {
      "message": "number at `/data/numberOfLegs` is less than: 2",
      "pointer": "/data/numberOfLegs",
      "code": "minimum"
    },
    {
      "message": "object at `/data` is missing required properties: mandatory",
      "pointer": "/data",
      "code": "required"
    }
  ]
}

openapi_first offers a JSON:API error response as well:

use OpenapiFirst::Middlewares::RequestValidation, spec: 'openapi.yaml, error_response: :jsonapi'
See details of JSON:API error response
// http-status: 400
// content-type: "application/vnd.api+json"

{
  "errors": [
    {
      "status": "400",
      "source": {
        "pointer": "/data/name"
      },
      "title": "value at `/data/name` is not a string",
      "code": "string"
    },
    {
      "status": "400",
      "source": {
        "pointer": "/data/numberOfLegs"
      },
      "title": "number at `/data/numberOfLegs` is less than: 2",
      "code": "minimum"
    },
    {
      "status": "400",
      "source": {
        "pointer": "/data"
      },
      "title": "object at `/data` is missing required properties: mandatory",
      "code": "required"
    }
  ]
}

Custom error responses

You can build your own custom error response with error_response: MyCustomClass that implements OpenapiFirst::ErrorResponse.

readOnly / writeOnly properties

Request validation fails if request includes a property with readOnly: true.

Response validation fails if response body includes a property with writeOnly: true.

Response validation

This middleware is especially useful when testing. It always raises an error if the response is not valid.

use OpenapiFirst::Middlewares::ResponseValidation, spec: 'openapi.yaml' if ENV['RACK_ENV'] == 'test'

Options

Name Possible values Description
spec: The path to the spec file or spec loaded via OpenapiFirst.load

Manual use

Load the API description:

require 'openapi_first'

definition = OpenapiFirst.load('openapi.yaml')

Validate request

# Find and validate request
rack_request = Rack::Request.new(env)
request = definition.validate_request(rack_request)
# Or raise an exception if validation fails:
request = definition.validate_request(rack_request, raise_error: true) # Raises OpenapiFirst::RequestInvalidError or OpenapiFirst::NotFoundError if request is invalid

# Inspect the request and access parsed parameters
request.known? # Is the request defined in the API description?
request.valid? # => true / false
request.error # => Failure object if request is invalid
request.body # alias: parsed_body
request.path_parameters # => { "pet_id" => 42 }
request.query # alias: query_parameters
request.params # Merged path and query parameters
request.headers
request.cookies
request.content_type
request.request_method # => "get"
request.path # => "/pets/42"

Validate response

# Find and validate the response
rack_response = Rack::Response[*app.call(env)]
response = definition.validate_response(rack_request, rack_response)

# Raise an exception if validation fails:
response = definition.validate_response(rack_request,rack_response, raise_error: true) # Raises OpenapiFirst::ResponseInvalidError or OpenapiFirst::ResponseNotFoundError
# Or you can also call a method on the request object mentioned above
request.validate_response(rack_response)

# Inspect the response and access parsed parameters and
response.known? # Is the response defined in the API description?
response.valid? # => true / false
response.error # => Failure object if response is invalid
response.body
request.headers
response.status # => 200
response.content_type

OpenapiFirst uses multi_json.

Configuration

You can configure default options globally:

OpenapiFirst.configure do |config|
  # Specify which plugin is used to render error responses returned by the request validation middleware (defaults to :default)
  config.request_validation_error_response = :jsonapi
  # Configure if the request validation middleware should raise an exception (defaults to false)
  config.request_validation_raise_error = true
end

Framework integration

Using rack middlewares is supported in probably all Ruby web frameworks. If you are using Ruby on Rails for example, you can add the request validation middleware globally in config/application.rb or inside specific controllers.

When running integration tests (or request specs when using rspec), it makes sense to add the response validation middleware to config/environments/test.rb:

config.middleware.use OpenapiFirst::Middlewares::ResponseValidation,
  spec: 'api/openapi.yaml'

That way you don't have to call specific test assertions to make sure your API matches the OpenAPI document. There is no need to run response validation on production if your test coverage is decent.

Alternatives

This gem was inspired by committe (Ruby) and Connexion (Python). Here is a feature comparison between openapi_first and committee.

Development

Run bin/setup to install dependencies.

See bundle exec rake to run the linter and the tests.

Run bundle exec rspec to run the tests only.

Benchmarks

Results

Run benchmarks:

cd benchmarks
bundle
bundle exec ruby benchmarks.rb

Contributing

If you have a question or an idea or found a bug don't hesitate to create an issue or start a discussion.

Pull requests are very welcome as well, of course. Feel free to create a "draft" pull request early on, even if your change is still work in progress. ๐Ÿค—

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.