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 }},
  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.



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

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.



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

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.



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

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.



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

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.



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

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)


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

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)


210
211
212
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 210

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.



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

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.



232
233
234
235
236
237
238
239
240
241
242
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 232

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.



257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 257

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.



245
246
247
248
249
250
251
252
253
254
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 245

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.



190
191
192
193
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 190

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.



196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 196

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)


288
289
290
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 288

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.



271
272
273
274
275
276
277
278
279
280
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 271

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.



292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 292

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)


283
284
285
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 283

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.



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

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.



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

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.



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

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.



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

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.



147
148
149
150
151
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 147

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.



159
160
161
162
163
164
165
166
167
168
169
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 159

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.



172
173
174
175
176
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 172

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.



135
136
137
138
139
140
141
142
143
144
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 135

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

    any_of = (keys[opts[:key]][:anyOf] ||= [])
    any_of << c.keys[:subschema]
  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.



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

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.



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

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