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 +4 -4
- data/.ruby-version +1 -0
- data/README.md +14 -1
- data/lib/strong_json/type.rb +71 -1
- data/lib/strong_json/types.rb +20 -0
- data/lib/strong_json/version.rb +1 -1
- data/spec/array_spec.rb +12 -0
- data/spec/basetype_spec.rb +84 -0
- data/spec/enum_spec.rb +45 -0
- data/spec/json_spec.rb +2 -2
- data/spec/literal_spec.rb +15 -0
- data/spec/object_spec.rb +12 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3740cb9d6923fabe6e9a14b191771d5dad7b5328
|
4
|
+
data.tar.gz: d719d0f91add645572ed65a1e89167f357037621
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee65118af560df40ffa22a18d3b30fe376eddb3282a999027df49e8d59d71a899dd49ae3fcdbc90fa4a0c1a239b14eee5e014aae161297e6c2a516f50419dae1
|
7
|
+
data.tar.gz: dc7f9eae7a70b1d8967ead36cef902275908db9a7d2f83f204924a95d5c74cb6e4909c34061eb17f49782766d64989e2d6e811f211cc840a816528d5c288d02d
|
data/.ruby-version
ADDED
@@ -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
|
|
data/lib/strong_json/type.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/strong_json/types.rb
CHANGED
@@ -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
|
data/lib/strong_json/version.rb
CHANGED
data/spec/array_spec.rb
CHANGED
@@ -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
|
data/spec/basetype_spec.rb
CHANGED
@@ -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
|
data/spec/enum_spec.rb
ADDED
@@ -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
|
data/spec/json_spec.rb
CHANGED
@@ -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" }
|
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
|
data/spec/object_spec.rb
CHANGED
@@ -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.
|
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:
|
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.
|
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
|