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, _) { Array(v) }.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 91 |
# 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.
107 108 109 |
# File 'lib/dry/types/extensions/json_schema.rb', line 107 def call(ast) visit(ast) end |
#loose? ⇒ Boolean
Checks if unknown predicates are ignored.
101 |
# File 'lib/dry/types/extensions/json_schema.rb', line 101 def loose? = @loose |
#root? ⇒ Boolean
Checks if the schema is the root schema.
96 |
# File 'lib/dry/types/extensions/json_schema.rb', line 96 def root? = @root |
#to_hash ⇒ Hash
Converts the internal schema representation into a hash.
114 115 116 117 118 |
# File 'lib/dry/types/extensions/json_schema.rb', line 114 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.
125 126 127 128 |
# File 'lib/dry/types/extensions/json_schema.rb', line 125 def visit(node, opts = EMPTY_HASH) name, rest = node public_send(:"visit_#{name}", rest, opts) end |
#visit_and(node, opts = EMPTY_HASH) ⇒ Object
208 209 210 211 212 213 214 |
# File 'lib/dry/types/extensions/json_schema.rb', line 208 def visit_and(node, opts = EMPTY_HASH) left, right = node (_, (_, ((_, left_type),))) = left visit(left, opts) visit(right, { left_type: left_type }.merge(opts)) end |
#visit_array(node, opts = EMPTY_HASH) ⇒ Object
230 231 232 233 234 235 236 |
# File 'lib/dry/types/extensions/json_schema.rb', line 230 def visit_array(node, opts = EMPTY_HASH) type, = node visit(type, { array: true }.merge(opts)) @keys[opts[:key]].merge!(.slice(*ANNOTATIONS)) if .any? end |
#visit_constrained(node, opts = EMPTY_HASH) ⇒ Object
130 131 132 |
# File 'lib/dry/types/extensions/json_schema.rb', line 130 def visit_constrained(node, opts = EMPTY_HASH) node.each { |it| visit(it, opts) } end |
#visit_constructor(node, opts = EMPTY_HASH) ⇒ Object
134 135 136 137 138 |
# File 'lib/dry/types/extensions/json_schema.rb', line 134 def visit_constructor(node, opts = EMPTY_HASH) type, _ = node visit(type, opts) end |
#visit_enum(node, opts = EMPTY_HASH) ⇒ Object
253 254 255 256 |
# File 'lib/dry/types/extensions/json_schema.rb', line 253 def visit_enum(node, opts = EMPTY_HASH) enum, _ = node visit(enum, opts) end |
#visit_hash(node, opts = EMPTY_HASH) ⇒ Object
216 217 218 219 220 |
# File 'lib/dry/types/extensions/json_schema.rb', line 216 def visit_hash(node, opts = EMPTY_HASH) _part, = node @keys[opts[:key]] = { type: :object } end |
#visit_intersection(node, opts = EMPTY_HASH) ⇒ Object
183 184 185 186 187 188 189 |
# File 'lib/dry/types/extensions/json_schema.rb', line 183 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
258 259 260 261 262 263 264 |
# File 'lib/dry/types/extensions/json_schema.rb', line 258 def visit_key(node, opts = EMPTY_HASH) name, required, rest = node @required << name if required visit(rest, { key: name }.merge(opts)) end |
#visit_nominal(node, opts = EMPTY_HASH) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/dry/types/extensions/json_schema.rb', line 140 def visit_nominal(node, opts = EMPTY_HASH) type, = node if opts.key?(:key) visit_nominal_with_key(node, opts) else if opts.key?(:array) @keys.merge!(items: { type: CLASS_TO_TYPE[type.to_s.to_sym] }) else @keys.merge!(type: CLASS_TO_TYPE[type.to_s.to_sym]) end @keys.merge!(.slice(*ALLOWED_TYPES_META_OVERRIDES)) if .any? end end |
#visit_predicate(node, opts = EMPTY_HASH) ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/dry/types/extensions/json_schema.rb', line 155 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? if (extra = EXTRA_PROPS_FOR_TYPE[type.to_s.to_sym]) definition = definition.merge(extra) end if ctx.nil? @keys.merge!(definition) else @keys[ctx] ||= {} @keys[ctx].merge!(definition) end end |
#visit_schema(node, opts = EMPTY_HASH) ⇒ Object
238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/dry/types/extensions/json_schema.rb', line 238 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
222 223 224 225 226 227 228 |
# File 'lib/dry/types/extensions/json_schema.rb', line 222 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
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/dry/types/extensions/json_schema.rb', line 191 def visit_sum(node, opts = EMPTY_HASH) *types, _ = node result = types .map { |type| compile_value(type, { sum: true }.merge(opts)) } .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 |