Class: Dry::Schema::JSONSchema::SchemaCompiler Private

Inherits:
Object
  • Object
show all
Defined in:
lib/dry/schema/extensions/json_schema/schema_compiler.rb

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Constant Summary collapse

UnknownConversionError =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

An error raised when a predicate cannot be converted

::Class.new(::StandardError)
IDENTITY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

->(v, _) { v }.freeze
TO_INTEGER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

->(v, _) { v.to_i }.freeze
PREDICATE_TO_TYPE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{
  array?: {type: "array"},
  bool?: {type: "boolean"},
  date?: {type: "string", format: "date"},
  date_time?: {type: "string", format: "date-time"},
  decimal?: {type: "number"},
  float?: {type: "number"},
  hash?: {type: "object"},
  int?: {type: "integer"},
  nil?: {type: "null"},
  str?: {type: "string"},
  time?: {type: "string", format: "time"},
  min_size?: {minLength: TO_INTEGER},
  max_size?: {maxLength: TO_INTEGER},
  size?: {maxLength: TO_INTEGER, minLength: TO_INTEGER},
  format?: {
    pattern: proc do |x|
      x.to_s.delete_prefix("(?-mix:").delete_suffix(")")
    end
  },
  true?: {},
  false?: {},
  included_in?: {enum: ->(v, _) { v.to_a }},
  eql?: {const: IDENTITY},
  filled?: EMPTY_HASH,
  uri?: {format: "uri"},
  uuid_v1?: {
    pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-1[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
  },
  uuid_v2?: {
    pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-2[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
  },
  uuid_v3?: {
    pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
  },
  uuid_v4?: {
    pattern: "^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}$"
  },
  uuid_v5?: {
    pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
  },
  uuid_v6?: {
    pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-6[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
  },
  uuid_v7?: {
    pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-7[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
  },
  uuid_v8?: {
    pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-8[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
  },
  gt?: {exclusiveMinimum: IDENTITY},
  gteq?: {minimum: IDENTITY},
  lt?: {exclusiveMaximum: IDENTITY},
  lteq?: {maximum: IDENTITY},
  odd?: {type: "integer", not: {multipleOf: 2}},
  even?: {type: "integer", multipleOf: 2}
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root: false, loose: false) ⇒ SchemaCompiler

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of SchemaCompiler.



79
80
81
82
83
84
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 79

def initialize(root: false, loose: false)
  @keys = EMPTY_HASH.dup
  @required = Set.new
  @root = root
  @loose = loose
end

Instance Attribute Details

#keysObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



76
77
78
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 76

def keys
  @keys
end

#requiredObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



76
77
78
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 76

def required
  @required
end

Instance Method Details

#apply_array_item_constraint(target, type_opts) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



228
229
230
231
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 228

def apply_array_item_constraint(target, type_opts)
  target[:items] ||= {}
  merge_opts!(target[:items], type_opts)
end

#apply_array_size_constraint(target, name, rest) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



222
223
224
225
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 222

def apply_array_size_constraint(target, name, rest)
  array_type_opts = convert_array_size_predicate(name, rest)
  merge_opts!(target, array_type_opts)
end

#array_size_predicate?(name) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


234
235
236
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 234

def array_size_predicate?(name)
  name == :min_size? || name == :max_size?
end

#array_with_size_predicate?(target, name, opts) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


217
218
219
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 217

def array_with_size_predicate?(target, name, opts)
  target[:type]&.include?("array") && array_size_predicate?(name) && !opts[:member]
end

#call(ast) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



97
98
99
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 97

def call(ast)
  visit(ast)
end

#convert_array_size_predicate(name, rest) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



239
240
241
242
243
244
245
246
247
248
249
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 239

def convert_array_size_predicate(name, rest)
  value = rest[0][1].to_i
  case name
  when :min_size?
    {minItems: value}
  when :max_size?
    {maxItems: value}
  else
    {}
  end
end

#fetch_filled_options(type, _target) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 264

def fetch_filled_options(type, _target)
  case type
  when "string"
    {minLength: 1}
  when "array"
    raise_unknown_conversion_error!(:type, :array) unless loose?

    {not: {type: "null"}}
  else
    {not: {type: "null"}}
  end
end

#fetch_type_opts_for_predicate(name, rest, target) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



252
253
254
255
256
257
258
259
260
261
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 252

def fetch_type_opts_for_predicate(name, rest, target)
  type_opts = PREDICATE_TO_TYPE.fetch(name) do
    raise_unknown_conversion_error!(:predicate, name) unless loose?

    EMPTY_HASH
  end.dup
  type_opts.transform_values! { |v| v.respond_to?(:call) ? v.call(rest[0][1], target) : v }
  type_opts.merge!(fetch_filled_options(target[:type], target)) if name == :filled?
  type_opts
end

#handle_key_predicate(rest) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



197
198
199
200
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 197

def handle_key_predicate(rest)
  prop_name = rest[0][1]
  keys[prop_name] = {}
end

#handle_value_predicate(name, rest, opts) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 203

def handle_value_predicate(name, rest, opts)
  target = keys[opts[:key]]
  type_opts = fetch_type_opts_for_predicate(name, rest, target)

  if array_with_size_predicate?(target, name, opts)
    apply_array_size_constraint(target, name, rest)
  elsif target[:type]&.include?("array")
    apply_array_item_constraint(target, type_opts)
  else
    merge_opts!(target, type_opts)
  end
end

#loose?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


295
296
297
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 295

def loose?
  @loose
end

#merge_opts!(orig_opts, new_opts) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



278
279
280
281
282
283
284
285
286
287
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 278

def merge_opts!(orig_opts, new_opts)
  new_type = new_opts[:type]
  orig_type = orig_opts[:type]

  if orig_type && new_type && orig_type != new_type
    new_opts[:type] = [orig_type, new_type].flatten.uniq
  end

  orig_opts.merge!(new_opts)
end

#raise_unknown_conversion_error!(type, name) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 299

def raise_unknown_conversion_error!(type, name)
  message = "    Could not find an equivalent conversion for \#{type} \#{name.inspect}.\n\n    This means that your generated JSON schema may be missing this validation.\n\n    You can ignore this by generating the schema in \"loose\" mode, i.e.:\n        my_schema.json_schema(loose: true)\n  MSG\n\n  raise UnknownConversionError, message.chomp\nend\n"

#root?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


290
291
292
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 290

def root?
  @root
end

#to_hashObject Also known as: to_h

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



87
88
89
90
91
92
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 87

def to_hash
  result = {}
  result[:$schema] = "http://json-schema.org/draft-06/schema#" if root?
  result.merge!(type: "object", properties: keys, required: required.to_a)
  result
end

#visit(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



102
103
104
105
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 102

def visit(node, opts = EMPTY_HASH)
  meth, rest = node
  public_send(:"visit_#{meth}", rest, opts)
end

#visit_and(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 122

def visit_and(node, opts = EMPTY_HASH)
  left, right = node

  # We need to know the type first to apply filled macro
  if left[1][0] == :filled?
    visit(right, opts)
    visit(left, opts)
  else
    visit(left, opts)
    visit(right, opts)
  end
end

#visit_each(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



161
162
163
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 161

def visit_each(node, opts = EMPTY_HASH)
  visit(node, opts.merge(member: true))
end

#visit_implication(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



154
155
156
157
158
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 154

def visit_implication(node, opts = EMPTY_HASH)
  node.each do |el|
    visit(el, **opts, required: false)
  end
end

#visit_key(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



166
167
168
169
170
171
172
173
174
175
176
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 166

def visit_key(node, opts = EMPTY_HASH)
  name, rest = node

  if opts.fetch(:required, :true)
    required << name.to_s
  else
    opts.delete(:required)
  end

  visit(rest, opts.merge(key: name))
end

#visit_not(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



179
180
181
182
183
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 179

def visit_not(node, opts = EMPTY_HASH)
  _name, rest = node

  visit_predicate(rest, opts)
end

#visit_or(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 136

def visit_or(node, opts = EMPTY_HASH)
  any_of = []
  node.each do |child|
    c = self.class.new(loose: loose?)
    c.keys.update(subschema: {})
    c.visit(child, opts.except(:member).merge(key: :subschema))
    any_of << c.keys[:subschema]
  end

  target = keys[opts[:key]]
  if target[:type]&.include?("array")
    target[:items] = {anyOf: any_of}
  else
    target[:anyOf] = any_of
  end
end

#visit_predicate(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



186
187
188
189
190
191
192
193
194
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 186

def visit_predicate(node, opts = EMPTY_HASH)
  name, rest = node

  if name.equal?(:key?)
    handle_key_predicate(rest)
  else
    handle_value_predicate(name, rest, opts)
  end
end

#visit_set(node, opts = EMPTY_HASH) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 108

def visit_set(node, opts = EMPTY_HASH)
  target = (key = opts[:key]) ? self.class.new(loose: loose?) : self

  node.map { |child| target.visit(child, opts.except(:member)) }

  return unless key

  target_info = opts[:member] ? {items: target.to_h} : target.to_h
  type = opts[:member] ? "array" : "object"

  merge_opts!(keys[key], {type: type, **target_info})
end