strong_json 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4a009e9385e3e0d6dceed251b6951e533d8d9cae
4
- data.tar.gz: 26c8e89826ebe9b88b325c647c662d7b89013ddf
3
+ metadata.gz: 3740cb9d6923fabe6e9a14b191771d5dad7b5328
4
+ data.tar.gz: d719d0f91add645572ed65a1e89167f357037621
5
5
  SHA512:
6
- metadata.gz: d597908375f4112ee71f931f60a4c489ead56ae2c56c31fac8f185375aad38539ded3b846e8ce4207d32641f4641cccb244dc28ee8dcf20487f0dfb12108de5b
7
- data.tar.gz: 643ca3880954ba6fd3f5ac9654313d604744c50f79142af703364281d1aab35d406b3610fced4b1ef0a79fc5a711d95fb03887c22cb7f77294d687b9d6898c79
6
+ metadata.gz: ee65118af560df40ffa22a18d3b30fe376eddb3282a999027df49e8d59d71a899dd49ae3fcdbc90fa4a0c1a239b14eee5e014aae161297e6c2a516f50419dae1
7
+ data.tar.gz: dc7f9eae7a70b1d8967ead36cef902275908db9a7d2f83f204924a95d5c74cb6e4909c34061eb17f49782766d64989e2d6e811f211cc840a816528d5c288d02d
@@ -0,0 +1 @@
1
+ 2.3.1
data/README.md CHANGED
@@ -30,7 +30,8 @@ s = StrongJSON.new do
30
30
  let :order, object(customer: customer, items: array(item))
31
31
  end
32
32
 
33
- json = s.order.coerce(JSON.parse(input))
33
+ json = s.order.coerce(JSON.parse(input), symbolize_names: true)
34
+ s.order =~ JSON.parse(input, symbolize_names: true)
34
35
  ```
35
36
 
36
37
  If the input JSON data conforms to `order`'s structure, the `json` will be that value.
@@ -57,6 +58,11 @@ If an attribute has a value which does not match with given type, the `coerce` m
57
58
  * The value can be `nil` (or not contained in an object)
58
59
  * If an value exists, it must be of given `type`
59
60
 
61
+ ### enum(type1, type2, ...)
62
+
63
+ * The value can be one of the given types
64
+ * First successfully coerced value will return
65
+
60
66
  ### Base types
61
67
 
62
68
  * `number` The value must be an instance of `Numeric`
@@ -65,6 +71,11 @@ If an attribute has a value which does not match with given type, the `coerce` m
65
71
  * `numeric` The value must be an instance of `Numeric` or a string which represents a number
66
72
  * `any` Any value except `nil` is accepted
67
73
  * `ignored` Any value will be ignored
74
+ * `symbol` The value must be an instance of `String` or `Symbol`; returns the result ot `#to_sym`
75
+
76
+ ### Literals
77
+
78
+ * `literal(lit)` The value must `=== lit`
68
79
 
69
80
  ### Shortcuts
70
81
 
@@ -74,6 +85,8 @@ There are some alias for `optional(base)`, where base is base types, as the foll
74
85
  * `string?`
75
86
  * `boolean?`
76
87
  * `numeric?`
88
+ * `symbol?`
89
+ * `literal?(lit)`
77
90
 
78
91
  Shorthands for `optional(array(ty))` and `optional(object(fields))` are also defined as the following:
79
92
 
@@ -2,7 +2,18 @@ class StrongJSON
2
2
  module Type
3
3
  NONE = ::Object.new
4
4
 
5
+ module Match
6
+ def =~(value)
7
+ coerce(value)
8
+ true
9
+ rescue
10
+ false
11
+ end
12
+ end
13
+
5
14
  class Base
15
+ include Match
16
+
6
17
  attr_reader :type
7
18
 
8
19
  def initialize(type)
@@ -23,6 +34,8 @@ class StrongJSON
23
34
  value == true || value == false
24
35
  when :numeric
25
36
  value.is_a?(Numeric) || value.is_a?(String) && /\A[\-\+]?[\d.]+\Z/ =~ value
37
+ when :symbol
38
+ value.is_a?(String) || value.is_a?(Symbol)
26
39
  else
27
40
  false
28
41
  end
@@ -31,7 +44,15 @@ class StrongJSON
31
44
  def coerce(value, path: [])
32
45
  raise Error.new(value: value, type: self, path: path) unless test(value)
33
46
  raise IllegalTypeError.new(type: self) if path == [] && @type == :ignored
34
- @type != :ignored ? value : NONE
47
+
48
+ case type
49
+ when :ignored
50
+ NONE
51
+ when :symbol
52
+ value.to_sym
53
+ else
54
+ value
55
+ end
35
56
  end
36
57
 
37
58
  def to_s
@@ -40,6 +61,8 @@ class StrongJSON
40
61
  end
41
62
 
42
63
  class Optional
64
+ include Match
65
+
43
66
  def initialize(type)
44
67
  @type = type
45
68
  end
@@ -57,7 +80,28 @@ class StrongJSON
57
80
  end
58
81
  end
59
82
 
83
+ class Literal
84
+ include Match
85
+
86
+ attr_reader :value
87
+
88
+ def initialize(value)
89
+ @value = value
90
+ end
91
+
92
+ def to_s
93
+ "literal(#{@value})"
94
+ end
95
+
96
+ def coerce(value, path: [])
97
+ raise Error.new(path: path, type: self, value: value) unless self.value == value
98
+ value
99
+ end
100
+ end
101
+
60
102
  class Array
103
+ include Match
104
+
61
105
  def initialize(type)
62
106
  @type = type
63
107
  end
@@ -78,6 +122,8 @@ class StrongJSON
78
122
  end
79
123
 
80
124
  class Object
125
+ include Match
126
+
81
127
  def initialize(fields)
82
128
  @fields = fields
83
129
  end
@@ -140,6 +186,30 @@ class StrongJSON
140
186
  end
141
187
  end
142
188
 
189
+ class Enum
190
+ include Match
191
+
192
+ attr_reader :types
193
+
194
+ def initialize(types)
195
+ @types = types
196
+ end
197
+
198
+ def to_s
199
+ "enum(#{types.map(&:to_s).join(", ")})"
200
+ end
201
+
202
+ def coerce(value, path: [])
203
+ type = types.find {|ty| ty =~ value }
204
+
205
+ if type
206
+ type.coerce(value, path: path)
207
+ else
208
+ raise Error.new(path: path, type: self, value: value)
209
+ end
210
+ end
211
+ end
212
+
143
213
  class UnexpectedFieldError < StandardError
144
214
  attr_reader :path, :value
145
215
 
@@ -36,6 +36,18 @@ class StrongJSON
36
36
  StrongJSON::Type::Base.new(:prohibited)
37
37
  end
38
38
 
39
+ def symbol
40
+ StrongJSON::Type::Base.new(:symbol)
41
+ end
42
+
43
+ def literal(value)
44
+ StrongJSON::Type::Literal.new(value)
45
+ end
46
+
47
+ def enum(*types)
48
+ StrongJSON::Type::Enum.new(types)
49
+ end
50
+
39
51
  def string?
40
52
  optional(string)
41
53
  end
@@ -52,6 +64,10 @@ class StrongJSON
52
64
  optional(boolean)
53
65
  end
54
66
 
67
+ def symbol?
68
+ optional(symbol)
69
+ end
70
+
55
71
  def ignored
56
72
  StrongJSON::Type::Base.new(:ignored)
57
73
  end
@@ -63,5 +79,9 @@ class StrongJSON
63
79
  def object?(fields)
64
80
  optional(object(fields))
65
81
  end
82
+
83
+ def literal?(value)
84
+ optional(literal(value))
85
+ end
66
86
  end
67
87
  end
@@ -1,3 +1,3 @@
1
1
  class StrongJSON
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -35,4 +35,16 @@ describe StrongJSON::Type::Array, "#coerce" do
35
35
 
36
36
  expect { type.coerce(nil) }.to raise_error(StrongJSON::Type::Error)
37
37
  end
38
+
39
+ describe "=~" do
40
+ it "returns true for array" do
41
+ type = StrongJSON::Type::Array.new(StrongJSON::Type::Base.new(:number))
42
+ expect(type =~ []).to be_truthy
43
+ end
44
+
45
+ it "returns false for number" do
46
+ type = StrongJSON::Type::Array.new(StrongJSON::Type::Base.new(:number))
47
+ expect(type =~ 3.0).to be_falsey
48
+ end
49
+ end
38
50
  end
@@ -24,6 +24,20 @@ describe StrongJSON::Type::Base do
24
24
  it "rejects string" do
25
25
  expect(type.test("string")).to be_falsey
26
26
  end
27
+
28
+ describe "=~" do
29
+ it "returns true for integer" do
30
+ expect(type =~ 3).to be_truthy
31
+ end
32
+
33
+ it "returns true for float" do
34
+ expect(type =~ 3.0).to be_truthy
35
+ end
36
+
37
+ it "returns false for string" do
38
+ expect(type =~ "foo").to be_falsey
39
+ end
40
+ end
27
41
  end
28
42
 
29
43
  context ":string" do
@@ -32,6 +46,16 @@ describe StrongJSON::Type::Base do
32
46
  it "accepts string" do
33
47
  expect(type.test("string")).to be_truthy
34
48
  end
49
+
50
+ describe "=~" do
51
+ it "returns true for string" do
52
+ expect(type =~ "3").to be_truthy
53
+ end
54
+
55
+ it "returns false for number" do
56
+ expect(type =~ 3.0).to be_falsey
57
+ end
58
+ end
35
59
  end
36
60
 
37
61
  context ":any" do
@@ -44,6 +68,12 @@ describe StrongJSON::Type::Base do
44
68
  it "accepts number" do
45
69
  expect(type.test(2.71828)).to be_truthy
46
70
  end
71
+
72
+ describe "=~" do
73
+ it "returns true for string" do
74
+ expect(type =~ "3").to be_truthy
75
+ end
76
+ end
47
77
  end
48
78
 
49
79
  context ":boolean" do
@@ -60,6 +90,16 @@ describe StrongJSON::Type::Base do
60
90
  it "rejects nil" do
61
91
  expect(type.test(nil)).to be_falsey
62
92
  end
93
+
94
+ describe "=~" do
95
+ it "returns true for boolean" do
96
+ expect(type =~ true).to be_truthy
97
+ end
98
+
99
+ it "returns false for nil" do
100
+ expect(type =~ nil).to be_falsey
101
+ end
102
+ end
63
103
  end
64
104
 
65
105
  context ":numeric" do
@@ -92,6 +132,50 @@ describe StrongJSON::Type::Base do
92
132
  it "rejects boolean" do
93
133
  expect(type.test(true)).to be_falsey
94
134
  end
135
+
136
+ describe "=~" do
137
+ it "returns true for numeric string" do
138
+ expect(type =~ "3").to be_truthy
139
+ end
140
+
141
+ it "returns false for boolean" do
142
+ expect(type =~ false).to be_falsey
143
+ end
144
+ end
145
+ end
146
+
147
+ context ":symbol" do
148
+ let (:type) { StrongJSON::Type::Base.new(:symbol) }
149
+
150
+ describe "#test" do
151
+ it "returns true for string" do
152
+ expect(type.test("foo")).to be_truthy
153
+ end
154
+
155
+ it "returns true for symbol" do
156
+ expect(type.test(:foo)).to be_truthy
157
+ end
158
+
159
+ it "returns false for boolean" do
160
+ expect(type.test(false)).to be_falsey
161
+ end
162
+ end
163
+
164
+ describe "#=~" do
165
+ it "returns true for string" do
166
+ expect(type =~ 'foo').to be_truthy
167
+ end
168
+
169
+ it "returns false for number" do
170
+ expect(type =~ 3).to be_falsey
171
+ end
172
+ end
173
+
174
+ describe "#coerce" do
175
+ it "returns symbol" do
176
+ expect(type.coerce("foo")).to eq(:foo)
177
+ end
178
+ end
95
179
  end
96
180
  end
97
181
  end
@@ -0,0 +1,45 @@
1
+ require "strong_json"
2
+
3
+ describe StrongJSON::Type::Enum do
4
+ describe "=~" do
5
+ let (:type) {
6
+ StrongJSON::Type::Enum.new([StrongJSON::Type::Literal.new(3),
7
+ StrongJSON::Type::Literal.new(4)])
8
+ }
9
+
10
+ it "returns true for 3" do
11
+ expect(type =~ 3).to be_truthy
12
+ end
13
+
14
+ it "returns true for 4" do
15
+ expect(type =~ 4).to be_truthy
16
+ end
17
+
18
+ it "returns false for 5" do
19
+ expect(type =~ 5).to be_falsey
20
+ end
21
+ end
22
+
23
+ describe "#coerce" do
24
+ let (:type) {
25
+ StrongJSON::Type::Enum.new([
26
+ StrongJSON::Type::Object.new(id: StrongJSON::Type::Literal.new("id1"),
27
+ value: StrongJSON::Type::Base.new(:string)),
28
+ StrongJSON::Type::Object.new(id: StrongJSON::Type::Base.new(:string),
29
+ value: StrongJSON::Type::Base.new(:symbol))
30
+ ])
31
+ }
32
+
33
+ it "returns object with string value" do
34
+ expect(type.coerce({id: "id1", value: "foo" })).to eq({ id: "id1", value: "foo" })
35
+ end
36
+
37
+ it "returns object with symbol value" do
38
+ expect(type.coerce({id: "id2", value: "foo" })).to eq({ id: "id2", value: :foo })
39
+ end
40
+
41
+ it "raises error" do
42
+ expect { type.coerce(3.14) }.to raise_error(StrongJSON::Type::Error)
43
+ end
44
+ end
45
+ end
@@ -4,9 +4,9 @@ describe "StrongJSON.new" do
4
4
  it "tests the structure of a JSON object" do
5
5
  s = StrongJSON.new do
6
6
  let :item, object(name: string, count: numeric, price: numeric, comment: ignored)
7
- let :checkout, object(items: array(item), change: optional(number))
7
+ let :checkout, object(items: array(item), change: optional(number), type: enum(literal(1), symbol))
8
8
  end
9
9
 
10
- expect(s.checkout.coerce(items: [ { name: "test", count: 1, price: "2.33", comment: "dummy" } ])).to eq(items: [ { name: "test", count: 1, price: "2.33" }])
10
+ expect(s.checkout.coerce(items: [ { name: "test", count: 1, price: "2.33", comment: "dummy" }], type: 1)).to eq(items: [ { name: "test", count: 1, price: "2.33" }], type: 1)
11
11
  end
12
12
  end
@@ -0,0 +1,15 @@
1
+ require "strong_json"
2
+
3
+ describe StrongJSON::Type::Literal do
4
+ describe "=~" do
5
+ let (:type) { StrongJSON::Type::Literal.new(3) }
6
+
7
+ it "returns true if == holds" do
8
+ expect(type =~ 3).to be_truthy
9
+ end
10
+
11
+ it "returns false if == doesn't hold" do
12
+ expect(type =~ 4).to be_falsey
13
+ end
14
+ end
15
+ end
@@ -75,4 +75,16 @@ describe StrongJSON::Type::Object do
75
75
  expect(ty2.coerce(b: "test")).to eq({ b: "test" })
76
76
  end
77
77
  end
78
+
79
+ describe "=~" do
80
+ let (:type) { StrongJSON::Type::Object.new(a: StrongJSON::Type::Base.new(:numeric), b: StrongJSON::Type::Base.new(:string)) }
81
+
82
+ it "returns true for valid object" do
83
+ expect(type =~ { a: 3, b: "foo" }).to be_truthy
84
+ end
85
+
86
+ it "returns false for invalid number" do
87
+ expect(type =~ {}).to be_falsey
88
+ end
89
+ end
78
90
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strong_json
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soutaro Matsumoto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-27 00:00:00.000000000 Z
11
+ date: 2016-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -60,6 +60,7 @@ extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
62
  - ".gitignore"
63
+ - ".ruby-version"
63
64
  - ".travis.yml"
64
65
  - Gemfile
65
66
  - LICENSE.txt
@@ -71,8 +72,10 @@ files:
71
72
  - lib/strong_json/version.rb
72
73
  - spec/array_spec.rb
73
74
  - spec/basetype_spec.rb
75
+ - spec/enum_spec.rb
74
76
  - spec/error_spec.rb
75
77
  - spec/json_spec.rb
78
+ - spec/literal_spec.rb
76
79
  - spec/object_spec.rb
77
80
  - spec/optional_spec.rb
78
81
  - strong_json.gemspec
@@ -96,14 +99,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
99
  version: '0'
97
100
  requirements: []
98
101
  rubyforge_project:
99
- rubygems_version: 2.2.2
102
+ rubygems_version: 2.5.1
100
103
  signing_key:
101
104
  specification_version: 4
102
105
  summary: Type check JSON objects
103
106
  test_files:
104
107
  - spec/array_spec.rb
105
108
  - spec/basetype_spec.rb
109
+ - spec/enum_spec.rb
106
110
  - spec/error_spec.rb
107
111
  - spec/json_spec.rb
112
+ - spec/literal_spec.rb
108
113
  - spec/object_spec.rb
109
114
  - spec/optional_spec.rb