Skip to content

Instantly share code, notes, and snippets.

@schmijos
Created September 19, 2025 14:05
Show Gist options
  • Save schmijos/7ddd39e85a590d7c12cb24c5727aa80e to your computer and use it in GitHub Desktop.
Save schmijos/7ddd39e85a590d7c12cb24c5727aa80e to your computer and use it in GitHub Desktop.
Auto-generate OpenAPI schema field properties from ActiveModel validations
# Configure for rswag:specs:swaggerize
RSpec.configure do |config|
config.openapi_specs = {
"v1/swagger.yaml" => {
components: {
schemas: {
user: User.object_for_rswag(:id, :first_name, :last_name, :email, :portal_role, :status)
}
}
}
}
end
components:
schemas:
user:
type: object
required:
- id
- first_name
- last_name
- email
- status
properties:
id:
type: string
format: uuid
first_name:
type: string
minLength: 1
maxLength: 70
last_name:
type: string
minLength: 1
maxLength: 70
email:
type: string
maxLength: 250
pattern: "\\A[a-zA-Z0-9.!\\#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\\z"
module SwaggerSchemaHelper
def attribute_for_rswag(attribute)
validators = validators_on(attribute).reject { it.is_a?(ActiveRecord::Validations::UniquenessValidator) }
return { type: :string, format: :uuid } if validators.empty?
validators.each_with_object({}) do |validator, options|
case validator
when ActiveRecord::Validations::PresenceValidator
next
when ActiveModel::Validations::LengthValidator
options[:type] = :string
options[:minLength] = validator.options[:minimum] if validator.options[:minimum]
options[:maxLength] = validator.options[:maximum] if validator.options[:maximum]
when ActiveModel::Validations::InclusionValidator
options[:type] = :string
options[:enum] = validator.options[:in]
when ActiveModel::Validations::FormatValidator
options[:pattern] = validator.options[:with].source
when ActiveRecord::Validations::NumericalityValidator
range = validator.options[:in]
options[:type] = validator.options[:only_integer] ? :integer : :number
options[:minimum] = range.begin if range&.begin
options[:maximum] = range.end if range&.end
else
raise "unhandled #{validator.class.name}"
end
options[:nullable] = true if validator.options[:allow_nil]
end
end
def object_for_rswag(*attributes)
properties = attributes.to_h { [ it, attribute_for_rswag(it) ] }
{ type: :object, required: properties.keys.select { !properties[it].key?(:nullable) }, properties: }
end
end
ApplicationRecord.singleton_class.include SwaggerSchemaHelper
# app/models/user.rb
class User < ApplicationRecord
validates :first_name, presence: true, length: { in: 1..70 }
validates :last_name, presence: true, length: { in: 1..70 }
validates :email, presence: true, uniqueness: true, length: { maximum: 250 },
format: { with: URI::MailTo::EMAIL_REGEXP }
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment