strong_json 0.0.4 → 0.1.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/README.md +5 -6
- data/lib/strong_json/type.rb +55 -8
- data/lib/strong_json/types.rb +4 -0
- data/lib/strong_json/version.rb +1 -1
- data/spec/basetype_spec.rb +4 -4
- data/spec/json_spec.rb +1 -1
- data/spec/object_spec.rb +10 -13
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec2fc74653b07371d8619d7d830ede16d86e277f
|
4
|
+
data.tar.gz: 07b28f98c1d24ec5136bf77b86e66cb77fd2e103
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5a0c16aa825b4d59c7cccb177ad349117dd3a441546a88b7e30f851fed3cb30ed67d46fae6afd45def233082441e73e8495a7ed57f13747884131e3097313c9
|
7
|
+
data.tar.gz: debfe3a79493d228f22970b022aae0f2d73acc9800f0be93356a18d6cfa85f4de40302d89b7c7ce1e83844a326d589856233e4d88bcc0715775d51a15b3f64dd
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
This library allows you to test the structure of JSON objects.
|
4
4
|
|
5
5
|
This is similar to Strong Parameters, which is introduced by Rails 4, but expected to work with more complex structures.
|
6
|
-
It may help you to understand what this is as: Strong
|
6
|
+
It may help you to understand what this is as: Strong Parameters is for simple structures, like HTML forms, and StrongJSON is for complex structures, like JSON objects posted to API.
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
|
@@ -33,12 +33,11 @@ end
|
|
33
33
|
json = s.order.coerce(JSON.parse(input))
|
34
34
|
```
|
35
35
|
|
36
|
-
If the input JSON data
|
36
|
+
If the input JSON data conforms to `order`'s structure, the `json` will be that value.
|
37
37
|
|
38
|
-
If the input JSON contains attributes which is not white-listed in the definition,
|
38
|
+
If the input JSON contains attributes which is not white-listed in the definition, it will raise an exception.
|
39
39
|
|
40
40
|
If an attribute has a value which does not match with given type, the `coerce` method call will raise an exception `StrongJSON::Type::Error`.
|
41
|
-
If the input JSON contains `prohibited` attributes, `id` of `item` in the example, it also will result in an exception.
|
42
41
|
|
43
42
|
## Catalogue of Types
|
44
43
|
|
@@ -46,7 +45,7 @@ If the input JSON contains `prohibited` attributes, `id` of `item` in the exampl
|
|
46
45
|
|
47
46
|
* The value must be an object
|
48
47
|
* Fields, `f1`, `f2`, and ..., must be present and its values must be of `type1`, `type2`, ..., respectively
|
49
|
-
*
|
48
|
+
* Objects with other fields will be rejected
|
50
49
|
|
51
50
|
### array(type)
|
52
51
|
|
@@ -65,7 +64,7 @@ If the input JSON contains `prohibited` attributes, `id` of `item` in the exampl
|
|
65
64
|
* `boolean` The value must be `true` or `false`
|
66
65
|
* `numeric` The value must be an instance of `Numeric` or a string which represents a number
|
67
66
|
* `any` Any value except `nil` is accepted
|
68
|
-
* `
|
67
|
+
* `ignored` Any value will be ignored
|
69
68
|
|
70
69
|
### Shortcuts
|
71
70
|
|
data/lib/strong_json/type.rb
CHANGED
@@ -3,14 +3,16 @@ class StrongJSON
|
|
3
3
|
NONE = ::Object.new
|
4
4
|
|
5
5
|
class Base
|
6
|
+
attr_reader :type
|
7
|
+
|
6
8
|
def initialize(type)
|
7
9
|
@type = type
|
8
10
|
end
|
9
11
|
|
10
|
-
def test(value
|
12
|
+
def test(value)
|
11
13
|
case @type
|
12
|
-
when :
|
13
|
-
|
14
|
+
when :ignored
|
15
|
+
true
|
14
16
|
when :any
|
15
17
|
true
|
16
18
|
when :number
|
@@ -28,7 +30,8 @@ class StrongJSON
|
|
28
30
|
|
29
31
|
def coerce(value, path: [])
|
30
32
|
raise Error.new(value: value, type: self, path: path) unless test(value)
|
31
|
-
|
33
|
+
raise IllegalTypeError.new(type: self) if path == [] && @type == :ignored
|
34
|
+
@type != :ignored ? value : NONE
|
32
35
|
end
|
33
36
|
|
34
37
|
def to_s
|
@@ -86,14 +89,32 @@ class StrongJSON
|
|
86
89
|
|
87
90
|
result = {}
|
88
91
|
|
89
|
-
@fields.
|
90
|
-
|
91
|
-
|
92
|
+
all_keys = (@fields.keys + object.keys).sort.uniq
|
93
|
+
all_keys.each do |key|
|
94
|
+
type = @fields.has_key?(key) ? @fields[key] : NONE
|
95
|
+
value = object.has_key?(key) ? object[key] : NONE
|
96
|
+
|
97
|
+
test_value_type(path + [key], type, value) do |v|
|
98
|
+
result[key] = v
|
99
|
+
end
|
92
100
|
end
|
93
101
|
|
94
102
|
result
|
95
103
|
end
|
96
104
|
|
105
|
+
def test_value_type(path, type, value)
|
106
|
+
if NONE.equal?(type) && !NONE.equal?(value)
|
107
|
+
raise UnexpectedFieldError.new(path: path, value: value)
|
108
|
+
end
|
109
|
+
|
110
|
+
v = type.coerce(value, path: path)
|
111
|
+
|
112
|
+
return if NONE.equal?(v) || NONE.equal?(type)
|
113
|
+
return if type.is_a?(Optional) && v == nil
|
114
|
+
|
115
|
+
yield(v)
|
116
|
+
end
|
117
|
+
|
97
118
|
def merge(fields)
|
98
119
|
if fields.is_a?(Object)
|
99
120
|
fields = Object.instance_variable_get("@fields")
|
@@ -119,6 +140,32 @@ class StrongJSON
|
|
119
140
|
end
|
120
141
|
end
|
121
142
|
|
143
|
+
class UnexpectedFieldError < StandardError
|
144
|
+
attr_reader :path, :value
|
145
|
+
|
146
|
+
def initialize(path: , value:)
|
147
|
+
@path = path
|
148
|
+
@value = value
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
position = "#{path.join('.')}"
|
153
|
+
"Unexpected field of #{position} (#{value})"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class IllegalTypeError < StandardError
|
158
|
+
attr_reader :type
|
159
|
+
|
160
|
+
def initialize(type:)
|
161
|
+
@type = type
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_s
|
165
|
+
"#{type} can not be put on toplevel"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
122
169
|
class Error < StandardError
|
123
170
|
attr_reader :path, :type, :value
|
124
171
|
|
@@ -129,7 +176,7 @@ class StrongJSON
|
|
129
176
|
end
|
130
177
|
|
131
178
|
def to_s
|
132
|
-
position = " at
|
179
|
+
position = path.empty? ? "" : " at .#{path.join('.')}"
|
133
180
|
"Expected type of value #{value}#{position} is #{type}"
|
134
181
|
end
|
135
182
|
end
|
data/lib/strong_json/types.rb
CHANGED
data/lib/strong_json/version.rb
CHANGED
data/spec/basetype_spec.rb
CHANGED
@@ -2,11 +2,11 @@ require "strong_json"
|
|
2
2
|
|
3
3
|
describe StrongJSON::Type::Base do
|
4
4
|
describe "#test" do
|
5
|
-
context ":
|
6
|
-
let (:type) { StrongJSON::Type::Base.new(:
|
5
|
+
context ":ignored" do
|
6
|
+
let (:type) { StrongJSON::Type::Base.new(:ignored) }
|
7
7
|
|
8
|
-
it "
|
9
|
-
expect
|
8
|
+
it "can not be placed on toplevel" do
|
9
|
+
expect { type.coerce(3, path: []) }.to raise_error(StrongJSON::Type::IllegalTypeError)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
data/spec/json_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require "strong_json"
|
|
3
3
|
describe "StrongJSON.new" do
|
4
4
|
it "tests the structure of a JSON object" do
|
5
5
|
s = StrongJSON.new do
|
6
|
-
let :item, object(name: string, count: numeric, price: numeric)
|
6
|
+
let :item, object(name: string, count: numeric, price: numeric, comment: ignored)
|
7
7
|
let :checkout, object(items: array(item), change: optional(number))
|
8
8
|
end
|
9
9
|
|
data/spec/object_spec.rb
CHANGED
@@ -9,23 +9,21 @@ describe StrongJSON::Type::Object do
|
|
9
9
|
expect(type.coerce(a: 123, b: "test")).to eq(a: 123, b: "test")
|
10
10
|
end
|
11
11
|
|
12
|
-
it "
|
12
|
+
it "rejects unspecified fields" do
|
13
13
|
type = StrongJSON::Type::Object.new(a: StrongJSON::Type::Base.new(:numeric))
|
14
14
|
|
15
|
-
expect
|
15
|
+
expect { type.coerce(a:123, b:true) }.to raise_error(StrongJSON::Type::UnexpectedFieldError)
|
16
16
|
end
|
17
17
|
|
18
|
-
describe "
|
19
|
-
it "
|
20
|
-
type = StrongJSON::Type::Object.new(a: StrongJSON::Type::Base.new(:
|
21
|
-
|
22
|
-
expect{ type.coerce(a: 123, b: true) }.to raise_error(StrongJSON::Type::Error)
|
18
|
+
describe "ignored" do
|
19
|
+
it "ignores field with any value" do
|
20
|
+
type = StrongJSON::Type::Object.new(a: StrongJSON::Type::Base.new(:numeric), b: StrongJSON::Type::Base.new(:ignored))
|
21
|
+
expect(type.coerce(a: 123, b: true)).to eq(a: 123)
|
23
22
|
end
|
24
23
|
|
25
24
|
it "accepts if it does not contains the field" do
|
26
|
-
type = StrongJSON::Type::Object.new(a: StrongJSON::Type::Base.new(:
|
27
|
-
|
28
|
-
expect(type.coerce(b: true)).to eq({})
|
25
|
+
type = StrongJSON::Type::Object.new(a: StrongJSON::Type::Base.new(:numeric), b: StrongJSON::Type::Base.new(:ignored))
|
26
|
+
expect(type.coerce(a: 123)).to eq(a: 123)
|
29
27
|
end
|
30
28
|
end
|
31
29
|
|
@@ -38,7 +36,7 @@ describe StrongJSON::Type::Object do
|
|
38
36
|
it "accepts missing field if optional" do
|
39
37
|
type = StrongJSON::Type::Object.new(a: StrongJSON::Type::Optional.new(StrongJSON::Type::Base.new(:numeric)))
|
40
38
|
|
41
|
-
expect(type.coerce(
|
39
|
+
expect(type.coerce({})).to eq({})
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
@@ -63,8 +61,7 @@ describe StrongJSON::Type::Object do
|
|
63
61
|
|
64
62
|
it "return object which ignores given fields but preserve others" do
|
65
63
|
ty2 = type.except(:a)
|
66
|
-
|
67
|
-
expect(ty2.coerce(a: 123, b: "test")).to eq({ b: "test" })
|
64
|
+
expect(ty2.coerce(b: "test")).to eq({ b: "test" })
|
68
65
|
end
|
69
66
|
end
|
70
67
|
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.0
|
4
|
+
version: 0.1.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-
|
11
|
+
date: 2015-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|