No release in over 3 years
Automatically convert dry-validation contract definitions to OpenAPI 3.0 schemas for use with rswag and other OpenAPI tools. Uses static parsing to extract type information from contract params blocks.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 13.0
~> 3.12
 Project Readme

DryValidationOpenapi

Automatically generate OpenAPI 3.0 schemas from dry-validation contracts for use with rswag and other OpenAPI tools.

Instead of manually writing OpenAPI schemas for your API documentation, this gem extracts type information directly from your dry-validation contract definitions using static parsing.

Features

  • 🎯 Zero runtime overhead - Uses static parsing, no contract instantiation needed
  • 🔄 Automatic conversion - Converts dry-validation types to OpenAPI types with formats
  • 🪆 Nested schemas - Handles nested objects and arrays of objects
  • 📝 Simple API - Just extend your contract and call .open_api_schema
  • Type formats - Includes OpenAPI format specifications (int32, double, date-time, etc.)

Supported Features

✅ Currently Supported

  • Required and optional fields
  • Basic types: string, integer, float, decimal, boolean
  • Type formats: int32, double, float, date, date-time
  • Array types with item schemas
  • Nested objects/hashes with properties
  • Arrays of objects with nested schemas
  • Multiple nested objects at the same level
  • .value() and .filled() predicates

📋 Not Yet Implemented

  • Custom type formats (email, uuid, etc.)
  • Descriptions and examples
  • Min/max constraints
  • Enums
  • Custom rules/validations
  • Pattern validations
  • Maybe/nil handling

Installation

Add this line to your application's Gemfile:

gem 'dry_validation_openapi'

And then execute:

bundle install

Or install it yourself as:

gem install dry_validation_openapi

Usage

Basic Usage

require 'dry_validation_openapi'

class CreateUserContract < Dry::Validation::Contract
  extend DryValidationOpenapi::Convertable

  params do
    required(:email).value(:string)
    required(:age).value(:integer)
    optional(:name).value(:string)
  end
end

# Generate OpenAPI schema
schema = CreateUserContract.open_api_schema
# => {
#   type: :object,
#   properties: {
#     email: { type: :string },
#     age: { type: :integer, format: 'int32' },
#     name: { type: :string }
#   },
#   required: ['email', 'age']
# }

With rswag

Use in your rswag request specs:

# spec/requests/users_spec.rb
require 'swagger_helper'

RSpec.describe 'Users API' do
  path '/users' do
    post 'Creates a user' do
      tags 'Users'
      consumes 'application/json'

      parameter name: :body,
                in: :body,
                required: true,
                schema: CreateUserContract.open_api_schema

      response '201', 'user created' do
        let(:body) { { email: 'user@example.com', age: 25 } }
        run_test!
      end
    end
  end
end

Nested Objects

class CreateOrderContract < Dry::Validation::Contract
  extend DryValidationOpenapi::Convertable

  params do
    required(:order_id).value(:string)
    required(:customer).hash do
      required(:name).value(:string)
      required(:email).value(:string)
    end
  end
end

CreateOrderContract.open_api_schema
# => {
#   type: :object,
#   properties: {
#     order_id: { type: :string },
#     customer: {
#       type: :object,
#       properties: {
#         name: { type: :string },
#         email: { type: :string }
#       },
#       required: ['name', 'email']
#     }
#   },
#   required: ['order_id', 'customer']
# }

Arrays of Objects

class CreateInvoiceContract < Dry::Validation::Contract
  extend DryValidationOpenapi::Convertable

  params do
    required(:issue_date).value(:time)
    required(:line_items).array(:hash) do
      required(:description).filled(:string)
      required(:amount).filled(:decimal)
    end
  end
end

CreateInvoiceContract.open_api_schema
# => {
#   type: :object,
#   properties: {
#     issue_date: { type: :string, format: 'date-time' },
#     line_items: {
#       type: :array,
#       items: {
#         type: :object,
#         properties: {
#           description: { type: :string },
#           amount: { type: :number, format: 'double' }
#         },
#         required: ['description', 'amount']
#       }
#     }
#   },
#   required: ['issue_date', 'line_items']
# }

Type Mappings

dry-validation type OpenAPI type OpenAPI format
:string string -
:integer, :int integer int32
:float number float
:decimal, :number number double
:bool, :boolean boolean -
:date string date
:time, :date_time string date-time
:hash object -
:array array -

How It Works

This gem uses static parsing rather than runtime introspection:

  1. Reads the contract file as plain text using the contract's source location
  2. Parses to AST using Ruby's built-in Ripper parser
  3. Walks the AST to find the params do...end block
  4. Extracts field definitions (required/optional, names, types, nesting)
  5. Converts to OpenAPI schema format

This approach is:

  • ✅ Fast and lightweight (no contract instantiation)
  • ✅ Simple to understand (just parsing Ruby code)
  • ✅ Works without dry-validation loaded
  • ⚠️ Cannot handle dynamically-generated contracts (rare in practice)

Development

After checking out the repo, run:

bundle install

Run the tests:

bundle exec rspec

Build the gem:

gem build dry_validation_openapi.gemspec

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/troptropcontent/dry_validation_openapi.

License

The gem is available as open source under the terms of the MIT License.

Credits

Created to solve the problem of maintaining duplicate schema definitions in dry-validation contracts and OpenAPI documentation.