jschematic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.rspec +1 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +40 -0
  5. data/LICENSE +20 -0
  6. data/README.md +48 -0
  7. data/Rakefile +10 -0
  8. data/cucumber.yml +1 -0
  9. data/features/default.feature +72 -0
  10. data/features/dependencies.feature +73 -0
  11. data/features/enum.feature +26 -0
  12. data/features/format.feature +40 -0
  13. data/features/id.feature +75 -0
  14. data/features/items.feature +90 -0
  15. data/features/min_length_max_length.feature +20 -0
  16. data/features/minimum_maximum.feature +29 -0
  17. data/features/pattern.feature +6 -0
  18. data/features/pattern_properties.feature +18 -0
  19. data/features/properties.feature +124 -0
  20. data/features/required.feature +42 -0
  21. data/features/step_definitions/jschematic_steps.rb +62 -0
  22. data/features/support/env.rb +6 -0
  23. data/features/type.feature +76 -0
  24. data/jschematic.gemspec +22 -0
  25. data/lib/jschematic/attributes/additional_items.rb +35 -0
  26. data/lib/jschematic/attributes/additional_properties.rb +28 -0
  27. data/lib/jschematic/attributes/dependencies.rb +26 -0
  28. data/lib/jschematic/attributes/enum.rb +18 -0
  29. data/lib/jschematic/attributes/exclusive_maximum.rb +21 -0
  30. data/lib/jschematic/attributes/exclusive_minimum.rb +21 -0
  31. data/lib/jschematic/attributes/format.rb +56 -0
  32. data/lib/jschematic/attributes/items.rb +24 -0
  33. data/lib/jschematic/attributes/max_items.rb +18 -0
  34. data/lib/jschematic/attributes/max_length.rb +18 -0
  35. data/lib/jschematic/attributes/maximum.rb +22 -0
  36. data/lib/jschematic/attributes/min_items.rb +18 -0
  37. data/lib/jschematic/attributes/min_length.rb +18 -0
  38. data/lib/jschematic/attributes/minimum.rb +22 -0
  39. data/lib/jschematic/attributes/pattern.rb +18 -0
  40. data/lib/jschematic/attributes/pattern_properties.rb +23 -0
  41. data/lib/jschematic/attributes/properties.rb +38 -0
  42. data/lib/jschematic/attributes/required.rb +28 -0
  43. data/lib/jschematic/attributes/type.rb +61 -0
  44. data/lib/jschematic/attributes/unique_items.rb +18 -0
  45. data/lib/jschematic/attributes.rb +28 -0
  46. data/lib/jschematic/element.rb +25 -0
  47. data/lib/jschematic/errors.rb +34 -0
  48. data/lib/jschematic/schema.rb +77 -0
  49. data/lib/jschematic/validation_error.rb +13 -0
  50. data/lib/jschematic.rb +13 -0
  51. data/spec/jschematic/attributes/enum_spec.rb +14 -0
  52. data/spec/jschematic/attributes/max_items_spec.rb +15 -0
  53. data/spec/jschematic/attributes/min_items_spec.rb +15 -0
  54. data/spec/jschematic/attributes/minimum_maximum_spec.rb +33 -0
  55. data/spec/jschematic/attributes/required_spec.rb +29 -0
  56. data/spec/jschematic/attributes/type_spec.rb +63 -0
  57. data/spec/jschematic/attributes_spec.rb +12 -0
  58. data/spec/jschematic/errors_spec.rb +43 -0
  59. data/spec/jschematic/schema_spec.rb +58 -0
  60. data/spec/spec_helper.rb +16 -0
  61. metadata +199 -0
@@ -0,0 +1,124 @@
1
+ Feature: Core Schema: properties & additionalProperties
2
+
3
+ Scenario: instance property values must conform to schema property definitions
4
+ When the schema is:
5
+ """
6
+ {
7
+ "properties": {
8
+ "name": { "type": "string" }
9
+ }
10
+ }
11
+ """
12
+ Then '{ "name": "Felizberto" }' is valid JSON
13
+ And '{ "color": "red" }' is valid JSON
14
+ But '{ "name": 12345" }' is not valid JSON
15
+
16
+ Scenario: With extra attribute
17
+ When the schema is:
18
+ """
19
+ {
20
+ "properties": {
21
+ "age": { "maximum": 25 }
22
+ }
23
+ }
24
+ """
25
+ Then '{ "age": 25, "color": "red" }' is valid JSON
26
+ But '{ "age": 26, "color": "red" }' is not valid JSON
27
+
28
+ Scenario: deeply nested
29
+ When the schema is:
30
+ """
31
+ {
32
+ "properties": {
33
+ "attributes": {
34
+ "properties": {
35
+ "attribute": {
36
+ "type": "string"
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ """
43
+ Then this is valid JSON:
44
+ """
45
+ {
46
+ "attributes": {
47
+ "attribute": "value"
48
+ }
49
+ }
50
+ """
51
+ But this is not valid JSON:
52
+ """
53
+ {
54
+ "attributes": {
55
+ "attribute": 2112
56
+ }
57
+ }
58
+ """
59
+
60
+ Scenario: additionalProperties is false
61
+ When the schema is:
62
+ """
63
+ {
64
+ "properties": {
65
+ "name": { "type": "string" },
66
+ "age": { "type": "integer" }
67
+ },
68
+
69
+ "additionalProperties": false
70
+ }
71
+ """
72
+ Then this is valid JSON:
73
+ """
74
+ {
75
+ "name": "Felizberto Albi",
76
+ "age": 24
77
+ }
78
+ """
79
+ But this is not valid JSON:
80
+ """
81
+ {
82
+ "name": "Felizberto Albi",
83
+ "age": 24,
84
+ "color": "red"
85
+ }
86
+ """
87
+
88
+ Scenario: additionalProperties defined by a schema
89
+ When the schema is:
90
+ """
91
+ {
92
+ "properties": {
93
+ "name": { "type": "string" },
94
+ "age": { "type": "integer" }
95
+ },
96
+
97
+ "additionalProperties": {
98
+ "type": "string"
99
+ }
100
+ }
101
+ """
102
+ Then this is valid JSON:
103
+ """
104
+ {
105
+ "name": "Felizberto Albi",
106
+ "age": 24,
107
+ "color": "red"
108
+ }
109
+ """
110
+ But this is not valid JSON:
111
+ """
112
+ {
113
+ "name": "Felizberto Albi",
114
+ "age": 24,
115
+ "number": 2112
116
+ }
117
+ """
118
+
119
+ Scenario: when true
120
+ TODO: duh
121
+ Scenario: neither boolean nor schema
122
+ TODO: should fail loudly: move into spec
123
+ Scenario: empty instance
124
+ TODO: find out what the behavior here should be
@@ -0,0 +1,42 @@
1
+ Feature: Core schema: required
2
+ Scenario: default false
3
+ When the schema is:
4
+ """
5
+ {
6
+ "properties": {
7
+ "color": {
8
+ "type": "string"
9
+ }
10
+ }
11
+ }
12
+ """
13
+ Then '{}' is valid JSON
14
+
15
+ When the schema is:
16
+ """
17
+ {
18
+ "properties": {
19
+ "color": {
20
+ "type": "string",
21
+ "required": false
22
+ }
23
+ }
24
+ }
25
+ """
26
+ Then '{}' is valid JSON
27
+
28
+ Scenario: true
29
+ When the schema is:
30
+ """
31
+ {
32
+ "properties": {
33
+ "color": {
34
+ "type": "string",
35
+ "required": true
36
+ }
37
+ }
38
+ }
39
+ """
40
+ Then '{ "color": "red" }' is valid JSON
41
+ But '{}' is not valid JSON
42
+ And '{ "age": 24 }' is not valid JSON
@@ -0,0 +1,62 @@
1
+ When /^the schema is '(.+)'$/ do |schema|
2
+ @schema = parse(schema)
3
+ end
4
+
5
+ When "the schema is:" do |schema|
6
+ @schema = parse(schema)
7
+ end
8
+
9
+ Then /^'(.+)' is valid JSON$/ do |json|
10
+ assert_valid(parse(json), @schema)
11
+ end
12
+
13
+ Then /^'(.+)' is not valid JSON$/ do |json|
14
+ assert_invalid(parse(json), @schema)
15
+ end
16
+
17
+ Then "this is valid JSON:" do |json|
18
+ assert_valid(parse(json), @schema)
19
+ end
20
+
21
+ Then "this is not valid JSON:" do |json|
22
+ assert_invalid(parse(json), @schema)
23
+ end
24
+
25
+ Then "these are valid JSON:" do |instances|
26
+ instances.raw.each do |row|
27
+ assert_valid(parse(row[0]), @schema)
28
+ end
29
+ end
30
+
31
+ Then "these are not valid JSON:" do |instances|
32
+ instances.raw.each do |row|
33
+ assert_invalid(parse(row[0]), @schema)
34
+ end
35
+ end
36
+
37
+ Then /^the id of "(.+)" is "(.+)"$/ do |title, uri|
38
+ schema = build_schema.find{ |el| el.title == title }
39
+ [schema.title, schema.id.to_s].should == [title, uri]
40
+ end
41
+
42
+ module JschematicWorld
43
+ def parse(json)
44
+ Yajl::Parser.parse(json)
45
+ rescue Yajl::ParseError => e
46
+ raise "Parsing '#{json}' failed with #{e.to_s}"
47
+ end
48
+
49
+ def assert_valid(json, raw_schema)
50
+ Jschematic.validate(json, raw_schema).should be_true
51
+ end
52
+
53
+ def assert_invalid(json, raw_schema)
54
+ Jschematic.validate(json, raw_schema).should be_false
55
+ end
56
+
57
+ def build_schema
58
+ @_schema ||= Jschematic::Schema.new(@schema)
59
+ end
60
+ end
61
+
62
+ World(JschematicWorld)
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../../lib"))
2
+
3
+ require 'rspec/expectations'
4
+ require 'yajl'
5
+
6
+ require 'jschematic'
@@ -0,0 +1,76 @@
1
+ Feature: Core Schema: type
2
+
3
+ Scenario: string
4
+ When the schema is '{ "type": "string" }'
5
+ Then '"hello, world"' is valid JSON
6
+
7
+ Scenario: number
8
+ When the schema is '{ "type": "number" }'
9
+ Then '3.14159' is valid JSON
10
+ And '-123.41235' is valid JSON
11
+ And '2112' is valid JSON
12
+ But '"1234"' is not valid JSON
13
+
14
+ Scenario: integer
15
+ When the schema is '{ "type": "integer" }'
16
+ Then '2112' is valid JSON
17
+ And '-12' is valid JSON
18
+ But '3.14159' is not valid JSON
19
+
20
+ Scenario: boolean
21
+ When the schema is '{ "type": "boolean" }'
22
+ Then 'true' is valid JSON
23
+ And 'false' is valid JSON
24
+
25
+ Scenario: object
26
+ When the schema is '{ "type": "object" }'
27
+ Then this is valid JSON:
28
+ """
29
+ { "person": "felizberto" }
30
+ """
31
+
32
+ Scenario: array
33
+ When the schema is '{ "type": "array" }'
34
+ Then this is valid JSON:
35
+ """
36
+ ["foo", "bar", "baz"]
37
+ """
38
+
39
+ Scenario: null
40
+ When the schema is '{ "type": "null" }'
41
+ Then 'null' is valid JSON
42
+
43
+ Scenario: any
44
+ When the schema is '{ "type": "any" }'
45
+ Then these are valid JSON:
46
+ | "Felizberto" |
47
+ | 3.1415 |
48
+ | 2112 |
49
+ | true |
50
+ | { "age": 24 } |
51
+ | ["foo", "bar"] |
52
+ | null |
53
+
54
+ Scenario: union
55
+ When the schema is '{ "type": ["string", "number"] }'
56
+ Then '"Felizberto"' is valid JSON
57
+ And '2112' is valid JSON
58
+ But 'true' is not valid JSON
59
+
60
+ Scenario: union allowing null values
61
+ When the schema is '{ "type": ["string", "null"] }'
62
+ Then 'null' is valid JSON
63
+
64
+ Scenario: union with schema element
65
+ When the schema is:
66
+ """
67
+ {
68
+ "type": [
69
+ "string",
70
+ { "type": "integer", "maximum": 2112 }
71
+ ]
72
+ }
73
+ """
74
+ Then '"Felizberto"' is valid JSON
75
+ And '2112' is valid JSON
76
+ But '2113' is not valid JSON
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'jschematic'
3
+ s.version = '0.0.1'
4
+ s.authors = ["Mike Sassak"]
5
+ s.description = "JSON Schema v3 Validator"
6
+ s.summary = "jschematic #{s.version}"
7
+ s.email = "msassak@gmail.com"
8
+ s.homepage = "https://github.com/msassak/jschematic"
9
+
10
+ s.add_dependency 'addressable'
11
+
12
+ s.add_development_dependency 'cucumber'
13
+ s.add_development_dependency 'rspec'
14
+ s.add_development_dependency 'yajl-ruby'
15
+
16
+ s.rubygems_version = "1.3.7"
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
19
+ s.extra_rdoc_files = ["LICENSE", "README.md"]
20
+ s.rdoc_options = ["--charset=UTF-8"]
21
+ s.require_path = "lib"
22
+ end
@@ -0,0 +1,35 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class AdditionalItems
6
+ include Jschematic::Element
7
+
8
+ def initialize(allowed, &block)
9
+ case items = block.call("items")
10
+ when Array
11
+ @allowed = allowed
12
+ @tuple_types_count = items.length
13
+ else
14
+ # TODO spec: additionalItems applies only with tuple-typing, so
15
+ # even if it is set to false we allow anything... should probably raise
16
+ # but the I-D is silent on the proper behavior in that case.
17
+ @allowed = true
18
+ end
19
+ end
20
+
21
+ def accepts?(instance)
22
+ return true if TrueClass === @allowed
23
+
24
+ case @allowed
25
+ when FalseClass
26
+ (instance.length == @tuple_types_count) || fail_validation!("#{@tuple_types_count} items", "#{instance.length} items")
27
+ when Hash
28
+ schema = Schema.new(@allowed)
29
+ additional = instance[@tuple_types_count..-1]
30
+ additional.all?{ |item| schema.accepts?(item) }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class AdditionalProperties
6
+ include Jschematic::Element
7
+
8
+ # TODO: rename value to allowed
9
+ def initialize(value, &block)
10
+ @value = value
11
+ @properties = block.call("properties").keys
12
+ end
13
+
14
+ def accepts?(instance)
15
+ case @value
16
+ when FalseClass
17
+ (@properties == instance.keys) || fail_validation!(@properties, instance.keys)
18
+ when Hash
19
+ schema = Schema.new(@value)
20
+ additional = instance.select{ |attribute, value| !@properties.include?(attribute) }
21
+ additional.all? do |attribute, value|
22
+ schema.accepts?(value)
23
+ end || fail_validation!(@value, instance)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class Dependencies
6
+ include Jschematic::Element
7
+
8
+ def initialize(dependencies)
9
+ @dependencies = dependencies
10
+ end
11
+
12
+ def accepts?(instance)
13
+ instance.keys.all? do |property|
14
+ case deps = @dependencies[property]
15
+ when String, Array
16
+ [deps].flatten.all?{ |req| instance.keys.include?(req) }
17
+ when Hash
18
+ Schema.new(deps).accepts?(instance)
19
+ else
20
+ true
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class Enum
6
+ include Jschematic::Element
7
+
8
+ def initialize(enum)
9
+ raise "Enum requires an Array of possible values" unless Array === enum
10
+ @enum = enum
11
+ end
12
+
13
+ def accepts?(instance)
14
+ @enum.any?{ |e| e == instance } || fail_validation!("one of #{@enum}", instance)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class ExclusiveMaximum
6
+ include Jschematic::Element
7
+
8
+ def initialize(enabled, &block)
9
+ @enabled = enabled
10
+ @maximum = block.call("maximum") if block_given?
11
+ raise "'exclusiveMaximum' depends on 'maximum'" unless @maximum
12
+ end
13
+
14
+ def accepts?(actual)
15
+ if @enabled
16
+ (actual < @maximum) || fail_validation!("< #{@maximum}", actual)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class ExclusiveMinimum
6
+ include Jschematic::Element
7
+
8
+ def initialize(enabled, &block)
9
+ @enabled = enabled
10
+ @minimum = block.call("minimum") if block_given?
11
+ raise "'exclusiveMinimum' depends on 'minimum'" unless @minimum
12
+ end
13
+
14
+ def accepts?(actual)
15
+ if @enabled
16
+ (actual > @minimum) || fail_validation!("> #{@minimum}", actual)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ require 'ipaddr'
2
+ require 'addressable/uri'
3
+
4
+ module Jschematic
5
+ module Attributes
6
+ module Format
7
+ def self.new(format)
8
+ case format
9
+ when "uri"
10
+ Uri.new
11
+ when "ip-address", "ipv6"
12
+ Ip.new(format)
13
+ else
14
+ NullFormat.new
15
+ end
16
+ end
17
+
18
+ class Uri
19
+ include Jschematic::Element
20
+
21
+ def accepts?(uri)
22
+ Addressable::URI.parse(uri)
23
+ rescue Addressable::URI::InvalidURIError
24
+ false
25
+ end
26
+ end
27
+
28
+ class Ip
29
+ include Jschematic::Element
30
+
31
+ def initialize(version)
32
+ @method = case version
33
+ when "ip-address"
34
+ :ipv4?
35
+ when "ipv6"
36
+ :ipv6?
37
+ end
38
+ end
39
+
40
+ def accepts?(addr)
41
+ IPAddr.new(addr).send(@method)
42
+ rescue ArgumentError
43
+ false
44
+ end
45
+ end
46
+
47
+ class NullFormat
48
+ include Jschematic::Element
49
+
50
+ def accepts?(instance)
51
+ true
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class Items
6
+ include Jschematic::Element
7
+
8
+ def initialize(schema)
9
+ @schema = schema
10
+ end
11
+
12
+ def accepts?(instance)
13
+ case @schema
14
+ when Hash
15
+ instance.all?{ |item| Schema.new(@schema).accepts?(item) }
16
+ when Array
17
+ @schema.zip(instance).all?{ |schema, item| Schema.new(schema).accepts?(item) }
18
+ # TODO: There is a bug here similar to the one in the Union type;
19
+ # the error reported does not mention the failure is w/i an item attribute
20
+ end || fail_validation!(@schema, instance)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class MaxItems
6
+ include Jschematic::Element
7
+
8
+ def initialize(max_items)
9
+ @max_items = max_items
10
+ end
11
+
12
+ def accepts?(instance)
13
+ return true unless Array === instance
14
+ (instance.length <= @max_items) || fail_validation!("at most #{@max_items}", instance.length)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class MinLength
6
+ include Jschematic::Element
7
+
8
+ def initialize(length)
9
+ @length = length
10
+ end
11
+
12
+ def accepts?(instance)
13
+ return true unless String === instance
14
+ (instance.length >= @length) || fail_validation!("minimum length of #{@length}", instance.length)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class Maximum
6
+ include Jschematic::Element
7
+
8
+ attr_reader :maximum
9
+
10
+ def initialize(maximum)
11
+ @maximum = maximum
12
+ end
13
+
14
+ def accepts?(number)
15
+ return true unless maximum
16
+ return true unless (number.kind_of?(Integer) || number.kind_of?(Float))
17
+
18
+ (number <= maximum) || fail_validation!("<= #{@maximum}", number)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class MinItems
6
+ include Jschematic::Element
7
+
8
+ def initialize(min_items)
9
+ @min_items = min_items
10
+ end
11
+
12
+ def accepts?(instance)
13
+ return true unless Array === instance
14
+ (instance.length >= @min_items) || fail_validation!("at least #{@min_items}", instance.length)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require 'jschematic/element'
2
+
3
+ module Jschematic
4
+ module Attributes
5
+ class MaxLength
6
+ include Jschematic::Element
7
+
8
+ def initialize(length)
9
+ @length = length
10
+ end
11
+
12
+ def accepts?(instance)
13
+ return true unless String === instance
14
+ (instance.length <= @length) || fail_validation!("maximum length of #{@length}", instance.length)
15
+ end
16
+ end
17
+ end
18
+ end