strong_json 0.0.4 → 0.1.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: e4ebb2da5c4e3d195b5141cff335676d87868bf7
4
- data.tar.gz: 8effc10c56e7208a846bbde39a144d2bb03bb91a
3
+ metadata.gz: ec2fc74653b07371d8619d7d830ede16d86e277f
4
+ data.tar.gz: 07b28f98c1d24ec5136bf77b86e66cb77fd2e103
5
5
  SHA512:
6
- metadata.gz: 377ee5533793e2523b521721dfbb567e63f10077521905a19209b0e71201951fd7a2c9c2a2249fe10bac148df661a696d103ec252f3286cd8b1f672cb9b546a7
7
- data.tar.gz: de56ca69657851d241233562be8ec5cc95296639e6991be9121fd49826282b8cc485dcd184c0e6c038236a2c7eac584d8bc029df0464a4528a79f36511cf656e
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 Parameter is for simple structures, like HTML forms, and StrongJSON is for complex structures, like JSON objects posted to API.
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 is conformant with `order`'s structure, the `json` will be that value.
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, the fields will be droped.
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
- * Other fields will be ignored
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
- * `prohibited` Any value will be rejected
67
+ * `ignored` Any value will be ignored
69
68
 
70
69
  ### Shortcuts
71
70
 
@@ -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, absent = false)
12
+ def test(value)
11
13
  case @type
12
- when :prohibited
13
- NONE.equal?(value)
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
- value
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.each do |name, ty|
90
- value = ty.coerce(object.has_key?(name) ? object[name] : NONE, path: path + [name])
91
- result[name] = value if object.has_key?(name)
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 #{path.join('.')}"
179
+ position = path.empty? ? "" : " at .#{path.join('.')}"
133
180
  "Expected type of value #{value}#{position} is #{type}"
134
181
  end
135
182
  end
@@ -51,5 +51,9 @@ class StrongJSON
51
51
  def boolean?
52
52
  optional(boolean)
53
53
  end
54
+
55
+ def ignored
56
+ StrongJSON::Type::Base.new(:ignored)
57
+ end
54
58
  end
55
59
  end
@@ -1,3 +1,3 @@
1
1
  class StrongJSON
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -2,11 +2,11 @@ require "strong_json"
2
2
 
3
3
  describe StrongJSON::Type::Base do
4
4
  describe "#test" do
5
- context ":prohibited" do
6
- let (:type) { StrongJSON::Type::Base.new(:prohibited) }
5
+ context ":ignored" do
6
+ let (:type) { StrongJSON::Type::Base.new(:ignored) }
7
7
 
8
- it "rejects number" do
9
- expect(type.test(123)).to be_falsey
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 "drops unspecified fields" do
12
+ it "rejects unspecified fields" do
13
13
  type = StrongJSON::Type::Object.new(a: StrongJSON::Type::Base.new(:numeric))
14
14
 
15
- expect(type.coerce(a: 123, b: true)).to eq(a: 123)
15
+ expect { type.coerce(a:123, b:true) }.to raise_error(StrongJSON::Type::UnexpectedFieldError)
16
16
  end
17
17
 
18
- describe "prohibited" do
19
- it "rejects field with any value" do
20
- type = StrongJSON::Type::Object.new(a: StrongJSON::Type::Base.new(:prohibited))
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(:prohibited))
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(b: "test")).to eq({})
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
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-02-16 00:00:00.000000000 Z
11
+ date: 2015-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler