strong_json 1.1.0 → 2.0.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
  SHA256:
3
- metadata.gz: bd9a6dd9efbb0ccca7cd5dfc14a415b4f408138724e5c4a4fe58d7fcc8fccafe
4
- data.tar.gz: 02a451a69abca02dfdb418b13302b3bb6545cf23b0ec302698b65d2127d6f6d4
3
+ metadata.gz: 4b06583a5049ceb64b418d9dbfa9d9b024487c116a2b84cda2effc8f20f47dfc
4
+ data.tar.gz: 3998a14cfa6c87822c9d8da3d510c64cdb3ec8ca49762e104564c2109711ca8e
5
5
  SHA512:
6
- metadata.gz: 4026edd18f863953d03652c5ea691a5ad6a88ba1db2ca8f5181da60974ebd70d10cf6cb09bc0d836583e285a9279d70b1dda43178881a3da37093e40ffd793b3
7
- data.tar.gz: e2ca4033fe34c2f9b433c6a92f8b44484b7f30dfb89ca10fb5b0bed8a21370eb463e8444f5d28c3575c133bb879868c762fba36620a78f27b0886df59f1f970a
6
+ metadata.gz: 3eea214df1267ca23eb4ca45150c8a967c2f928738a225ccc1b42d2a5dbace87593d1ae8184554e47e40012f3e694b5c8f333a58dcb90fb680e73401b5dd49b6
7
+ data.tar.gz: 5e974fe31499668b39a8dd953267f2a7ef2ea0b070dcc1c48dfa14b7c1ac33df5a57d7dd20a039bfe1cd16dfa52de0a91c8c8a1bc3445fd48b092b007437d827
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 2.0.0 (2019-06-28)
6
+
7
+ * [17](https://github.com/soutaro/strong_json/pull/17): Revise `Object` type _ignore_/_reject_ API
8
+
5
9
  ## 1.1.0 (2019-06-09)
6
10
 
7
11
  * [#15](https://github.com/soutaro/strong_json/pull/15): Add `hash` type
data/README.md CHANGED
@@ -59,26 +59,20 @@ If an attribute has a value which does not match with given type, the `coerce` m
59
59
 
60
60
  #### Ignoring unknown attributes
61
61
 
62
- `Object` type ignores unknown attributes by default.
63
- You can reject the unknown attributes.
62
+ You can use `ignore` method to ignore unknown attributes.
64
63
 
65
64
  ```
66
- object(attrs).ignore(Set.new) # Ignores nothing (== raise an error)
67
- object(attrs).ignore!(Set.new) # Destructive version
65
+ object(attrs).ignore() # Ignores all unknown attributes.
66
+ object(attrs).ignore(:x, :y) # Ignores :x and :y, but rejects other unknown attributes.
67
+ object(attrs).ignore(except: Set[:x, :y]) # Rejects :x and :y, but ignores other unknown attributes.
68
68
  ```
69
69
 
70
- You can selectively ignore attributes:
70
+ `Object` also provides `reject` method to do the opposite.
71
71
 
72
72
  ```
73
- object(attrs).ignore(Set.new([:a, :b, :c])) # Ignores only :a, :b, and :c
74
- object(attrs).ignore(:any) # Ignores everything (default)
75
- ```
76
-
77
- `Object` also has `prohibit` method to specify attributes to make the type check failed.
78
-
79
- ```
80
- object(attrs).prohibit(Set.new([:created_at, :updated_at])) # Make type check failed if :created_at or :updated_at included
81
- object(attrs).prohibit!(Set.new([:created_at, :updated_at])) # Destructive version
73
+ object(attrs).reject() # Rejects all unknown attributes. (default)
74
+ object(attrs).reject(:x, :y) # Rejects :x and :y, but ignores other unknown attributes.
75
+ object(attrs).reject(except: Set[:x, :y]) # Ignores :x and :y, but rejects other unknown attributes.
82
76
  ```
83
77
 
84
78
  ### array(type)
@@ -192,13 +192,13 @@ class StrongJSON
192
192
  include Match
193
193
  include WithAlias
194
194
 
195
- # @dynamic fields, ignored_attributes, prohibited_attributes
196
- attr_reader :fields, :ignored_attributes, :prohibited_attributes
195
+ # @dynamic fields, on_unknown, exceptions
196
+ attr_reader :fields, :on_unknown, :exceptions
197
197
 
198
- def initialize(fields, ignored_attributes:, prohibited_attributes:)
198
+ def initialize(fields, on_unknown:, exceptions:)
199
199
  @fields = fields
200
- @ignored_attributes = ignored_attributes
201
- @prohibited_attributes = prohibited_attributes
200
+ @on_unknown = on_unknown
201
+ @exceptions = exceptions
202
202
  end
203
203
 
204
204
  def coerce(object, path: ErrorPath.root(self))
@@ -206,33 +206,31 @@ class StrongJSON
206
206
  raise TypeError.new(path: path, value: object)
207
207
  end
208
208
 
209
- unless (intersection = Set.new(object.keys).intersection(prohibited_attributes)).empty?
210
- raise UnexpectedAttributeError.new(path: path, attribute: intersection.to_a.first)
211
- end
209
+ object = object.dup
210
+ unknown_attributes = Set.new(object.keys) - fields.keys
212
211
 
213
- case attrs = ignored_attributes
214
- when :any
215
- object = object.dup
216
- extra_keys = Set.new(object.keys) - Set.new(fields.keys)
217
- extra_keys.each do |key|
218
- object.delete(key)
212
+ case on_unknown
213
+ when :reject
214
+ unknown_attributes.each do |attr|
215
+ if exceptions.member?(attr)
216
+ object.delete(attr)
217
+ else
218
+ raise UnexpectedAttributeError.new(path: path, attribute: attr)
219
+ end
219
220
  end
220
- when Set
221
- object = object.dup
222
- attrs.each do |key|
223
- object.delete(key)
221
+ when :ignore
222
+ unknown_attributes.each do |attr|
223
+ if exceptions.member?(attr)
224
+ raise UnexpectedAttributeError.new(path: path, attribute: attr)
225
+ else
226
+ object.delete(attr)
227
+ end
224
228
  end
225
229
  end
226
230
 
227
231
  # @type var result: ::Hash<Symbol, any>
228
232
  result = {}
229
233
 
230
- object.each do |key, _|
231
- unless fields.key?(key)
232
- raise UnexpectedAttributeError.new(path: path, attribute: key)
233
- end
234
- end
235
-
236
234
  fields.each do |key, type|
237
235
  result[key] = type.coerce(object[key], path: path.dig(key: key, type: type))
238
236
  end
@@ -240,29 +238,37 @@ class StrongJSON
240
238
  _ = result
241
239
  end
242
240
 
243
- def ignore(attrs)
244
- Object.new(fields, ignored_attributes: attrs, prohibited_attributes: prohibited_attributes)
245
- end
246
-
247
- def ignore!(attrs)
248
- @ignored_attributes = attrs
249
- self
250
- end
251
-
252
- def prohibit(attrs)
253
- Object.new(fields, ignored_attributes: ignored_attributes, prohibited_attributes: attrs)
241
+ # @type method ignore: (*Symbol, ?except: Set<Symbol>?) -> self
242
+ def ignore(*ignores, except: nil)
243
+ if ignores.empty? && !except
244
+ Object.new(fields, on_unknown: :ignore, exceptions: Set[])
245
+ else
246
+ if except
247
+ Object.new(fields, on_unknown: :ignore, exceptions: except)
248
+ else
249
+ Object.new(fields, on_unknown: :reject, exceptions: Set.new(ignores))
250
+ end
251
+ end
254
252
  end
255
253
 
256
- def prohibit!(attrs)
257
- @prohibited_attributes = attrs
258
- self
254
+ # @type method reject: (*Symbol, ?except: Set<Symbol>?) -> self
255
+ def reject(*rejecteds, except: nil)
256
+ if rejecteds.empty? && !except
257
+ Object.new(fields, on_unknown: :reject, exceptions: Set[])
258
+ else
259
+ if except
260
+ Object.new(fields, on_unknown: :reject, exceptions: except)
261
+ else
262
+ Object.new(fields, on_unknown: :ignore, exceptions: Set.new(rejecteds))
263
+ end
264
+ end
259
265
  end
260
266
 
261
267
  def update_fields
262
268
  fields.dup.yield_self do |fields|
263
269
  yield fields
264
270
 
265
- Object.new(fields, ignored_attributes: ignored_attributes, prohibited_attributes: prohibited_attributes)
271
+ Object.new(fields, on_unknown: on_unknown, exceptions: exceptions)
266
272
  end
267
273
  end
268
274
 
@@ -278,8 +284,8 @@ class StrongJSON
278
284
  if other.is_a?(Object)
279
285
  # @type var other: Object<any>
280
286
  other.fields == fields &&
281
- other.ignored_attributes == ignored_attributes &&
282
- other.prohibited_attributes == prohibited_attributes
287
+ other.on_unknown == on_unknown &&
288
+ other.exceptions == exceptions
283
289
  end
284
290
  end
285
291
 
@@ -3,9 +3,9 @@ class StrongJSON
3
3
  # @type method object: (?Hash<Symbol, ty>) -> Type::Object<any>
4
4
  def object(fields = {})
5
5
  if fields.empty?
6
- Type::Object.new(fields, ignored_attributes: nil, prohibited_attributes: Set.new)
6
+ Type::Object.new(fields, on_unknown: :ignore, exceptions: Set.new)
7
7
  else
8
- Type::Object.new(fields, ignored_attributes: :any, prohibited_attributes: Set.new)
8
+ Type::Object.new(fields, on_unknown: :reject, exceptions: Set.new)
9
9
  end
10
10
  end
11
11
 
@@ -1,5 +1,5 @@
1
1
  class StrongJSON
2
2
  # @dynamic initialize, let
3
-
4
- VERSION = "1.1.0"
3
+
4
+ VERSION = "2.0.0"
5
5
  end
data/sig/type.rbi CHANGED
@@ -60,16 +60,26 @@ class StrongJSON::Type::Object<'t>
60
60
  include WithAlias
61
61
 
62
62
  attr_reader fields: ::Hash<Symbol, _Schema<any>>
63
- attr_reader ignored_attributes: :any | Set<Symbol> | nil
64
- attr_reader prohibited_attributes: Set<Symbol>
63
+ attr_reader on_unknown: :ignore | :reject
64
+ attr_reader exceptions: Set<Symbol>
65
65
 
66
- def initialize: (::Hash<Symbol, _Schema<'t>>, ignored_attributes: :any | Set<Symbol> | nil, prohibited_attributes: Set<Symbol>) -> any
66
+ def initialize: (::Hash<Symbol, _Schema<'t>>, on_unknown: :ignore | :reject, exceptions: Set<Symbol>) -> any
67
67
  def coerce: (any, ?path: ErrorPath) -> 't
68
68
 
69
- def ignore: (:any | Set<Symbol> | nil) -> self
70
- def ignore!: (:any | Set<Symbol> | nil) -> self
71
- def prohibit: (Set<Symbol>) -> self
72
- def prohibit!: (Set<Symbol>) -> self
69
+ # If no argument is given, it ignores all unknown attributes.
70
+ # If `Symbol`s are given, it ignores the listed attributes, but rejects if other unknown attributes are detected.
71
+ # If `except:` is specified, it rejects attributes listed in `except` are detected, but ignores other unknown attributes.
72
+ def ignore: () -> self
73
+ | (*Symbol) -> self
74
+ | (?except: Set<Symbol>) -> self
75
+
76
+ # If no argument is given, it rejects on any unknown attribute.
77
+ # If `Symbol`s are given, it rejects the listed attributes are detected, but ignores other unknown attributes.
78
+ # If `except:` is specified, it ignores given attributes, but rejects if other unknown attributes are detected.
79
+ def reject: () -> self
80
+ | (*Symbol) -> self
81
+ | (?except: Set<Symbol>) -> self
82
+
73
83
  def update_fields: <'x> { (::Hash<Symbol, _Schema<any>>) -> void } -> Object<'x>
74
84
  end
75
85
 
data/spec/enum_spec.rb CHANGED
@@ -29,16 +29,16 @@ describe StrongJSON::Type::Enum do
29
29
  id: StrongJSON::Type::Literal.new("id1"),
30
30
  value: StrongJSON::Type::Base.new(:string)
31
31
  },
32
- ignored_attributes: nil,
33
- prohibited_attributes: Set.new
32
+ on_unknown: :raise,
33
+ exceptions: Set[]
34
34
  ),
35
35
  StrongJSON::Type::Object.new(
36
36
  {
37
37
  id: StrongJSON::Type::Base.new(:string),
38
38
  value: StrongJSON::Type::Base.new(:symbol)
39
39
  },
40
- ignored_attributes: nil,
41
- prohibited_attributes: Set.new
40
+ on_unknown: :raise,
41
+ exceptions: Set[]
42
42
  ),
43
43
  StrongJSON::Type::Optional.new(StrongJSON::Type::Literal.new(3)),
44
44
  StrongJSON::Type::Literal.new(false),
@@ -73,8 +73,8 @@ describe StrongJSON::Type::Enum do
73
73
  regexp: StrongJSON::Type::Base.new(:string),
74
74
  option: StrongJSON::Type::Base.new(:string),
75
75
  },
76
- ignored_attributes: nil,
77
- prohibited_attributes: Set.new
76
+ on_unknown: :raise,
77
+ exceptions: Set[]
78
78
  )
79
79
  }
80
80
 
@@ -83,8 +83,8 @@ describe StrongJSON::Type::Enum do
83
83
  {
84
84
  literal: StrongJSON::Type::Base.new(:string)
85
85
  },
86
- ignored_attributes: nil,
87
- prohibited_attributes: Set.new
86
+ on_unknown: :raise,
87
+ exceptions: Set[]
88
88
  )
89
89
  }
90
90
 
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).ignore(Set.new([:comment]))
6
+ let :item, object(name: string, count: numeric, price: numeric).ignore(:comment)
7
7
  let :items, array(item)
8
8
  let :checkout,
9
9
  object(items: items,
data/spec/object_spec.rb CHANGED
@@ -8,114 +8,73 @@ describe StrongJSON::Type::Object do
8
8
  a: StrongJSON::Type::Base.new(:numeric),
9
9
  b: StrongJSON::Type::Base.new(:string)
10
10
  },
11
- ignored_attributes: nil,
12
- prohibited_attributes: Set.new
11
+ on_unknown: :raise,
12
+ exceptions: Set.new
13
13
  )
14
14
 
15
15
  expect(type.coerce(a: 123, b: "test")).to eq(a: 123, b: "test")
16
16
  end
17
17
 
18
- it "rejects unspecified fields" do
19
- type = StrongJSON::Type::Object.new(
20
- {
21
- a: StrongJSON::Type::Base.new(:numeric)
22
- },
23
- ignored_attributes: nil,
24
- prohibited_attributes: Set.new
25
- )
26
-
27
- expect { type.coerce(a:123, b:true) }.to raise_error(StrongJSON::Type::UnexpectedAttributeError) {|e|
28
- expect(e.path.to_s).to eq("$")
29
- expect(e.attribute).to eq(:b)
30
- }
31
- end
32
-
33
18
  it "rejects objects with missing fields" do
34
19
  type = StrongJSON::Type::Object.new(
35
20
  {
36
21
  a: StrongJSON::Type::Base.new(:numeric)
37
22
  },
38
- ignored_attributes: nil,
39
- prohibited_attributes: Set.new
23
+ on_unknown: :reject,
24
+ exceptions: Set.new
40
25
  )
41
26
 
42
- expect{ type.coerce(b: "test") }.to raise_error(StrongJSON::Type::UnexpectedAttributeError) {|e|
27
+ expect{ type.coerce(a: 123, b: "test") }.to raise_error(StrongJSON::Type::UnexpectedAttributeError) {|e|
43
28
  expect(e.path.to_s).to eq("$")
44
29
  expect(e.attribute).to eq(:b)
45
30
  }
46
31
  end
47
32
 
48
- describe "ignored_attributes" do
49
- context "when ignored_attributes are given as Set" do
50
- let(:type) {
51
- StrongJSON::Type::Object.new(
52
- {
53
- a: StrongJSON::Type::Base.new(:numeric)
54
- },
55
- ignored_attributes: Set.new([:b]),
56
- prohibited_attributes: Set.new
57
- )
58
- }
59
-
60
- it "ignores field with any value" do
61
- expect(type.coerce(a: 123, b: true)).to eq(a: 123)
62
- end
63
-
64
- it "accepts if it does not contains the field" do
65
- expect(type.coerce(a: 123)).to eq(a: 123)
66
- end
67
- end
68
-
69
- context "when ignored_attributes is nil" do
70
- let(:type) {
71
- StrongJSON::Type::Object.new(
72
- {
73
- a: StrongJSON::Type::Base.new(:numeric)
74
- },
75
- ignored_attributes: nil,
76
- prohibited_attributes: Set.new
77
- )
78
- }
33
+ context "when on_unknown is :ignore" do
34
+ let(:type) {
35
+ StrongJSON::Type::Object.new(
36
+ {
37
+ a: StrongJSON::Type::Base.new(:numeric)
38
+ },
39
+ on_unknown: :ignore,
40
+ exceptions: Set[:x]
41
+ )
42
+ }
79
43
 
80
- it "ignores field with any value" do
81
- expect {
82
- type.coerce(a: 123, b: true)
83
- }.to raise_error(StrongJSON::Type::UnexpectedAttributeError)
84
- end
44
+ it "ignores field with any value" do
45
+ expect(type.coerce(a: 123, b: true)).to eq(a: 123)
85
46
  end
86
47
 
87
- context "when ignored_attributes is :any" do
88
- let(:type) {
89
- StrongJSON::Type::Object.new(
90
- {
91
- a: StrongJSON::Type::Base.new(:numeric)
92
- },
93
- ignored_attributes: :any,
94
- prohibited_attributes: Set.new
95
- )
48
+ it "raises error on attributes listed in exceptions" do
49
+ expect {
50
+ type.coerce(a: 123, x: false)
51
+ }.to raise_error(StrongJSON::Type::UnexpectedAttributeError) {|error|
52
+ expect(error.attribute).to eq(:x)
96
53
  }
97
-
98
- it "ignores field with any value" do
99
- expect(type.coerce(a: 123, b: true)).to eq(a: 123)
100
- end
101
54
  end
102
55
  end
103
56
 
104
- describe "prohibited_attributes" do
57
+ context "when on_unknown is :reject" do
105
58
  let(:type) {
106
59
  StrongJSON::Type::Object.new(
107
60
  {
108
61
  a: StrongJSON::Type::Base.new(:numeric)
109
62
  },
110
- ignored_attributes: :any,
111
- prohibited_attributes: Set.new([:x])
63
+ on_unknown: :reject,
64
+ exceptions: Set[:c]
112
65
  )
113
66
  }
114
67
 
115
- it "raises error if the attribute is given" do
68
+ it "raises with unknown attribute" do
116
69
  expect {
117
- type.coerce(a:123, b:true, x: [])
118
- }.to raise_error(StrongJSON::Type::UnexpectedAttributeError)
70
+ type.coerce(a: 123, b: true)
71
+ }.to raise_error(StrongJSON::Type::UnexpectedAttributeError) {|error|
72
+ expect(error.attribute).to eq(:b)
73
+ }
74
+ end
75
+
76
+ it "ignores attributes listed in exceptions" do
77
+ expect(type.coerce(a: 123, c: false)).to eq(a:123)
119
78
  end
120
79
  end
121
80
  end
@@ -126,8 +85,8 @@ describe StrongJSON::Type::Object do
126
85
  {
127
86
  a: StrongJSON::Type::Optional.new(StrongJSON::Type::Base.new(:numeric))
128
87
  },
129
- ignored_attributes: nil,
130
- prohibited_attributes: Set.new
88
+ on_unknown: :raise,
89
+ exceptions: Set[]
131
90
  )
132
91
  }
133
92
 
@@ -151,8 +110,8 @@ describe StrongJSON::Type::Object do
151
110
  a: StrongJSON::Type::Base.new(:numeric),
152
111
  b: StrongJSON::Type::Base.new(:string)
153
112
  },
154
- ignored_attributes: nil,
155
- prohibited_attributes: Set.new
113
+ on_unknown: :raise,
114
+ exceptions: Set[]
156
115
  )
157
116
  }
158
117
 
@@ -164,4 +123,73 @@ describe StrongJSON::Type::Object do
164
123
  expect(type =~ {}).to be_falsey
165
124
  end
166
125
  end
126
+
127
+ describe "#ignore" do
128
+ let (:type) {
129
+ StrongJSON::Type::Object.new(
130
+ { a: StrongJSON::Type::Base.new(:numeric) },
131
+ on_unknown: :raise,
132
+ exceptions: Set[]
133
+ )
134
+ }
135
+
136
+ context "if no argument is given" do
137
+ it "ignores all unknown attributes" do
138
+ updated_type = type.ignore()
139
+ expect(updated_type.on_unknown).to eq(:ignore)
140
+ expect(updated_type.exceptions).to eq(Set[])
141
+ end
142
+ end
143
+
144
+
145
+ context "if list of Symbol is given" do
146
+ it "ignores specified attributes but raises unknowns" do
147
+ updated_type = type.ignore(:x, :y)
148
+ expect(updated_type.on_unknown).to eq(:reject)
149
+ expect(updated_type.exceptions).to eq(Set[:x, :y])
150
+ end
151
+ end
152
+
153
+ context "if except keyword is specified" do
154
+ it "raises unknowns but ignores specified attributes" do
155
+ updated_type = type.ignore(except: Set[:x, :y])
156
+ expect(updated_type.on_unknown).to eq(:ignore)
157
+ expect(updated_type.exceptions).to eq(Set[:x, :y])
158
+ end
159
+ end
160
+ end
161
+
162
+ describe "#reject" do
163
+ let (:type) {
164
+ StrongJSON::Type::Object.new(
165
+ { a: StrongJSON::Type::Base.new(:numeric) },
166
+ on_unknown: :raise,
167
+ exceptions: Set[]
168
+ )
169
+ }
170
+
171
+ context "if no argument is given" do
172
+ it "raises on any unknown attribute" do
173
+ updated_type = type.reject()
174
+ expect(updated_type.on_unknown).to eq(:reject)
175
+ expect(updated_type.exceptions).to eq(Set[])
176
+ end
177
+ end
178
+
179
+ context "if list of Symbol is given" do
180
+ it "raises unknowns but ignores specified attributes" do
181
+ updated_type = type.reject(:x, :y)
182
+ expect(updated_type.on_unknown).to eq(:ignore)
183
+ expect(updated_type.exceptions).to eq(Set[:x, :y])
184
+ end
185
+ end
186
+
187
+ context "if except keyword is specified" do
188
+ it "ignores specified attributes but raises unknowns" do
189
+ updated_type = type.reject(except: Set[:x, :y])
190
+ expect(updated_type.on_unknown).to eq(:reject)
191
+ expect(updated_type.exceptions).to eq(Set[:x, :y])
192
+ end
193
+ end
194
+ end
167
195
  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: 1.1.0
4
+ version: 2.0.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: 2019-06-09 00:00:00.000000000 Z
11
+ date: 2019-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler