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