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



61
62
63
64
65
66
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 61

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.



58
59
60
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 58

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.



58
59
60
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 58

def required
  @required
end

Instance Method Details

#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.



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

def call(ast)
  visit(ast)
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.



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

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.



182
183
184
185
186
187
188
189
190
191
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 182

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

#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)


225
226
227
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 225

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.



208
209
210
211
212
213
214
215
216
217
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 208

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]
  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.



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

def raise_unknown_conversion_error!(type, name)
  message = <<~MSG
    Could not find an equivalent conversion for #{type} #{name.inspect}.

    This means that your generated JSON schema may be missing this validation.

    You can ignore this by generating the schema in "loose" mode, i.e.:
        my_schema.json_schema(loose: true)
  MSG

  raise UnknownConversionError, message.chomp
end

#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)


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

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.



69
70
71
72
73
74
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 69

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.



84
85
86
87
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 84

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.



104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 104

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.



137
138
139
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 137

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.



130
131
132
133
134
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 130

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.



142
143
144
145
146
147
148
149
150
151
152
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 142

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.



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

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.



118
119
120
121
122
123
124
125
126
127
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 118

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.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 162

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

  if name.equal?(:key?)
    prop_name = rest[0][1]
    keys[prop_name] = {}
  else
    target = keys[opts[:key]]
    type_opts = fetch_type_opts_for_predicate(name, rest, target)

    if target[:type]&.include?("array")
      target[:items] ||= {}
      merge_opts!(target[:items], type_opts)
    else
      merge_opts!(target, type_opts)
    end
  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.



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/dry/schema/extensions/json_schema/schema_compiler.rb', line 90

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

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

  return unless key

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

  keys.update(key => {**keys[key], type: type, **target_info})
end