respect 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +289 -0
- data/RELATED_WORK.md +40 -0
- data/RELEASE_NOTES.md +23 -0
- data/Rakefile +31 -0
- data/STATUS_MATRIX.html +137 -0
- data/lib/respect.rb +231 -0
- data/lib/respect/any_schema.rb +22 -0
- data/lib/respect/array_def.rb +28 -0
- data/lib/respect/array_schema.rb +203 -0
- data/lib/respect/boolean_schema.rb +32 -0
- data/lib/respect/composite_schema.rb +86 -0
- data/lib/respect/core_statements.rb +206 -0
- data/lib/respect/datetime_schema.rb +27 -0
- data/lib/respect/def_without_name.rb +6 -0
- data/lib/respect/divisible_by_validator.rb +20 -0
- data/lib/respect/doc_helper.rb +24 -0
- data/lib/respect/doc_parser.rb +37 -0
- data/lib/respect/dsl_dumper.rb +181 -0
- data/lib/respect/equal_to_validator.rb +20 -0
- data/lib/respect/fake_name_proxy.rb +116 -0
- data/lib/respect/float_schema.rb +27 -0
- data/lib/respect/format_validator.rb +136 -0
- data/lib/respect/global_def.rb +79 -0
- data/lib/respect/greater_than_or_equal_to_validator.rb +19 -0
- data/lib/respect/greater_than_validator.rb +19 -0
- data/lib/respect/has_constraints.rb +34 -0
- data/lib/respect/hash_def.rb +40 -0
- data/lib/respect/hash_schema.rb +218 -0
- data/lib/respect/in_validator.rb +19 -0
- data/lib/respect/integer_schema.rb +27 -0
- data/lib/respect/ip_addr_schema.rb +23 -0
- data/lib/respect/ipv4_addr_schema.rb +27 -0
- data/lib/respect/ipv6_addr_schema.rb +27 -0
- data/lib/respect/items_def.rb +21 -0
- data/lib/respect/json_schema_html_formatter.rb +143 -0
- data/lib/respect/less_than_or_equal_to_validator.rb +19 -0
- data/lib/respect/less_than_validator.rb +19 -0
- data/lib/respect/match_validator.rb +19 -0
- data/lib/respect/max_length_validator.rb +20 -0
- data/lib/respect/min_length_validator.rb +20 -0
- data/lib/respect/multiple_of_validator.rb +10 -0
- data/lib/respect/null_schema.rb +26 -0
- data/lib/respect/numeric_schema.rb +33 -0
- data/lib/respect/org3_dumper.rb +213 -0
- data/lib/respect/regexp_schema.rb +19 -0
- data/lib/respect/schema.rb +285 -0
- data/lib/respect/schema_def.rb +16 -0
- data/lib/respect/string_schema.rb +21 -0
- data/lib/respect/unit_test_helper.rb +37 -0
- data/lib/respect/uri_schema.rb +23 -0
- data/lib/respect/utc_time_schema.rb +17 -0
- data/lib/respect/validator.rb +51 -0
- data/lib/respect/version.rb +3 -0
- data/test/any_schema_test.rb +79 -0
- data/test/array_def_test.rb +113 -0
- data/test/array_schema_test.rb +487 -0
- data/test/boolean_schema_test.rb +89 -0
- data/test/composite_schema_test.rb +30 -0
- data/test/datetime_schema_test.rb +83 -0
- data/test/doc_helper_test.rb +34 -0
- data/test/doc_parser_test.rb +109 -0
- data/test/dsl_dumper_test.rb +395 -0
- data/test/fake_name_proxy_test.rb +138 -0
- data/test/float_schema_test.rb +146 -0
- data/test/format_validator_test.rb +224 -0
- data/test/hash_def_test.rb +126 -0
- data/test/hash_schema_test.rb +613 -0
- data/test/integer_schema_test.rb +142 -0
- data/test/ip_addr_schema_test.rb +78 -0
- data/test/ipv4_addr_schema_test.rb +71 -0
- data/test/ipv6_addr_schema_test.rb +71 -0
- data/test/json_schema_html_formatter_test.rb +214 -0
- data/test/null_schema_test.rb +46 -0
- data/test/numeric_schema_test.rb +294 -0
- data/test/org3_dumper_test.rb +784 -0
- data/test/regexp_schema_test.rb +54 -0
- data/test/respect_test.rb +108 -0
- data/test/schema_def_test.rb +405 -0
- data/test/schema_test.rb +290 -0
- data/test/string_schema_test.rb +209 -0
- data/test/support/circle.rb +11 -0
- data/test/support/color.rb +24 -0
- data/test/support/point.rb +11 -0
- data/test/support/respect/circle_schema.rb +16 -0
- data/test/support/respect/color_def.rb +19 -0
- data/test/support/respect/color_schema.rb +33 -0
- data/test/support/respect/point_schema.rb +19 -0
- data/test/support/respect/rgba_schema.rb +20 -0
- data/test/support/respect/universal_validator.rb +25 -0
- data/test/support/respect/user_macros.rb +12 -0
- data/test/support/rgba.rb +11 -0
- data/test/test_helper.rb +90 -0
- data/test/uri_schema_test.rb +54 -0
- data/test/utc_time_schema_test.rb +63 -0
- data/test/validator_test.rb +22 -0
- metadata +288 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module Respect
|
4
|
+
# Validate a string containing an IPv4 or IPv6 address but also accept IPAddr object.
|
5
|
+
class IPAddrSchema < StringSchema
|
6
|
+
|
7
|
+
def validate_type(object)
|
8
|
+
case object
|
9
|
+
when NilClass
|
10
|
+
if allow_nil?
|
11
|
+
nil
|
12
|
+
else
|
13
|
+
raise ValidationError, "object is nil but this #{self.class} does not allow nil"
|
14
|
+
end
|
15
|
+
when IPAddr
|
16
|
+
object
|
17
|
+
else
|
18
|
+
FormatValidator.new(:ip_addr).validate(object)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end # class IPAddrSchema
|
23
|
+
end # module Respect
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module Respect
|
4
|
+
# Validate a string containing an IPv4 address but also accept IPAddr object.
|
5
|
+
class Ipv4AddrSchema < StringSchema
|
6
|
+
|
7
|
+
def validate_type(object)
|
8
|
+
case object
|
9
|
+
when NilClass
|
10
|
+
if allow_nil?
|
11
|
+
nil
|
12
|
+
else
|
13
|
+
raise ValidationError, "object is nil but this #{self.class} does not allow nil"
|
14
|
+
end
|
15
|
+
when IPAddr
|
16
|
+
if object.ipv4?
|
17
|
+
object
|
18
|
+
else
|
19
|
+
raise ValidationError, "IPAddr object '#{object}' is not IPv4"
|
20
|
+
end
|
21
|
+
else
|
22
|
+
FormatValidator.new(:ipv4_addr).validate(object)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end # class Ipv4AddrSchema
|
27
|
+
end # module Respect
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module Respect
|
4
|
+
# Validate a string containing an IPv6 address but also accept IPAddr object.
|
5
|
+
class Ipv6AddrSchema < StringSchema
|
6
|
+
|
7
|
+
def validate_type(object)
|
8
|
+
case object
|
9
|
+
when NilClass
|
10
|
+
if allow_nil?
|
11
|
+
nil
|
12
|
+
else
|
13
|
+
raise ValidationError, "object is nil but this #{self.class} does not allow nil"
|
14
|
+
end
|
15
|
+
when IPAddr
|
16
|
+
if object.ipv6?
|
17
|
+
object
|
18
|
+
else
|
19
|
+
raise ValidationError, "IPAddr object '#{object}' is not IPv6"
|
20
|
+
end
|
21
|
+
else
|
22
|
+
FormatValidator.new(:ipv6_addr).validate(object)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end # class Ipv6AddrSchema
|
27
|
+
end # module Respect
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Respect
|
2
|
+
class ItemsDef < GlobalDef
|
3
|
+
include_core_statements
|
4
|
+
include DefWithoutName
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@items = []
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def evaluation_result
|
13
|
+
@items
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_context(name, schema)
|
17
|
+
@items << schema
|
18
|
+
schema
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Respect
|
2
|
+
# Format a JSON schema in HTML with CSS class to allow highlighting.
|
3
|
+
class JSONSchemaHTMLFormatter
|
4
|
+
def initialize(json_schema)
|
5
|
+
@indent_level = 0
|
6
|
+
@indent_size = 2
|
7
|
+
@json_schema = json_schema
|
8
|
+
@css_class ||= {
|
9
|
+
json_highlight: "json_highlight",
|
10
|
+
plain: "plain",
|
11
|
+
key: "key",
|
12
|
+
keyword: "keyword",
|
13
|
+
string: "string",
|
14
|
+
numeric: "numeric",
|
15
|
+
comment: "comment",
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :css_class
|
20
|
+
|
21
|
+
def dump(output = "")
|
22
|
+
@output = output
|
23
|
+
@output ||= String.new
|
24
|
+
@output << "<div class=\"#{css_class[:json_highlight]}\"><pre>"
|
25
|
+
@output << dump_json(@json_schema)
|
26
|
+
@output << "</pre></div>\n"
|
27
|
+
@output
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def indent(&block)
|
33
|
+
@indent_level += 1
|
34
|
+
block.call
|
35
|
+
@indent_level -= 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def newline
|
39
|
+
"\n#{indentation}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def indentation
|
43
|
+
" " * @indent_level * @indent_size
|
44
|
+
end
|
45
|
+
|
46
|
+
def dump_json(json)
|
47
|
+
case json
|
48
|
+
when Hash
|
49
|
+
dump_hash(json)
|
50
|
+
when Array
|
51
|
+
dump_array(json)
|
52
|
+
else
|
53
|
+
dump_terminal(json)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def dump_hash(json)
|
58
|
+
result = plain_text("{")
|
59
|
+
indent do
|
60
|
+
result << newline
|
61
|
+
keys = json.keys
|
62
|
+
keys.each_with_index do |key, i|
|
63
|
+
if json[key].is_a? Hash
|
64
|
+
doc = ""
|
65
|
+
if json[key].key? "title"
|
66
|
+
doc << json[key]["title"]
|
67
|
+
json[key].delete("title")
|
68
|
+
end
|
69
|
+
if json[key].key? "description"
|
70
|
+
doc << "\n\n"
|
71
|
+
doc << json[key]["description"]
|
72
|
+
json[key].delete("description")
|
73
|
+
end
|
74
|
+
unless doc.empty?
|
75
|
+
result << comment(doc)
|
76
|
+
result << newline
|
77
|
+
end
|
78
|
+
end
|
79
|
+
result << span(css_class[:key], key.to_s.inspect) << plain_text(":") << " "
|
80
|
+
result << dump_json(json[key])
|
81
|
+
if i < keys.size - 1
|
82
|
+
result << plain_text(",")
|
83
|
+
result << newline
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
result << newline
|
88
|
+
result << plain_text("}")
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
def dump_array(json)
|
93
|
+
result = plain_text("[")
|
94
|
+
indent do
|
95
|
+
result << newline
|
96
|
+
json.each_with_index do |item, i|
|
97
|
+
result << dump_json(item)
|
98
|
+
if i < json.size - 1
|
99
|
+
result << plain_text(",")
|
100
|
+
result << newline
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
result << newline
|
105
|
+
result << plain_text("]")
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
def dump_terminal(json)
|
110
|
+
css = (case json
|
111
|
+
when TrueClass, FalseClass
|
112
|
+
css_class[:keyword]
|
113
|
+
when String
|
114
|
+
css_class[:string]
|
115
|
+
when Numeric
|
116
|
+
css_class[:numeric]
|
117
|
+
else
|
118
|
+
css_class[:plain]
|
119
|
+
end)
|
120
|
+
span(css, json.inspect)
|
121
|
+
end
|
122
|
+
|
123
|
+
def plain_text(text)
|
124
|
+
span(css_class[:plain], text)
|
125
|
+
end
|
126
|
+
|
127
|
+
def tag(tag, klass, value)
|
128
|
+
"<#{tag} class=\"#{klass}\">#{value}</#{tag}>"
|
129
|
+
end
|
130
|
+
|
131
|
+
def span(klass, value)
|
132
|
+
tag("span", klass, value)
|
133
|
+
end
|
134
|
+
|
135
|
+
def comment(text)
|
136
|
+
s = text.dup
|
137
|
+
s.sub!(/\n*\Z/m, '')
|
138
|
+
s.gsub!(/\n/m, "\n#{indentation}// ")
|
139
|
+
s.gsub!(/\s*\n/m, "\n")
|
140
|
+
span(css_class[:comment], "// #{s}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end # module Respect
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Respect
|
2
|
+
class LessThanOrEqualToValidator < Validator
|
3
|
+
def initialize(max)
|
4
|
+
@max = max
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate(value)
|
8
|
+
unless value <= @max
|
9
|
+
raise ValidationError, "#{value} is not less than or equal to #@max"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def to_h_org3
|
16
|
+
{ "maximum" => @max }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Respect
|
2
|
+
class LessThanValidator < Validator
|
3
|
+
def initialize(max)
|
4
|
+
@max = max
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate(value)
|
8
|
+
unless value < @max
|
9
|
+
raise ValidationError, "#{value} is not less than #@max"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def to_h_org3
|
16
|
+
{ "maximum" => @max, "exclusiveMaximum" => true }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Respect
|
2
|
+
class MatchValidator < Validator
|
3
|
+
def initialize(pattern)
|
4
|
+
@pattern = pattern
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate(value)
|
8
|
+
unless value =~ @pattern
|
9
|
+
raise ValidationError, "#{value.inspect} does not match #{@pattern.inspect}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def to_h_org3
|
16
|
+
{ "pattern" => @pattern.source }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Respect
|
2
|
+
class MaxLengthValidator < Validator
|
3
|
+
def initialize(max_length)
|
4
|
+
@max_length = max_length
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate(value)
|
8
|
+
unless value.length <= @max_length
|
9
|
+
raise ValidationError,
|
10
|
+
"#{value.inspect} must be at most #@max_length long but is #{value.length}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def to_h_org3
|
17
|
+
{ 'maxLength' => @max_length }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Respect
|
2
|
+
class MinLengthValidator < Validator
|
3
|
+
def initialize(min_length)
|
4
|
+
@min_length = min_length
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate(value)
|
8
|
+
unless value.length >= @min_length
|
9
|
+
raise ValidationError,
|
10
|
+
"#{value.inspect} must be at least #@min_length long but is #{value.length}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def to_h_org3
|
17
|
+
{ 'minLength' => @min_length }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Respect
|
2
|
+
class NullSchema < Schema
|
3
|
+
|
4
|
+
public_class_method :new
|
5
|
+
|
6
|
+
def validate(object)
|
7
|
+
case object
|
8
|
+
when String
|
9
|
+
if object == "null"
|
10
|
+
self.sanitized_object = nil
|
11
|
+
true
|
12
|
+
else
|
13
|
+
raise ValidationError,
|
14
|
+
"expected 'null' value but got '#{object}:#{object.class}'"
|
15
|
+
end
|
16
|
+
when NilClass
|
17
|
+
self.sanitized_object = nil
|
18
|
+
true
|
19
|
+
else
|
20
|
+
raise ValidationError,
|
21
|
+
"object is not of null type but a #{object.class}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end # class NullSchema
|
26
|
+
end # module Respect
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Respect
|
2
|
+
class NumericSchema < Schema
|
3
|
+
include HasConstraints
|
4
|
+
|
5
|
+
public_class_method :new
|
6
|
+
|
7
|
+
def validate_type(object)
|
8
|
+
case object
|
9
|
+
when String
|
10
|
+
if match_data = /^[-+]?\d+(\.\d+)?$/.match(object)
|
11
|
+
if match_data[1]
|
12
|
+
object.to_f
|
13
|
+
else
|
14
|
+
object.to_i
|
15
|
+
end
|
16
|
+
else
|
17
|
+
raise ValidationError, "malformed numeric value: `#{object}'"
|
18
|
+
end
|
19
|
+
when Integer, Float
|
20
|
+
object
|
21
|
+
when NilClass
|
22
|
+
if allow_nil?
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
raise ValidationError, "object is nil but this #{self.class} does not allow nil"
|
26
|
+
end
|
27
|
+
else
|
28
|
+
raise ValidationError, "object is not a numeric but a '#{object.class}'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end # class NumericSchema
|
33
|
+
end # module Respect
|
@@ -0,0 +1,213 @@
|
|
1
|
+
module Respect
|
2
|
+
# Dump a schema to a hash representation following the format specified
|
3
|
+
# on {json-schema.org standard draft v3}[http://tools.ietf.org/id/draft-zyp-json-schema-03.html].
|
4
|
+
#
|
5
|
+
# The current implementation covers all the _Schema_ and _Validator_ classes
|
6
|
+
# defined in this package. User-defined {Schema} and {Validator} are not guarantee
|
7
|
+
# to work and may never work in the future. The _JSON-Schema_ standard is
|
8
|
+
# a general purpose standard and include only primitive type so it is
|
9
|
+
# very unlikely that it will include your custom schema and validator
|
10
|
+
# out of the box. However, if you can translate your schema/validator
|
11
|
+
# as a composition of primitive type mentioned in the standard it will work.
|
12
|
+
# That's why it is recommended to sub-class {CompositeSchema} when creating
|
13
|
+
# your own schema. User-defined are not properly supported yet as the
|
14
|
+
# API of this dumper is *experimental*. However, an easy way to extend
|
15
|
+
# both the schema and validator class hierarchies will be added in
|
16
|
+
# future releases.
|
17
|
+
class Org3Dumper
|
18
|
+
|
19
|
+
# Translation table mapping DSL options with json-schema.org v3
|
20
|
+
# options. The associated hash is injected in the output. Values
|
21
|
+
# are interpreted as follow:
|
22
|
+
# - :option_value represent the option value passed to the DSL option parameter.
|
23
|
+
# - a proc is called with the option value as argument and the result is used
|
24
|
+
# as the value for the output key if it is not nil.
|
25
|
+
# - other value are inserted verbatim.
|
26
|
+
OPTION_MAP = {
|
27
|
+
min_size: { 'minItems' => :option_value },
|
28
|
+
max_size: { 'maxItems' => :option_value },
|
29
|
+
uniq: { "uniqueItems" => Proc.new{|v| v if v } },
|
30
|
+
default: { "default" => Proc.new{|v| v unless v.nil? } },
|
31
|
+
required: { "required" => Proc.new{|v| true if required? } },
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
def initialize(schema)
|
35
|
+
@schema = schema
|
36
|
+
end
|
37
|
+
|
38
|
+
def dump(output = nil)
|
39
|
+
@output = output
|
40
|
+
@output ||= Hash.new
|
41
|
+
@output = dump_schema(@schema, ignore: [:required])
|
42
|
+
@output
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :output
|
46
|
+
|
47
|
+
def dump_schema(schema, *args)
|
48
|
+
dispatch("dump_schema", schema.class, schema, *args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def dump_schema_for_schema(schema, params = {})
|
52
|
+
return nil if !schema.documented?
|
53
|
+
h = {}
|
54
|
+
h['type'] = dump_statement_name(schema)
|
55
|
+
# Dump generic options.
|
56
|
+
schema.options.each do |opt, opt_value|
|
57
|
+
next if params[:ignore] && params[:ignore].include?(opt)
|
58
|
+
if validator_class = Respect.validator_for(opt)
|
59
|
+
h.merge!(validator_class.new(opt_value).to_h(:org3))
|
60
|
+
elsif Org3Dumper::OPTION_MAP.has_key?(opt)
|
61
|
+
Org3Dumper::OPTION_MAP[opt].each do |k, v|
|
62
|
+
if v == :option_value
|
63
|
+
h[k] = (opt_value.is_a?(Numeric) ? opt_value : opt_value.dup)
|
64
|
+
elsif v.is_a?(Proc)
|
65
|
+
result = schema.instance_exec(opt_value, &v)
|
66
|
+
h[k] = result unless result.nil?
|
67
|
+
else
|
68
|
+
h[k] = v
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
h.merge!(dump_options(schema))
|
74
|
+
# Dump documentation
|
75
|
+
h["title"] = schema.title if schema.title
|
76
|
+
h["description"] = schema.description if schema.description
|
77
|
+
h
|
78
|
+
end
|
79
|
+
|
80
|
+
def dump_schema_for_hash_schema(schema, params = {})
|
81
|
+
h = dump_schema_for_schema(schema, params)
|
82
|
+
return nil if h.nil?
|
83
|
+
props = {}
|
84
|
+
pattern_props = {}
|
85
|
+
additional_props = {}
|
86
|
+
schema.properties.each do |prop, schema|
|
87
|
+
if prop.is_a?(Regexp)
|
88
|
+
if schema.optional?
|
89
|
+
# FIXME(Nicolas Despres): Find a better warning reporting system.
|
90
|
+
warn "pattern properties cannot be optional in json-schema.org draft v3"
|
91
|
+
else
|
92
|
+
# FIXME(Nicolas Despres): What do we do with regexp options such as 'i'?
|
93
|
+
schema_dump = dump_schema(schema)
|
94
|
+
pattern_props[prop.source] = schema_dump if schema_dump
|
95
|
+
end
|
96
|
+
else
|
97
|
+
if schema.optional?
|
98
|
+
schema_dump = dump_schema(schema)
|
99
|
+
additional_props[prop.to_s] = schema_dump if schema_dump
|
100
|
+
else
|
101
|
+
schema_dump = dump_schema(schema)
|
102
|
+
props[prop.to_s] = schema_dump if schema_dump
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
h['properties'] = props unless props.empty?
|
107
|
+
h['patternProperties'] = pattern_props unless pattern_props.empty?
|
108
|
+
if additional_props.empty?
|
109
|
+
if schema.options[:strict]
|
110
|
+
h['additionalProperties'] = false
|
111
|
+
end
|
112
|
+
else
|
113
|
+
h['additionalProperties'] = additional_props
|
114
|
+
end
|
115
|
+
h
|
116
|
+
end
|
117
|
+
|
118
|
+
def dump_schema_for_array_schema(schema, params = {})
|
119
|
+
h = dump_schema_for_schema(schema, params)
|
120
|
+
return nil if h.nil?
|
121
|
+
if schema.item
|
122
|
+
h['items'] = dump_schema(schema.item, ignore: [:required])
|
123
|
+
else
|
124
|
+
if schema.items && !schema.items.empty?
|
125
|
+
h['items'] = schema.items.map do |x|
|
126
|
+
dump_schema(x, ignore: [:required])
|
127
|
+
end
|
128
|
+
end
|
129
|
+
if schema.extra_items && !schema.extra_items.empty?
|
130
|
+
h['additionalItems'] = schema.extra_items.map do |x|
|
131
|
+
dump_schema(x, ignore: [:required])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
h
|
136
|
+
end
|
137
|
+
|
138
|
+
def dump_schema_for_composite_schema(schema, params = {})
|
139
|
+
dump_schema(schema.schema, params)
|
140
|
+
end
|
141
|
+
|
142
|
+
def dump_statement_name(schema, *args)
|
143
|
+
dispatch("dump_statement_name", schema.class, schema, *args)
|
144
|
+
end
|
145
|
+
|
146
|
+
def dump_statement_name_for_schema(schema)
|
147
|
+
schema.class.statement_name
|
148
|
+
end
|
149
|
+
|
150
|
+
def dump_statement_name_for_hash_schema(schema)
|
151
|
+
"object"
|
152
|
+
end
|
153
|
+
|
154
|
+
def dump_statement_name_for_numeric_schema(schema)
|
155
|
+
"number"
|
156
|
+
end
|
157
|
+
|
158
|
+
def dump_statement_name_for_integer_schema(schema)
|
159
|
+
"integer"
|
160
|
+
end
|
161
|
+
|
162
|
+
def dump_statement_name_for_string_schema(schema)
|
163
|
+
"string"
|
164
|
+
end
|
165
|
+
|
166
|
+
def dump_options(schema, *args)
|
167
|
+
dispatch("dump_options", schema.class, schema, *args)
|
168
|
+
end
|
169
|
+
|
170
|
+
def dump_options_for_schema(schema)
|
171
|
+
{}
|
172
|
+
end
|
173
|
+
|
174
|
+
def dump_options_for_uri_schema(schema)
|
175
|
+
{ "format" => "uri" }
|
176
|
+
end
|
177
|
+
|
178
|
+
def dump_options_for_regexp_schema(schema)
|
179
|
+
{ "format" => "regex" }
|
180
|
+
end
|
181
|
+
|
182
|
+
def dump_options_for_datetime_schema(schema)
|
183
|
+
{ "format" => "date-time" }
|
184
|
+
end
|
185
|
+
|
186
|
+
def dump_options_for_ipv4_addr_schema(schema)
|
187
|
+
{ "format" => "ip-address" }
|
188
|
+
end
|
189
|
+
|
190
|
+
def dump_options_for_ipv6_addr_schema(schema)
|
191
|
+
{ "format" => "ipv6" }
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
# Perform a virtual dispatch on a single object.
|
197
|
+
# FIXME(Nicolas Despres): Get me out of here and test me.
|
198
|
+
def dispatch(prefix, klass, object, *args, &block)
|
199
|
+
symbol = "#{prefix}_for_#{klass.name.demodulize.underscore}"
|
200
|
+
if respond_to? symbol
|
201
|
+
send(symbol, object, *args, &block)
|
202
|
+
else
|
203
|
+
if klass == BasicObject
|
204
|
+
raise NoMethodError, "undefined method '#{symbol}' for schema class #{object.class}"
|
205
|
+
else
|
206
|
+
dispatch(prefix, klass.superclass, object, *args, &block)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|