Class: Dry::Types::JSONSchema
- Inherits:
-
Object
- Object
- Dry::Types::JSONSchema
- Defined in:
- lib/dry/types/extensions/json_schema.rb
Overview
The ‘JSONSchema` class is responsible for converting dry-types type definitions into JSON Schema definitions. This class enables the transformation of complex type constraints into a standardized JSON Schema format, facilitating interoperability with systems that utilize JSON Schema for validation.
Constant Summary collapse
- UnknownPredicateError =
Error raised when an unknown predicate is encountered during schema generation.
Class.new(StandardError)
- EMPTY_HASH =
Constant definitions for various lambdas and mappings used throughout the JSON schema conversion process.
{}.freeze
- IDENTITY =
->(v, _) { v }.freeze
- INSPECT =
->(v, _) { v.inspect }.freeze
- TO_INTEGER =
->(v, _) { v.to_i }.freeze
- TO_ARRAY =
->(v, _) { v.to_a }.freeze
- TO_TYPE =
->(v, _) { CLASS_TO_TYPE.fetch(v.to_s.to_sym) }.freeze
- ANNOTATIONS =
Metadata annotations and allowed types overrides for schema generation.
%i[title description].freeze
- ALLOWED_TYPES_META_OVERRIDES =
ANNOTATIONS.dup.concat([:format]).freeze
- ARRAY_PREDICATE_OVERRIDE =
Mapping for array predicate overrides.
{ min_size?: :min_items?, max_size?: :max_items? }.freeze
- CLASS_TO_TYPE =
Mapping of Ruby classes to their corresponding JSON Schema types.
{ String: :string, Integer: :integer, TrueClass: :boolean, FalseClass: :boolean, NilClass: :null, BigDecimal: :number, Float: :number, Hash: :object, Array: :array, Date: :string, DateTime: :string, Time: :string }.freeze
- EXTRA_PROPS_FOR_TYPE =
Additional properties for specific types, such as formatting options.
{ Date: { format: :date }, Time: { format: :time }, DateTime: { format: :"date-time" } }.freeze
- PREDICATE_TO_TYPE =
Mapping of predicate methods to their corresponding JSON Schema expressions.
{ type?: { type: TO_TYPE }, min_size?: { minLength: TO_INTEGER }, min_items?: { minItems: TO_INTEGER }, max_size?: { maxLength: TO_INTEGER }, max_items?: { maxItems: TO_INTEGER }, min?: { maxLength: TO_INTEGER }, gt?: { exclusiveMinimum: IDENTITY }, gteq?: { minimum: IDENTITY }, lt?: { exclusiveMaximum: IDENTITY }, lteq?: { maximum: IDENTITY }, format?: { format: INSPECT }, included_in?: { enum: TO_ARRAY } }.freeze
Instance Attribute Summary collapse
-
#required ⇒ Set
readonly
The set of required keys for the JSON Schema.
Instance Method Summary collapse
-
#call(ast) ⇒ void
Processes the abstract syntax tree (AST) and generates the JSON Schema.
-
#initialize(root: false, loose: false) ⇒ JSONSchema
constructor
Initializes a new instance of the JSONSchema class.
-
#loose? ⇒ Boolean
Checks if unknown predicates are ignored.
-
#root? ⇒ Boolean
Checks if the schema is the root schema.
-
#to_hash ⇒ Hash
Converts the internal schema representation into a hash.
-
#visit(node, opts = EMPTY_HASH) ⇒ void
Visits a node in the abstract syntax tree and processes it according to its type.
- #visit_and(node, opts = EMPTY_HASH) ⇒ Object
- #visit_array(node, opts = EMPTY_HASH) ⇒ Object
- #visit_constrained(node, opts = EMPTY_HASH) ⇒ Object
- #visit_constructor(node, opts = EMPTY_HASH) ⇒ Object
- #visit_enum(node, opts = EMPTY_HASH) ⇒ Object
- #visit_hash(node, opts = EMPTY_HASH) ⇒ Object
- #visit_intersection(node, opts = EMPTY_HASH) ⇒ Object
- #visit_key(node, opts = EMPTY_HASH) ⇒ Object
- #visit_nominal(node, opts = EMPTY_HASH) ⇒ Object
- #visit_predicate(node, opts = EMPTY_HASH) ⇒ Object
- #visit_schema(node, opts = EMPTY_HASH) ⇒ Object
- #visit_struct(node, opts = EMPTY_HASH) ⇒ Object
- #visit_sum(node, opts = EMPTY_HASH) ⇒ Object
Constructor Details
#initialize(root: false, loose: false) ⇒ JSONSchema
Initializes a new instance of the JSONSchema class.
85 86 87 88 89 90 |
# File 'lib/dry/types/extensions/json_schema.rb', line 85 def initialize(root: false, loose: false) @keys = EMPTY_HASH.dup @required = Set.new @root = root @loose = loose end |
Instance Attribute Details
#required ⇒ Set (readonly)
Returns the set of required keys for the JSON Schema.
79 80 81 |
# File 'lib/dry/types/extensions/json_schema.rb', line 79 def required @required end |
Instance Method Details
#call(ast) ⇒ void
This method returns an undefined value.
Processes the abstract syntax tree (AST) and generates the JSON Schema.
106 107 108 |
# File 'lib/dry/types/extensions/json_schema.rb', line 106 def call(ast) visit(ast) end |
#loose? ⇒ Boolean
Checks if unknown predicates are ignored.
100 |
# File 'lib/dry/types/extensions/json_schema.rb', line 100 def loose? = @loose |
#root? ⇒ Boolean
Checks if the schema is the root schema.
95 |
# File 'lib/dry/types/extensions/json_schema.rb', line 95 def root? = @root |
#to_hash ⇒ Hash
Converts the internal schema representation into a hash.
113 114 115 116 117 |
# File 'lib/dry/types/extensions/json_schema.rb', line 113 def to_hash result = @keys.to_hash result[:$schema] = "http://json-schema.org/draft-06/schema#" if root? result end |
#visit(node, opts = EMPTY_HASH) ⇒ void
This method returns an undefined value.
Visits a node in the abstract syntax tree and processes it according to its type.
124 125 126 127 |
# File 'lib/dry/types/extensions/json_schema.rb', line 124 def visit(node, opts = EMPTY_HASH) name, rest = node public_send(:"visit_#{name}", rest, opts) end |
#visit_and(node, opts = EMPTY_HASH) ⇒ Object
199 200 201 202 203 204 205 |
# File 'lib/dry/types/extensions/json_schema.rb', line 199 def visit_and(node, opts = EMPTY_HASH) left, right = node (_, (_, ((_, left_type),))) = left visit(left, opts) visit(right, opts.merge(left_type: left_type)) end |
#visit_array(node, opts = EMPTY_HASH) ⇒ Object
221 222 223 224 225 226 227 |
# File 'lib/dry/types/extensions/json_schema.rb', line 221 def visit_array(node, opts = EMPTY_HASH) type, = node visit(type, opts.merge(array: true)) @keys[opts[:key]].merge!(.slice(*ANNOTATIONS)) if .any? end |
#visit_constrained(node, opts = EMPTY_HASH) ⇒ Object
129 130 131 |
# File 'lib/dry/types/extensions/json_schema.rb', line 129 def visit_constrained(node, opts = EMPTY_HASH) node.each { |it| visit(it, opts) } end |
#visit_constructor(node, opts = EMPTY_HASH) ⇒ Object
133 134 135 136 137 |
# File 'lib/dry/types/extensions/json_schema.rb', line 133 def visit_constructor(node, opts = EMPTY_HASH) type, _ = node visit(type, opts) end |
#visit_enum(node, opts = EMPTY_HASH) ⇒ Object
244 245 246 247 |
# File 'lib/dry/types/extensions/json_schema.rb', line 244 def visit_enum(node, opts = EMPTY_HASH) enum, _ = node visit(enum, opts) end |
#visit_hash(node, opts = EMPTY_HASH) ⇒ Object
207 208 209 210 211 |
# File 'lib/dry/types/extensions/json_schema.rb', line 207 def visit_hash(node, opts = EMPTY_HASH) _part, = node @keys[opts[:key]] = { type: :object } end |
#visit_intersection(node, opts = EMPTY_HASH) ⇒ Object
174 175 176 177 178 179 180 |
# File 'lib/dry/types/extensions/json_schema.rb', line 174 def visit_intersection(node, opts = EMPTY_HASH) *types, _ = node result = types.map { |type| compile_type(type) } @keys[opts[:key]] = deep_merge_items(result) end |
#visit_key(node, opts = EMPTY_HASH) ⇒ Object
249 250 251 252 253 254 255 |
# File 'lib/dry/types/extensions/json_schema.rb', line 249 def visit_key(node, opts = EMPTY_HASH) name, required, rest = node @required << name if required visit(rest, opts.merge(key: name)) end |
#visit_nominal(node, opts = EMPTY_HASH) ⇒ Object
139 140 141 142 143 144 145 146 147 148 |
# File 'lib/dry/types/extensions/json_schema.rb', line 139 def visit_nominal(node, opts = EMPTY_HASH) type, = node if opts.fetch(:key, false) visit_nominal_with_key(node, opts) else @keys.merge!(type: CLASS_TO_TYPE[type.to_s.to_sym]) @keys.merge!(.slice(*ALLOWED_TYPES_META_OVERRIDES)) if .any? end end |
#visit_predicate(node, opts = EMPTY_HASH) ⇒ Object
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/dry/types/extensions/json_schema.rb', line 150 def visit_predicate(node, opts = EMPTY_HASH) head, ((_, type),) = node ctx = opts[:key] head = ARRAY_PREDICATE_OVERRIDE.fetch(head) if opts[:left_type] == ::Array definition = PREDICATE_TO_TYPE.fetch(head) do raise UnknownPredicateError, head unless loose? EMPTY_HASH end.dup definition.transform_values! { |v| v.call(type, ctx) } return unless definition.any? && ctx if (extra = EXTRA_PROPS_FOR_TYPE[type.to_s.to_sym]) definition = definition.merge(extra) end @keys[ctx] ||= {} @keys[ctx].merge!(definition) end |
#visit_schema(node, opts = EMPTY_HASH) ⇒ Object
229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/dry/types/extensions/json_schema.rb', line 229 def visit_schema(node, opts = EMPTY_HASH) keys, _, = node target = self.class.new keys.each { |fragment| target.visit(fragment, opts) } definition = { type: :object, properties: target.to_hash } definition[:required] = target.required.to_a if target.required.any? definition.merge!(.slice(*ANNOTATIONS)) if .any? @keys.merge!(definition) end |
#visit_struct(node, opts = EMPTY_HASH) ⇒ Object
213 214 215 216 217 218 219 |
# File 'lib/dry/types/extensions/json_schema.rb', line 213 def visit_struct(node, opts = EMPTY_HASH) _, schema = node return visit(schema, opts) unless opts[:key] @keys[opts[:key]] = compile_type(schema) end |
#visit_sum(node, opts = EMPTY_HASH) ⇒ Object
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/dry/types/extensions/json_schema.rb', line 182 def visit_sum(node, opts = EMPTY_HASH) *types, _ = node result = types .map { |type| compile_value(type, opts.merge(sum: true)) } .uniq return @keys[opts[:key]] = result.first if result.count == 1 return @keys[opts[:key]] = { anyOf: result } unless opts[:array] @keys[opts[:key]] = { type: :array, items: { anyOf: result } } end |