json-schema 0.1.12 → 0.1.13
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.
- data/README.textile +15 -50
- data/lib/json-schema/validator.rb +104 -0
- data/test/test_jsonschema.rb +201 -0
- metadata +5 -5
data/README.textile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
h1. Ruby JSON Schema Validator
|
2
2
|
|
3
|
-
This library is intended to provide Ruby with an interface for validating JSON objects against a JSON schema conforming to "JSON Schema Draft 3":http://tools.ietf.org/html/draft-zyp-json-schema-03.
|
3
|
+
This library is intended to provide Ruby with an interface for validating JSON objects against a JSON schema conforming to "JSON Schema Draft 3":http://tools.ietf.org/html/draft-zyp-json-schema-03.
|
4
4
|
|
5
5
|
h2. Usage
|
6
6
|
|
@@ -13,8 +13,8 @@ gem install json-schema
|
|
13
13
|
If downloading the git repo, build the gem and install it:
|
14
14
|
|
15
15
|
<pre>
|
16
|
-
$
|
17
|
-
$ gem install
|
16
|
+
$ gem build json-schema.gemspec
|
17
|
+
$ gem install json-schema-0.1.13.gem
|
18
18
|
</pre>
|
19
19
|
|
20
20
|
<pre>
|
@@ -51,56 +51,21 @@ end
|
|
51
51
|
</pre>
|
52
52
|
|
53
53
|
|
54
|
-
h2.
|
55
|
-
|
56
|
-
The following core schema
|
57
|
-
|
58
|
-
* type
|
59
|
-
* properties
|
60
|
-
* patternProperties
|
61
|
-
* additionalProperties
|
62
|
-
* items
|
63
|
-
* additionalItems
|
64
|
-
* required
|
65
|
-
* dependencies
|
66
|
-
* minimum
|
67
|
-
* maximum
|
68
|
-
* exclusiveMinimum
|
69
|
-
* exclusiveMaximum
|
70
|
-
* minItems
|
71
|
-
* maxItems
|
72
|
-
* uniqueItems
|
73
|
-
* pattern
|
74
|
-
* minLength
|
75
|
-
* maxLength
|
76
|
-
* enum
|
77
|
-
* title
|
78
|
-
* description
|
79
|
-
* divisibleBy
|
80
|
-
* disallow
|
81
|
-
* extends
|
82
|
-
* id
|
83
|
-
* $ref (this implementation only follows slash-delimited fragment resolution)
|
84
|
-
|
85
|
-
|
86
|
-
h2. Not implemented
|
87
|
-
|
88
|
-
The following core schema definitions are not implemented:
|
89
|
-
|
90
|
-
* format - Some of the formats listed will be supported, others are fairly vague in their definition and may be left out
|
54
|
+
h2. Notes
|
55
|
+
|
56
|
+
The following core schema attributes are not implemented:
|
57
|
+
|
91
58
|
* default - This library aims to solely be a validator and does not modify an object it is validating.
|
92
59
|
* $schema - Support for this will come later, possibly with the ability to validate against other JSON Schema draft versions
|
93
60
|
|
94
|
-
|
95
|
-
|
96
|
-
* links
|
97
|
-
* fragmentResolution (only handles default slash-delimited)
|
98
|
-
* contentEncoding
|
99
|
-
* pathStart
|
61
|
+
The 'format' attribute is only validated for the following values:
|
100
62
|
|
63
|
+
* date-time
|
64
|
+
* date
|
65
|
+
* time
|
66
|
+
* ip-address
|
67
|
+
* ipv6
|
101
68
|
|
102
|
-
|
69
|
+
All other 'format' attribute values are simply checked to ensure the instance value is of the correct datatype (e.g., an instance value is validated to be an integer or a float in the case of 'utc-millisec').
|
103
70
|
|
104
|
-
|
105
|
-
* Documentation
|
106
|
-
* Breaking the current validator out into a subclass, acting as the foundation for supporting multiple versions of validators
|
71
|
+
Additionally, JSON::Validator does not handle any json hyperschema attributes.
|
@@ -3,6 +3,7 @@ require 'open-uri'
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'bigdecimal'
|
5
5
|
require 'digest/sha1'
|
6
|
+
require 'date'
|
6
7
|
|
7
8
|
module JSON
|
8
9
|
|
@@ -48,6 +49,7 @@ module JSON
|
|
48
49
|
"additionalItems",
|
49
50
|
"dependencies",
|
50
51
|
"extends",
|
52
|
+
"format",
|
51
53
|
"$ref"
|
52
54
|
]
|
53
55
|
|
@@ -175,6 +177,108 @@ module JSON
|
|
175
177
|
end
|
176
178
|
|
177
179
|
|
180
|
+
# Validate the format of an item
|
181
|
+
def validate_format(current_schema, data, fragments)
|
182
|
+
case current_schema.schema['format']
|
183
|
+
|
184
|
+
# Timestamp in restricted ISO-8601 YYYY-MM-DDThh:mm:ssZ
|
185
|
+
when 'date-time'
|
186
|
+
error_message = "The property '#{build_fragment(fragments)}' must be a string and be a date/time in the ISO-8601 format of YYYY-MM-DDThh:mm:ssZ"
|
187
|
+
raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
|
188
|
+
r = Regexp.new('^\d\d\d\d-\d\d-\d\dT(\d\d):(\d\d):(\d\d)Z$')
|
189
|
+
if (m = r.match(data))
|
190
|
+
parts = data.split("T")
|
191
|
+
begin
|
192
|
+
Date.parse(parts[0])
|
193
|
+
rescue Exception
|
194
|
+
raise ValidationError.new(error_message, fragments, current_schema)
|
195
|
+
end
|
196
|
+
begin
|
197
|
+
raise ValidationError.new(error_message, fragments, current_schema) if m[1].to_i > 23
|
198
|
+
raise ValidationError.new(error_message, fragments, current_schema) if m[2].to_i > 59
|
199
|
+
raise ValidationError.new(error_message, fragments, current_schema) if m[3].to_i > 59
|
200
|
+
rescue Exception
|
201
|
+
raise ValidationError.new(error_message, fragments, current_schema)
|
202
|
+
end
|
203
|
+
else
|
204
|
+
raise ValidationError.new(error_message, fragments, current_schema)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Date in the format of YYYY-MM-DD
|
208
|
+
when 'date'
|
209
|
+
error_message = "The property '#{build_fragment(fragments)}' must be a string and be a date in the format of YYYY-MM-DD"
|
210
|
+
raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
|
211
|
+
r = Regexp.new('^\d\d\d\d-\d\d-\d\d$')
|
212
|
+
if (m = r.match(data))
|
213
|
+
begin
|
214
|
+
Date.parse(data)
|
215
|
+
rescue Exception
|
216
|
+
raise ValidationError.new(error_message, fragments, current_schema)
|
217
|
+
end
|
218
|
+
else
|
219
|
+
raise ValidationError.new(error_message, fragments, current_schema)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Time in the format of HH:MM:SS
|
223
|
+
when 'time'
|
224
|
+
error_message = "The property '#{build_fragment(fragments)}' must be a string and be a time in the format of hh:mm:ss"
|
225
|
+
raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
|
226
|
+
r = Regexp.new('^(\d\d):(\d\d):(\d\d)$')
|
227
|
+
if (m = r.match(data))
|
228
|
+
raise ValidationError.new(error_message, fragments, current_schema) if m[1].to_i > 23
|
229
|
+
raise ValidationError.new(error_message, fragments, current_schema) if m[2].to_i > 59
|
230
|
+
raise ValidationError.new(error_message, fragments, current_schema) if m[3].to_i > 59
|
231
|
+
else
|
232
|
+
raise ValidationError.new(error_message, fragments, current_schema)
|
233
|
+
end
|
234
|
+
|
235
|
+
# IPv4 in dotted-quad format
|
236
|
+
when 'ip-address', 'ipv4'
|
237
|
+
error_message = "The property '#{build_fragment(fragments)}' must be a string and be a valid IPv4 address"
|
238
|
+
raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
|
239
|
+
r = Regexp.new('^(\d+){1,3}\.(\d+){1,3}\.(\d+){1,3}\.(\d+){1,3}$')
|
240
|
+
if (m = r.match(data))
|
241
|
+
1.upto(4) do |x|
|
242
|
+
raise ValidationError.new(error_message, fragments, current_schema) if m[x].to_i > 255
|
243
|
+
end
|
244
|
+
else
|
245
|
+
raise ValidationError.new(error_message, fragments, current_schema)
|
246
|
+
end
|
247
|
+
|
248
|
+
# IPv6 in standard format (including abbreviations)
|
249
|
+
when 'ipv6'
|
250
|
+
error_message = "The property '#{build_fragment(fragments)}' must be a string and be a valid IPv6 address"
|
251
|
+
raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
|
252
|
+
r = Regexp.new('^[a-f0-9:]+$')
|
253
|
+
if (m = r.match(data))
|
254
|
+
# All characters are valid, now validate structure
|
255
|
+
parts = data.split(":")
|
256
|
+
raise ValidationError.new(error_message, fragments, current_schema) if parts.length > 8
|
257
|
+
condensed_zeros = false
|
258
|
+
parts.each do |part|
|
259
|
+
if part.length == 0
|
260
|
+
raise ValidationError.new(error_message, fragments, current_schema) if condensed_zeros
|
261
|
+
condensed_zeros = true
|
262
|
+
end
|
263
|
+
raise ValidationError.new(error_message, fragments, current_schema) if part.length > 4
|
264
|
+
end
|
265
|
+
else
|
266
|
+
raise ValidationError.new(error_message, fragments, current_schema)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Milliseconds since the epoch. Must be an integer or a float
|
270
|
+
when 'utc-millisec'
|
271
|
+
error_message = "The property '#{build_fragment(fragments)}' must be an integer or a float"
|
272
|
+
raise ValidationError.new(error_message, fragments, current_schema) if (!data.is_a?(Numeric))
|
273
|
+
|
274
|
+
# Must be a string
|
275
|
+
when 'regex','color','style','phone','uri','email','host-name'
|
276
|
+
error_message = "The property '#{build_fragment(fragments)}' must be a string"
|
277
|
+
raise ValidationError.new(error_message, fragments, current_schema) if (!data.is_a?(String))
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
|
178
282
|
# Validate the minimum value of a number
|
179
283
|
def validate_minimum(current_schema, data, fragments)
|
180
284
|
if data.is_a?(Numeric)
|
data/test/test_jsonschema.rb
CHANGED
@@ -744,5 +744,206 @@ class JSONSchemaTest < Test::Unit::TestCase
|
|
744
744
|
|
745
745
|
end
|
746
746
|
|
747
|
+
|
748
|
+
def test_self_reference
|
749
|
+
schema = {
|
750
|
+
"type" => "object",
|
751
|
+
"properties" => { "a" => {"type" => "integer"}, "b" => {"$ref" => "#"}}
|
752
|
+
}
|
753
|
+
|
754
|
+
data = {"a" => 5, "b" => {"b" => {"a" => 1}}}
|
755
|
+
assert(JSON::Validator.validate(schema,data))
|
756
|
+
data = {"a" => 5, "b" => {"b" => {"a" => 'taco'}}}
|
757
|
+
assert(!JSON::Validator.validate(schema,data))
|
758
|
+
end
|
759
|
+
|
760
|
+
|
761
|
+
def test_format_ipv4
|
762
|
+
schema = {
|
763
|
+
"type" => "object",
|
764
|
+
"properties" => { "a" => {"type" => "string", "format" => "ip-address"}}
|
765
|
+
}
|
766
|
+
|
767
|
+
data = {"a" => "1.1.1.1"}
|
768
|
+
assert(JSON::Validator.validate(schema,data))
|
769
|
+
data = {"a" => "1.1.1"}
|
770
|
+
assert(!JSON::Validator.validate(schema,data))
|
771
|
+
data = {"a" => "1.1.1.300"}
|
772
|
+
assert(!JSON::Validator.validate(schema,data))
|
773
|
+
data = {"a" => 5}
|
774
|
+
assert(!JSON::Validator.validate(schema,data))
|
775
|
+
data = {"a" => "1.1.1"}
|
776
|
+
assert(!JSON::Validator.validate(schema,data))
|
777
|
+
data = {"a" => "1.1.1.1b"}
|
778
|
+
assert(!JSON::Validator.validate(schema,data))
|
779
|
+
data = {"a" => "b1.1.1.1"}
|
780
|
+
end
|
781
|
+
|
782
|
+
|
783
|
+
def test_format_ipv6
|
784
|
+
schema = {
|
785
|
+
"type" => "object",
|
786
|
+
"properties" => { "a" => {"type" => "string", "format" => "ipv6"}}
|
787
|
+
}
|
788
|
+
|
789
|
+
data = {"a" => "1111:2222:8888:9999:aaaa:cccc:eeee:ffff"}
|
790
|
+
assert(JSON::Validator.validate(schema,data))
|
791
|
+
data = {"a" => "1111:0:8888:0:0:0:eeee:ffff"}
|
792
|
+
assert(JSON::Validator.validate(schema,data))
|
793
|
+
data = {"a" => "1111:2222:8888::eeee:ffff"}
|
794
|
+
assert(JSON::Validator.validate(schema,data))
|
795
|
+
data = {"a" => "1111:2222:8888:99999:aaaa:cccc:eeee:ffff"}
|
796
|
+
assert(!JSON::Validator.validate(schema,data))
|
797
|
+
data = {"a" => "1111:2222:8888:9999:aaaa:cccc:eeee:gggg"}
|
798
|
+
assert(!JSON::Validator.validate(schema,data))
|
799
|
+
data = {"a" => "1111:2222::9999::cccc:eeee:ffff"}
|
800
|
+
assert(!JSON::Validator.validate(schema,data))
|
801
|
+
data = {"a" => "1111:2222:8888:9999:aaaa:cccc:eeee:ffff:bbbb"}
|
802
|
+
assert(!JSON::Validator.validate(schema,data))
|
803
|
+
end
|
804
|
+
|
805
|
+
def test_format_time
|
806
|
+
schema = {
|
807
|
+
"type" => "object",
|
808
|
+
"properties" => { "a" => {"type" => "string", "format" => "time"}}
|
809
|
+
}
|
810
|
+
|
811
|
+
data = {"a" => "12:00:00"}
|
812
|
+
assert(JSON::Validator.validate(schema,data))
|
813
|
+
data = {"a" => "12:00"}
|
814
|
+
assert(!JSON::Validator.validate(schema,data))
|
815
|
+
data = {"a" => "12:00:60"}
|
816
|
+
assert(!JSON::Validator.validate(schema,data))
|
817
|
+
data = {"a" => "12:60:00"}
|
818
|
+
assert(!JSON::Validator.validate(schema,data))
|
819
|
+
data = {"a" => "24:00:00"}
|
820
|
+
assert(!JSON::Validator.validate(schema,data))
|
821
|
+
data = {"a" => "0:00:00"}
|
822
|
+
assert(!JSON::Validator.validate(schema,data))
|
823
|
+
data = {"a" => "-12:00:00"}
|
824
|
+
assert(!JSON::Validator.validate(schema,data))
|
825
|
+
data = {"a" => "12:00:00b"}
|
826
|
+
assert(!JSON::Validator.validate(schema,data))
|
827
|
+
end
|
828
|
+
|
829
|
+
|
830
|
+
def test_format_date
|
831
|
+
schema = {
|
832
|
+
"type" => "object",
|
833
|
+
"properties" => { "a" => {"type" => "string", "format" => "date"}}
|
834
|
+
}
|
835
|
+
|
836
|
+
data = {"a" => "2010-01-01"}
|
837
|
+
assert(JSON::Validator.validate(schema,data))
|
838
|
+
data = {"a" => "2010-01-32"}
|
839
|
+
assert(!JSON::Validator.validate(schema,data))
|
840
|
+
data = {"a" => "n2010-01-01"}
|
841
|
+
assert(!JSON::Validator.validate(schema,data))
|
842
|
+
data = {"a" => "2010-1-01"}
|
843
|
+
assert(!JSON::Validator.validate(schema,data))
|
844
|
+
data = {"a" => "2010-01-1"}
|
845
|
+
assert(!JSON::Validator.validate(schema,data))
|
846
|
+
data = {"a" => "2010-01-01n"}
|
847
|
+
assert(!JSON::Validator.validate(schema,data))
|
848
|
+
end
|
849
|
+
|
850
|
+
def test_format_datetime
|
851
|
+
schema = {
|
852
|
+
"type" => "object",
|
853
|
+
"properties" => { "a" => {"type" => "string", "format" => "date-time"}}
|
854
|
+
}
|
855
|
+
|
856
|
+
data = {"a" => "2010-01-01T12:00:00Z"}
|
857
|
+
assert(JSON::Validator.validate(schema,data))
|
858
|
+
data = {"a" => "2010-01-32T12:00:00Z"}
|
859
|
+
assert(!JSON::Validator.validate(schema,data))
|
860
|
+
data = {"a" => "2010-13-01T12:00:00Z"}
|
861
|
+
assert(!JSON::Validator.validate(schema,data))
|
862
|
+
data = {"a" => "2010-01-01T24:00:00Z"}
|
863
|
+
assert(!JSON::Validator.validate(schema,data))
|
864
|
+
data = {"a" => "2010-01-01T12:60:00Z"}
|
865
|
+
assert(!JSON::Validator.validate(schema,data))
|
866
|
+
data = {"a" => "2010-01-01T12:00:60Z"}
|
867
|
+
assert(!JSON::Validator.validate(schema,data))
|
868
|
+
data = {"a" => "2010-01-01T12:00:00"}
|
869
|
+
assert(!JSON::Validator.validate(schema,data))
|
870
|
+
data = {"a" => "2010-01-01T12:00:00z"}
|
871
|
+
assert(!JSON::Validator.validate(schema,data))
|
872
|
+
data = {"a" => "2010-01-0112:00:00Z"}
|
873
|
+
assert(!JSON::Validator.validate(schema,data))
|
874
|
+
end
|
875
|
+
|
876
|
+
|
877
|
+
def test_format_strings
|
878
|
+
data1 = {"a" => "boo"}
|
879
|
+
data2 = {"a" => 5}
|
880
|
+
|
881
|
+
schema = {
|
882
|
+
"type" => "object",
|
883
|
+
"properties" => { "a" => {"format" => "regex"}}
|
884
|
+
}
|
885
|
+
assert(JSON::Validator.validate(schema,data1))
|
886
|
+
assert(!JSON::Validator.validate(schema,data2))
|
887
|
+
|
888
|
+
schema = {
|
889
|
+
"type" => "object",
|
890
|
+
"properties" => { "a" => {"format" => "color"}}
|
891
|
+
}
|
892
|
+
assert(JSON::Validator.validate(schema,data1))
|
893
|
+
assert(!JSON::Validator.validate(schema,data2))
|
894
|
+
|
895
|
+
schema = {
|
896
|
+
"type" => "object",
|
897
|
+
"properties" => { "a" => {"format" => "style"}}
|
898
|
+
}
|
899
|
+
assert(JSON::Validator.validate(schema,data1))
|
900
|
+
assert(!JSON::Validator.validate(schema,data2))
|
901
|
+
|
902
|
+
schema = {
|
903
|
+
"type" => "object",
|
904
|
+
"properties" => { "a" => {"format" => "phone"}}
|
905
|
+
}
|
906
|
+
assert(JSON::Validator.validate(schema,data1))
|
907
|
+
assert(!JSON::Validator.validate(schema,data2))
|
908
|
+
|
909
|
+
schema = {
|
910
|
+
"type" => "object",
|
911
|
+
"properties" => { "a" => {"format" => "uri"}}
|
912
|
+
}
|
913
|
+
assert(JSON::Validator.validate(schema,data1))
|
914
|
+
assert(!JSON::Validator.validate(schema,data2))
|
915
|
+
|
916
|
+
schema = {
|
917
|
+
"type" => "object",
|
918
|
+
"properties" => { "a" => {"format" => "email"}}
|
919
|
+
}
|
920
|
+
assert(JSON::Validator.validate(schema,data1))
|
921
|
+
assert(!JSON::Validator.validate(schema,data2))
|
922
|
+
|
923
|
+
schema = {
|
924
|
+
"type" => "object",
|
925
|
+
"properties" => { "a" => {"format" => "host-name"}}
|
926
|
+
}
|
927
|
+
assert(JSON::Validator.validate(schema,data1))
|
928
|
+
assert(!JSON::Validator.validate(schema,data2))
|
929
|
+
end
|
930
|
+
|
931
|
+
|
932
|
+
def test_format_numeric
|
933
|
+
data1 = {"a" => "boo"}
|
934
|
+
data2 = {"a" => 5}
|
935
|
+
data3 = {"a" => 5.4}
|
936
|
+
|
937
|
+
schema = {
|
938
|
+
"type" => "object",
|
939
|
+
"properties" => { "a" => {"format" => "utc-millisec"}}
|
940
|
+
}
|
941
|
+
assert(!JSON::Validator.validate(schema,data1))
|
942
|
+
assert(JSON::Validator.validate(schema,data2))
|
943
|
+
assert(JSON::Validator.validate(schema,data3))
|
944
|
+
end
|
945
|
+
|
946
|
+
|
947
|
+
|
747
948
|
end
|
748
949
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json-schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 1
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 13
|
10
|
+
version: 0.1.13
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Kenny Hoxworth
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-16 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -45,9 +45,9 @@ files:
|
|
45
45
|
- lib/json-schema/uri/file.rb
|
46
46
|
- lib/json-schema/validator.rb
|
47
47
|
- lib/json-schema.rb
|
48
|
+
- README.textile
|
48
49
|
- test/test_files.rb
|
49
50
|
- test/test_jsonschema.rb
|
50
|
-
- README.textile
|
51
51
|
has_rdoc: true
|
52
52
|
homepage: http://github.com/hoxworth/json-schema/tree/master
|
53
53
|
licenses: []
|