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