strong_json 0.1.2 → 0.2.0

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