mega-sharp-sys 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/dry-schema-1.16.0/CHANGELOG.md +901 -0
- data/dry-schema-1.16.0/LICENSE +20 -0
- data/dry-schema-1.16.0/README.md +17 -0
- data/dry-schema-1.16.0/config/errors.yml +127 -0
- data/dry-schema-1.16.0/dry-schema.gemspec +50 -0
- data/dry-schema-1.16.0/lib/dry/schema/compiler.rb +51 -0
- data/dry-schema-1.16.0/lib/dry/schema/config.rb +83 -0
- data/dry-schema-1.16.0/lib/dry/schema/constants.rb +47 -0
- data/dry-schema-1.16.0/lib/dry/schema/dsl.rb +517 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/compiler_methods.rb +19 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/message_compiler_methods.rb +89 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/message_set_methods.rb +44 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/result_methods.rb +47 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints.rb +65 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/info/schema_compiler.rb +123 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/info.rb +27 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/json_schema/schema_compiler.rb +314 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/json_schema.rb +29 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/monads.rb +32 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/struct.rb +62 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions.rb +21 -0
- data/dry-schema-1.16.0/lib/dry/schema/json.rb +17 -0
- data/dry-schema-1.16.0/lib/dry/schema/key.rb +184 -0
- data/dry-schema-1.16.0/lib/dry/schema/key_coercer.rb +41 -0
- data/dry-schema-1.16.0/lib/dry/schema/key_map.rb +136 -0
- data/dry-schema-1.16.0/lib/dry/schema/key_validator.rb +117 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/array.rb +54 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/core.rb +55 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/dsl.rb +282 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/each.rb +31 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/filled.rb +58 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/hash.rb +36 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/key.rb +50 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/maybe.rb +38 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/optional.rb +22 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/required.rb +17 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/schema.rb +103 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/value.rb +125 -0
- data/dry-schema-1.16.0/lib/dry/schema/message/or/abstract.rb +28 -0
- data/dry-schema-1.16.0/lib/dry/schema/message/or/multi_path.rb +100 -0
- data/dry-schema-1.16.0/lib/dry/schema/message/or/single_path.rb +72 -0
- data/dry-schema-1.16.0/lib/dry/schema/message/or.rb +30 -0
- data/dry-schema-1.16.0/lib/dry/schema/message.rb +116 -0
- data/dry-schema-1.16.0/lib/dry/schema/message_compiler/visitor_opts.rb +56 -0
- data/dry-schema-1.16.0/lib/dry/schema/message_compiler.rb +234 -0
- data/dry-schema-1.16.0/lib/dry/schema/message_set.rb +144 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/abstract.rb +202 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/i18n.rb +139 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/namespaced.rb +93 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/template.rb +43 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/yaml.rb +209 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages.rb +32 -0
- data/dry-schema-1.16.0/lib/dry/schema/namespaced_rule.rb +38 -0
- data/dry-schema-1.16.0/lib/dry/schema/params.rb +17 -0
- data/dry-schema-1.16.0/lib/dry/schema/path.rb +107 -0
- data/dry-schema-1.16.0/lib/dry/schema/predicate.rb +102 -0
- data/dry-schema-1.16.0/lib/dry/schema/predicate_inferrer.rb +25 -0
- data/dry-schema-1.16.0/lib/dry/schema/predicate_registry.rb +22 -0
- data/dry-schema-1.16.0/lib/dry/schema/primitive_inferrer.rb +22 -0
- data/dry-schema-1.16.0/lib/dry/schema/processor.rb +213 -0
- data/dry-schema-1.16.0/lib/dry/schema/processor_steps.rb +141 -0
- data/dry-schema-1.16.0/lib/dry/schema/result.rb +203 -0
- data/dry-schema-1.16.0/lib/dry/schema/rule_applier.rb +47 -0
- data/dry-schema-1.16.0/lib/dry/schema/step.rb +59 -0
- data/dry-schema-1.16.0/lib/dry/schema/trace.rb +114 -0
- data/dry-schema-1.16.0/lib/dry/schema/type_container.rb +27 -0
- data/dry-schema-1.16.0/lib/dry/schema/type_registry.rb +44 -0
- data/dry-schema-1.16.0/lib/dry/schema/types.rb +14 -0
- data/dry-schema-1.16.0/lib/dry/schema/types_merger.rb +139 -0
- data/dry-schema-1.16.0/lib/dry/schema/value_coercer.rb +36 -0
- data/dry-schema-1.16.0/lib/dry/schema/version.rb +7 -0
- data/dry-schema-1.16.0/lib/dry/schema.rb +114 -0
- data/dry-schema-1.16.0/lib/dry-schema.rb +3 -0
- data/mega-sharp-sys.gemspec +11 -0
- metadata +114 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/schema/extensions/hints/compiler_methods"
|
|
4
|
+
require "dry/schema/extensions/hints/message_compiler_methods"
|
|
5
|
+
require "dry/schema/extensions/hints/message_set_methods"
|
|
6
|
+
require "dry/schema/extensions/hints/result_methods"
|
|
7
|
+
|
|
8
|
+
module Dry
|
|
9
|
+
module Schema
|
|
10
|
+
# Hint-specific Message extensions
|
|
11
|
+
#
|
|
12
|
+
# @see Message
|
|
13
|
+
#
|
|
14
|
+
# @api public
|
|
15
|
+
class Message
|
|
16
|
+
# Hints extension for Or messages
|
|
17
|
+
#
|
|
18
|
+
# @see Message::Or
|
|
19
|
+
#
|
|
20
|
+
# @api public
|
|
21
|
+
module Or
|
|
22
|
+
class SinglePath
|
|
23
|
+
# @api private
|
|
24
|
+
def hint?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class MultiPath
|
|
30
|
+
# @api private
|
|
31
|
+
def hint?
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @api private
|
|
38
|
+
def hint?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# A hint message sub-type
|
|
44
|
+
#
|
|
45
|
+
# @api private
|
|
46
|
+
class Hint < Message
|
|
47
|
+
# @api private
|
|
48
|
+
def hint?
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Hints extensions
|
|
54
|
+
module Extensions
|
|
55
|
+
# @api private
|
|
56
|
+
module Hints
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
Compiler.prepend(Hints::CompilerMethods)
|
|
60
|
+
MessageCompiler.prepend(Hints::MessageCompilerMethods)
|
|
61
|
+
MessageSet.prepend(Hints::MessageSetMethods)
|
|
62
|
+
Result.prepend(Hints::ResultMethods)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/schema/constants"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
# @api private
|
|
8
|
+
module Info
|
|
9
|
+
# @api private
|
|
10
|
+
class SchemaCompiler
|
|
11
|
+
PREDICATE_TO_TYPE = {
|
|
12
|
+
array?: "array",
|
|
13
|
+
bool?: "bool",
|
|
14
|
+
date?: "date",
|
|
15
|
+
date_time?: "date_time",
|
|
16
|
+
decimal?: "float",
|
|
17
|
+
float?: "float",
|
|
18
|
+
hash?: "hash",
|
|
19
|
+
int?: "integer",
|
|
20
|
+
nil?: "nil",
|
|
21
|
+
str?: "string",
|
|
22
|
+
time?: "time"
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
attr_reader :keys
|
|
27
|
+
|
|
28
|
+
# @api private
|
|
29
|
+
def initialize
|
|
30
|
+
@keys = EMPTY_HASH.dup
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @api private
|
|
34
|
+
def to_h
|
|
35
|
+
{keys: keys}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @api private
|
|
39
|
+
def call(ast)
|
|
40
|
+
visit(ast)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @api private
|
|
44
|
+
def visit(node, opts = EMPTY_HASH)
|
|
45
|
+
meth, rest = node
|
|
46
|
+
public_send(:"visit_#{meth}", rest, opts)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @api private
|
|
50
|
+
def visit_set(node, opts = EMPTY_HASH)
|
|
51
|
+
target = (key = opts[:key]) ? self.class.new : self
|
|
52
|
+
|
|
53
|
+
node.each { |child| target.visit(child, opts) }
|
|
54
|
+
|
|
55
|
+
return unless key
|
|
56
|
+
|
|
57
|
+
target_info = opts[:member] ? {member: target.to_h} : target.to_h
|
|
58
|
+
type = opts[:member] ? "array" : "hash"
|
|
59
|
+
|
|
60
|
+
keys.update(key => {**keys[key], type: type, **target_info})
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @api private
|
|
64
|
+
def visit_and(node, opts = EMPTY_HASH)
|
|
65
|
+
left, right = node
|
|
66
|
+
|
|
67
|
+
visit(left, opts)
|
|
68
|
+
visit(right, opts)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @api private
|
|
72
|
+
def visit_implication(node, opts = EMPTY_HASH)
|
|
73
|
+
case node
|
|
74
|
+
in [:not, [:predicate, [:nil?, _]]], el
|
|
75
|
+
visit(el, {**opts, nullable: true})
|
|
76
|
+
else
|
|
77
|
+
node.each do |el|
|
|
78
|
+
visit(el, {**opts, required: false})
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @api private
|
|
84
|
+
def visit_each(node, opts = EMPTY_HASH)
|
|
85
|
+
visit(node, {**opts, member: true})
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @api private
|
|
89
|
+
def visit_key(node, opts = EMPTY_HASH)
|
|
90
|
+
name, rest = node
|
|
91
|
+
visit(rest, {**opts, key: name, required: true})
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @api private
|
|
95
|
+
def visit_predicate(node, opts = EMPTY_HASH)
|
|
96
|
+
name, rest = node
|
|
97
|
+
|
|
98
|
+
key = opts[:key]
|
|
99
|
+
|
|
100
|
+
if name.equal?(:key?)
|
|
101
|
+
keys[rest[0][1]] = {
|
|
102
|
+
required: opts.fetch(:required, true)
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
type = PREDICATE_TO_TYPE[name]
|
|
106
|
+
nullable = opts.fetch(:nullable, false)
|
|
107
|
+
assign_type(key, type, nullable) if type
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @api private
|
|
112
|
+
def assign_type(key, type, nullable)
|
|
113
|
+
if keys[key][:type]
|
|
114
|
+
keys[key][:member] = type
|
|
115
|
+
else
|
|
116
|
+
keys[key][:type] = type
|
|
117
|
+
keys[key][:nullable] = nullable
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/schema/extensions/info/schema_compiler"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
# Info extension
|
|
8
|
+
#
|
|
9
|
+
# @api public
|
|
10
|
+
module Info
|
|
11
|
+
module SchemaMethods
|
|
12
|
+
# Return information about keys and types
|
|
13
|
+
#
|
|
14
|
+
# @return [Hash<Symbol=>Hash>]
|
|
15
|
+
#
|
|
16
|
+
# @api public
|
|
17
|
+
def info
|
|
18
|
+
compiler = SchemaCompiler.new
|
|
19
|
+
compiler.call(to_ast)
|
|
20
|
+
compiler.to_h
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Processor.include(Info::SchemaMethods)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/schema/constants"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
# @api private
|
|
8
|
+
module JSONSchema
|
|
9
|
+
# @api private
|
|
10
|
+
class SchemaCompiler
|
|
11
|
+
# An error raised when a predicate cannot be converted
|
|
12
|
+
UnknownConversionError = ::Class.new(::StandardError)
|
|
13
|
+
|
|
14
|
+
IDENTITY = ->(v, _) { v }.freeze
|
|
15
|
+
TO_INTEGER = ->(v, _) { v.to_i }.freeze
|
|
16
|
+
|
|
17
|
+
PREDICATE_TO_TYPE = {
|
|
18
|
+
array?: {type: "array"},
|
|
19
|
+
bool?: {type: "boolean"},
|
|
20
|
+
date?: {type: "string", format: "date"},
|
|
21
|
+
date_time?: {type: "string", format: "date-time"},
|
|
22
|
+
decimal?: {type: "number"},
|
|
23
|
+
float?: {type: "number"},
|
|
24
|
+
hash?: {type: "object"},
|
|
25
|
+
int?: {type: "integer"},
|
|
26
|
+
nil?: {type: "null"},
|
|
27
|
+
str?: {type: "string"},
|
|
28
|
+
time?: {type: "string", format: "time"},
|
|
29
|
+
min_size?: {minLength: TO_INTEGER},
|
|
30
|
+
max_size?: {maxLength: TO_INTEGER},
|
|
31
|
+
size?: {maxLength: TO_INTEGER, minLength: TO_INTEGER},
|
|
32
|
+
format?: {
|
|
33
|
+
pattern: proc do |x|
|
|
34
|
+
x.to_s.delete_prefix("(?-mix:").delete_suffix(")")
|
|
35
|
+
end
|
|
36
|
+
},
|
|
37
|
+
true?: {},
|
|
38
|
+
false?: {},
|
|
39
|
+
included_in?: {enum: ->(v, _) { v.to_a }},
|
|
40
|
+
eql?: {const: IDENTITY},
|
|
41
|
+
filled?: EMPTY_HASH,
|
|
42
|
+
uri?: {format: "uri"},
|
|
43
|
+
uuid_v1?: {
|
|
44
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-1[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
|
45
|
+
},
|
|
46
|
+
uuid_v2?: {
|
|
47
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-2[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
|
48
|
+
},
|
|
49
|
+
uuid_v3?: {
|
|
50
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
|
51
|
+
},
|
|
52
|
+
uuid_v4?: {
|
|
53
|
+
pattern: "^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}$"
|
|
54
|
+
},
|
|
55
|
+
uuid_v5?: {
|
|
56
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
|
57
|
+
},
|
|
58
|
+
uuid_v6?: {
|
|
59
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-6[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
|
60
|
+
},
|
|
61
|
+
uuid_v7?: {
|
|
62
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-7[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
|
63
|
+
},
|
|
64
|
+
uuid_v8?: {
|
|
65
|
+
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-8[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
|
|
66
|
+
},
|
|
67
|
+
gt?: {exclusiveMinimum: IDENTITY},
|
|
68
|
+
gteq?: {minimum: IDENTITY},
|
|
69
|
+
lt?: {exclusiveMaximum: IDENTITY},
|
|
70
|
+
lteq?: {maximum: IDENTITY},
|
|
71
|
+
odd?: {type: "integer", not: {multipleOf: 2}},
|
|
72
|
+
even?: {type: "integer", multipleOf: 2}
|
|
73
|
+
}.freeze
|
|
74
|
+
|
|
75
|
+
# @api private
|
|
76
|
+
attr_reader :keys, :required
|
|
77
|
+
|
|
78
|
+
# @api private
|
|
79
|
+
def initialize(root: false, loose: false)
|
|
80
|
+
@keys = EMPTY_HASH.dup
|
|
81
|
+
@required = Set.new
|
|
82
|
+
@root = root
|
|
83
|
+
@loose = loose
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @api private
|
|
87
|
+
def to_hash
|
|
88
|
+
result = {}
|
|
89
|
+
result[:$schema] = "http://json-schema.org/draft-06/schema#" if root?
|
|
90
|
+
result.merge!(type: "object", properties: keys, required: required.to_a)
|
|
91
|
+
result
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
alias_method :to_h, :to_hash
|
|
95
|
+
|
|
96
|
+
# @api private
|
|
97
|
+
def call(ast)
|
|
98
|
+
visit(ast)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @api private
|
|
102
|
+
def visit(node, opts = EMPTY_HASH)
|
|
103
|
+
meth, rest = node
|
|
104
|
+
public_send(:"visit_#{meth}", rest, opts)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @api private
|
|
108
|
+
def visit_set(node, opts = EMPTY_HASH)
|
|
109
|
+
target = (key = opts[:key]) ? self.class.new(loose: loose?) : self
|
|
110
|
+
|
|
111
|
+
node.map { |child| target.visit(child, opts.except(:member)) }
|
|
112
|
+
|
|
113
|
+
return unless key
|
|
114
|
+
|
|
115
|
+
target_info = opts[:member] ? {items: target.to_h} : target.to_h
|
|
116
|
+
type = opts[:member] ? "array" : "object"
|
|
117
|
+
|
|
118
|
+
merge_opts!(keys[key], {type: type, **target_info})
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @api private
|
|
122
|
+
def visit_and(node, opts = EMPTY_HASH)
|
|
123
|
+
left, right = node
|
|
124
|
+
|
|
125
|
+
# We need to know the type first to apply filled macro
|
|
126
|
+
if left[1][0] == :filled?
|
|
127
|
+
visit(right, opts)
|
|
128
|
+
visit(left, opts)
|
|
129
|
+
else
|
|
130
|
+
visit(left, opts)
|
|
131
|
+
visit(right, opts)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @api private
|
|
136
|
+
def visit_or(node, opts = EMPTY_HASH)
|
|
137
|
+
any_of = []
|
|
138
|
+
node.each do |child|
|
|
139
|
+
c = self.class.new(loose: loose?)
|
|
140
|
+
c.keys.update(subschema: {})
|
|
141
|
+
c.visit(child, opts.except(:member).merge(key: :subschema))
|
|
142
|
+
any_of << c.keys[:subschema]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
target = keys[opts[:key]]
|
|
146
|
+
if target[:type]&.include?("array")
|
|
147
|
+
target[:items] = {anyOf: any_of}
|
|
148
|
+
else
|
|
149
|
+
target[:anyOf] = any_of
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @api private
|
|
154
|
+
def visit_implication(node, opts = EMPTY_HASH)
|
|
155
|
+
node.each do |el|
|
|
156
|
+
visit(el, **opts, required: false)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# @api private
|
|
161
|
+
def visit_each(node, opts = EMPTY_HASH)
|
|
162
|
+
visit(node, opts.merge(member: true))
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @api private
|
|
166
|
+
def visit_key(node, opts = EMPTY_HASH)
|
|
167
|
+
name, rest = node
|
|
168
|
+
|
|
169
|
+
if opts.fetch(:required, :true)
|
|
170
|
+
required << name.to_s
|
|
171
|
+
else
|
|
172
|
+
opts.delete(:required)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
visit(rest, opts.merge(key: name))
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @api private
|
|
179
|
+
def visit_not(node, opts = EMPTY_HASH)
|
|
180
|
+
_name, rest = node
|
|
181
|
+
|
|
182
|
+
visit_predicate(rest, opts)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @api private
|
|
186
|
+
def visit_predicate(node, opts = EMPTY_HASH)
|
|
187
|
+
name, rest = node
|
|
188
|
+
|
|
189
|
+
if name.equal?(:key?)
|
|
190
|
+
handle_key_predicate(rest)
|
|
191
|
+
else
|
|
192
|
+
handle_value_predicate(name, rest, opts)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# @api private
|
|
197
|
+
def handle_key_predicate(rest)
|
|
198
|
+
prop_name = rest[0][1]
|
|
199
|
+
keys[prop_name] = {}
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# @api private
|
|
203
|
+
def handle_value_predicate(name, rest, opts)
|
|
204
|
+
target = keys[opts[:key]]
|
|
205
|
+
type_opts = fetch_type_opts_for_predicate(name, rest, target)
|
|
206
|
+
|
|
207
|
+
if array_with_size_predicate?(target, name, opts)
|
|
208
|
+
apply_array_size_constraint(target, name, rest)
|
|
209
|
+
elsif target[:type]&.include?("array")
|
|
210
|
+
apply_array_item_constraint(target, type_opts)
|
|
211
|
+
else
|
|
212
|
+
merge_opts!(target, type_opts)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# @api private
|
|
217
|
+
def array_with_size_predicate?(target, name, opts)
|
|
218
|
+
target[:type]&.include?("array") && array_size_predicate?(name) && !opts[:member]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @api private
|
|
222
|
+
def apply_array_size_constraint(target, name, rest)
|
|
223
|
+
array_type_opts = convert_array_size_predicate(name, rest)
|
|
224
|
+
merge_opts!(target, array_type_opts)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @api private
|
|
228
|
+
def apply_array_item_constraint(target, type_opts)
|
|
229
|
+
target[:items] ||= {}
|
|
230
|
+
merge_opts!(target[:items], type_opts)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# @api private
|
|
234
|
+
def array_size_predicate?(name)
|
|
235
|
+
name == :min_size? || name == :max_size?
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# @api private
|
|
239
|
+
def convert_array_size_predicate(name, rest)
|
|
240
|
+
value = rest[0][1].to_i
|
|
241
|
+
case name
|
|
242
|
+
when :min_size?
|
|
243
|
+
{minItems: value}
|
|
244
|
+
when :max_size?
|
|
245
|
+
{maxItems: value}
|
|
246
|
+
else
|
|
247
|
+
{}
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# @api private
|
|
252
|
+
def fetch_type_opts_for_predicate(name, rest, target)
|
|
253
|
+
type_opts = PREDICATE_TO_TYPE.fetch(name) do
|
|
254
|
+
raise_unknown_conversion_error!(:predicate, name) unless loose?
|
|
255
|
+
|
|
256
|
+
EMPTY_HASH
|
|
257
|
+
end.dup
|
|
258
|
+
type_opts.transform_values! { |v| v.respond_to?(:call) ? v.call(rest[0][1], target) : v }
|
|
259
|
+
type_opts.merge!(fetch_filled_options(target[:type], target)) if name == :filled?
|
|
260
|
+
type_opts
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# @api private
|
|
264
|
+
def fetch_filled_options(type, _target)
|
|
265
|
+
case type
|
|
266
|
+
when "string"
|
|
267
|
+
{minLength: 1}
|
|
268
|
+
when "array"
|
|
269
|
+
raise_unknown_conversion_error!(:type, :array) unless loose?
|
|
270
|
+
|
|
271
|
+
{not: {type: "null"}}
|
|
272
|
+
else
|
|
273
|
+
{not: {type: "null"}}
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# @api private
|
|
278
|
+
def merge_opts!(orig_opts, new_opts)
|
|
279
|
+
new_type = new_opts[:type]
|
|
280
|
+
orig_type = orig_opts[:type]
|
|
281
|
+
|
|
282
|
+
if orig_type && new_type && orig_type != new_type
|
|
283
|
+
new_opts[:type] = [orig_type, new_type].flatten.uniq
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
orig_opts.merge!(new_opts)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# @api private
|
|
290
|
+
def root?
|
|
291
|
+
@root
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# @api private
|
|
295
|
+
def loose?
|
|
296
|
+
@loose
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def raise_unknown_conversion_error!(type, name)
|
|
300
|
+
message = <<~MSG
|
|
301
|
+
Could not find an equivalent conversion for #{type} #{name.inspect}.
|
|
302
|
+
|
|
303
|
+
This means that your generated JSON schema may be missing this validation.
|
|
304
|
+
|
|
305
|
+
You can ignore this by generating the schema in "loose" mode, i.e.:
|
|
306
|
+
my_schema.json_schema(loose: true)
|
|
307
|
+
MSG
|
|
308
|
+
|
|
309
|
+
raise UnknownConversionError, message.chomp
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/schema/extensions/json_schema/schema_compiler"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
# JSONSchema extension
|
|
8
|
+
#
|
|
9
|
+
# @api public
|
|
10
|
+
module JSONSchema
|
|
11
|
+
module SchemaMethods
|
|
12
|
+
# Convert the schema into a JSON schema hash
|
|
13
|
+
#
|
|
14
|
+
# @param [Symbol] loose Compile the schema in "loose" mode
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash<Symbol=>Hash>]
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
def json_schema(loose: false)
|
|
20
|
+
compiler = SchemaCompiler.new(root: true, loose: loose)
|
|
21
|
+
compiler.call(to_ast)
|
|
22
|
+
compiler.to_hash
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Processor.include(JSONSchema::SchemaMethods)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/monads"
|
|
4
|
+
|
|
5
|
+
# preload monads
|
|
6
|
+
Dry::Monads[:result]
|
|
7
|
+
|
|
8
|
+
module Dry
|
|
9
|
+
module Schema
|
|
10
|
+
# Monad extension for Result
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
13
|
+
class Result
|
|
14
|
+
include ::Dry::Monads::Result::Mixin
|
|
15
|
+
|
|
16
|
+
# Turn result into a monad
|
|
17
|
+
#
|
|
18
|
+
# This makes result objects work with dry-monads (or anything with a compatible interface)
|
|
19
|
+
#
|
|
20
|
+
# @return [Dry::Monads::Success,Dry::Monads::Failure]
|
|
21
|
+
#
|
|
22
|
+
# @api public
|
|
23
|
+
def to_monad
|
|
24
|
+
if success?
|
|
25
|
+
Success(self)
|
|
26
|
+
else
|
|
27
|
+
Failure(self)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/struct"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
module Macros
|
|
8
|
+
class StructToSchema < ::Dry::Struct::Compiler
|
|
9
|
+
def call(struct)
|
|
10
|
+
visit(struct.to_ast)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# strip away structs from AST
|
|
14
|
+
def visit_struct(node)
|
|
15
|
+
_, ast = node
|
|
16
|
+
visit(ast)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Hash.option :struct_compiler, default: proc { StructToSchema.new(schema_dsl.config.types) }
|
|
21
|
+
|
|
22
|
+
Hash.prepend(::Module.new {
|
|
23
|
+
def call(*args)
|
|
24
|
+
if args.size >= 1 && struct?(args[0])
|
|
25
|
+
if block_given?
|
|
26
|
+
raise ArgumentError, "blocks are not supported when using " \
|
|
27
|
+
"a struct class (#{name.inspect} => #{args[0]})"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
struct_class = extract_struct_class(args[0])
|
|
31
|
+
schema = struct_compiler.(struct_class)
|
|
32
|
+
|
|
33
|
+
super(schema, *args.drop(1))
|
|
34
|
+
type(schema_dsl.types[name].constructor(schema))
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def struct?(type)
|
|
43
|
+
(type.is_a?(::Class) && type <= ::Dry::Struct) ||
|
|
44
|
+
(type.is_a?(::Dry::Types::Constructor) && type.primitive <= ::Dry::Struct)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def extract_struct_class(type)
|
|
48
|
+
if type.is_a?(::Class) && type <= ::Dry::Struct
|
|
49
|
+
type
|
|
50
|
+
elsif type.is_a?(::Dry::Types::Constructor) && type.primitive <= ::Dry::Struct
|
|
51
|
+
type.primitive
|
|
52
|
+
else
|
|
53
|
+
type
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
})
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
PredicateInferrer::Compiler.alias_method(:visit_struct, :visit_hash)
|
|
60
|
+
PrimitiveInferrer::Compiler.alias_method(:visit_struct, :visit_hash)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Dry::Schema.register_extension(:monads) do
|
|
4
|
+
require "dry/schema/extensions/monads"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
Dry::Schema.register_extension(:hints) do
|
|
8
|
+
require "dry/schema/extensions/hints"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Dry::Schema.register_extension(:struct) do
|
|
12
|
+
require "dry/schema/extensions/struct"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Dry::Schema.register_extension(:info) do
|
|
16
|
+
require "dry/schema/extensions/info"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Dry::Schema.register_extension(:json_schema) do
|
|
20
|
+
require "dry/schema/extensions/json_schema"
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Schema
|
|
5
|
+
# JSON schema type
|
|
6
|
+
#
|
|
7
|
+
# @see Processor
|
|
8
|
+
# @see Schema#JSON
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
class JSON < Processor
|
|
12
|
+
config.key_map_type = :stringified
|
|
13
|
+
config.type_registry_namespace = :json
|
|
14
|
+
config.filter_empty_string = false
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|