strong_json 1.1.0 → 2.0.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
  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