decoding 0.1.0 → 0.2.1

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: 95c32106911949f49f3b50dc46f744912cc9abf75405cd7e20248de3dd0b1668
4
- data.tar.gz: 492fbe3bf3d64cfe954da4b7016dabb5287d19d140bbf662146cff678dbe7ca6
3
+ metadata.gz: 05a711dfeb38945b0d8f57745f9555cd0a8a01b8e74a119aafddb9b74e3f4ccc
4
+ data.tar.gz: 5faa5cc2848bb269bf1bf65766dfed1fd80f6f74e39b48c3cfcddc9b9ede1946
5
5
  SHA512:
6
- metadata.gz: 7ad3271531ef17b985ecacf39e480baa8c117170d6d3d69b0e2bb1f231663f9ad10a41a390e740ff951600dac7f2d7163ac817d3f1928979cea03898249b8a77
7
- data.tar.gz: 95b14f9f7bd57df7f537d6c3402b2eafd46ef397686ac343b2239e76732189d606896e26b606838c403436d1738007deee3e51880880f6839ab77308d7d80a90
6
+ metadata.gz: 6822f9f59e3da74225b435131991b9e903bfd77206c1edd0620849d74d89db509572550eb184ec49c1ec5ef4ef1729072a93f104b6bf05fa300ffe2853dfbdee
7
+ data.tar.gz: 30d2caa4fa4ff49fab9f4327eada6daf088204f392e87765048decc5b6c3534ebc629850d8d0aff5a7b43228afc8f9bbe58fd121a6466e515e1dd8b4b69b96b7
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.3.0
1
+ ruby 3.4.7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2025-10-25
4
+
5
+ * Fixed missed error not returning `Decoding::Failure`
6
+
7
+ ## [0.2.0] - 2025-10-25
8
+
9
+ * Added `decode_hash`, `regexp` and `original` decoders
10
+ * Fixed incorrect `Decoding::Failure` value comparisons
11
+ * Fixed some decoders incorrectly returning `String` values rather than
12
+ `Decoding::Failure` values
13
+ * Bumped target Ruby version to 3.4.7
14
+
3
15
  ## [0.1.0] - 2024-05-08
4
16
 
5
17
  - Initial release
data/README.md CHANGED
@@ -1,12 +1,9 @@
1
1
  # Decoding
2
2
 
3
- Decoding is a library to help transform unknown external data into neat values
4
- with known shapes.
3
+ Decoding is a library to help transform unknown external data into neat values with known shapes.
5
4
 
6
5
  ## Installation
7
6
 
8
- TODO: Replace `decoding` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
9
-
10
7
  Install the gem and add to the application's Gemfile by executing:
11
8
 
12
9
  $ bundle add decoding
@@ -17,111 +14,137 @@ If bundler is not being used to manage dependencies, install the gem by executin
17
14
 
18
15
  ## Usage
19
16
 
20
- Decoding is a library to help transform unknown external data into neat values
21
- with known shapes. Consider calling an HTTP API: you might pull in whatever
22
- value. After passing it through decoder, you will have a value with a known
23
- shape -- or a sensible error message.
17
+ Decoding is a library to help transform unknown external data into neat values with known shapes. Consider calling an HTTP API: you might pull in whatever value. After passing it through decoder, you will have a value with a known shape -- or a sensible error message.
24
18
 
25
19
  For example, call an API to get some JSON value:
26
20
 
27
- body = JSON.parse(Net::HTTP.get("https://api.placeholderjson.dev/shipments/7EBWXB5"))
21
+ ```ruby
22
+ body = JSON.parse(Net::HTTP.get("https://api.placeholderjson.dev/shipments/7EBWXB5"))
23
+ ```
28
24
 
29
- How do you safely work with `body`? If parsing the response body as JSON has
30
- worked, you know you have some kind of Ruby value -- but you're not sure of
31
- its structure. This can lead to cryptic error messages far removing of making
32
- this HTTP call where values are of unexpected types, hashes turn out not to
33
- have certain keys or the nesting of data is different from what you expected.
25
+ How do you safely work with `body`? If parsing the response body as JSON has worked, you know you have some kind of Ruby value -- but you're not sure of its structure. This can lead to cryptic error messages far removing of making this HTTP call where values are of unexpected types, hashes turn out not to have certain keys or the nesting of data is different from what you expected.
34
26
 
35
27
  Assume the response body, parsed as JSON, results in a value like this:
36
28
 
37
- {
38
- "orderID" => "7EBWXB5",
39
- "orderDate" => "1595674680",
40
- "estimatedDeliveryDate" => "1596365935",
41
- "deliveryDate" => null,
42
- "delayed" => false,
43
- "status" => {
44
- "orderPlaced" => true,
45
- "orderShipped" => true,
46
- "outForDelivery" => true,
47
- "orderDelivered" => false
48
- }
49
- }
50
-
51
- We can use decoders to extract exactly those pieces from this payload that we need,
52
- making assertions along the way of what the data looks like and generating helpful errors
53
- when reality does not match our expectations.
29
+ ```ruby
30
+ {
31
+ "orderID" => "7EBWXB5",
32
+ "orderDate" => "1595674680",
33
+ "estimatedDeliveryDate" => "1596365935",
34
+ "deliveryDate" => null,
35
+ "delayed" => false,
36
+ "status" => {
37
+ "orderPlaced" => true,
38
+ "orderShipped" => true,
39
+ "outForDelivery" => true,
40
+ "orderDelivered" => false
41
+ }
42
+ }
43
+ ```
44
+
45
+ We can use decoders to extract exactly those pieces from this payload that we need, making assertions along the way of what the data looks like and generating helpful errors when reality does not match our expectations.
54
46
 
55
47
  For example, we could parse the above payload like so:
56
48
 
57
- Order = Data.define(:id, :date, :status)
58
- D = Decoding::Decoders
59
- order_decoder = D.map(
60
- D.field("orderID", D.string),
61
- D.map(D.field("orderDate", D.string)) { Time.at(_1.to_i) },
62
- D.hash(D.string, D.boolean)
63
- ) { Order.new(*args) }
64
- Decoding.decode(order_decoder, body)
65
- # => Decoding::Ok(#<data Order
66
- id: '7EBWXB5',
67
- date: 2020-07-25 12:58:00 +0200,
68
- status: {"orderPlaced"=>true,"orderShipped"=>true,"outForDelivery"=>true,"orderDelivered"=>false}>)
69
-
70
- Decoders take an input value and generate an output value from it. There are
71
- decoders for basic Ruby types, compound types such as arrays and hashes,
72
- decoders for trying out various decoders and, finally, there is the `map`
73
- decoder for decoding one or more output values from a given input value and
74
- applying a transformation to them with a block. All these decoders can be
75
- composed together into new, more complex decoders.
76
-
77
- A decoder is, in essence, a function that returns a result based on an input value. Consider
78
- how, roughly, the `string` decoder is implemented:
79
-
80
- string_decoder = ->(input_value) do
81
- if input_value.is_a?(String)
82
- Decoding::Result.ok(input_value)
83
- else
84
- Decoding::Result.err("expected String, got #{input_value.class}")
85
- end
86
- end
87
-
88
- You can use the base decoders along with `map` to write more complex decoder. For example, you could
89
- extract a `time_decoder` from the example above:
90
-
91
- time_decoder = D.map(D.string) { Time.at(_1.to_i) }
92
-
93
- When the shape of the incoming data is unknown, you can try out various
94
- decoders in a row to find the first that succeeds using `any`:
95
-
96
- string_or_integer = D.any(D.string, D.integer)
97
- Decoding.decode(string_or_integer, 1) # => Decoding::Ok(1)
98
- Decoding.decode(string_or_integer, '1') # => Decoding::Ok('1')
99
-
100
- You can also base one decoder on a previously decoded value. For example, a
101
- payload might contain a version number describing its format. Use `and_then`
102
- to decode one value and then construct a new decoder to run against the same
103
- input using that value:
104
-
105
- multiple_version_decoder = D.and_then(D.field("version", D.string)) do |version|
106
- if version == "1"
107
- D.field("name", D.string)
108
- else
109
- D.field("fullName", D.string)
110
- end
111
- end
49
+ ```ruby
50
+ Order = Data.define(:id, :date, :status)
51
+ D = Decoding::Decoders
52
+
53
+ time_decoder = D.map(D.string) { Time.at(_1.to_i) }
54
+ order_decoder = D.map(
55
+ D.field("orderID", D.string),
56
+ D.field("orderDate", time_decoder),
57
+ D.hash(D.string, D.boolean)
58
+ ) { Order.new(*args) }
59
+
60
+ Decoding.decode(order_decoder, body)
61
+ # => Decoding::Ok(#<data Order
62
+ id: '7EBWXB5',
63
+ date: 2020-07-25 12:58:00 +0200,
64
+ status: {"orderPlaced"=>true,"orderShipped"=>true,"outForDelivery"=>true,"orderDelivered"=>false}>)
65
+ ```
66
+
67
+ Decoders take an input value and generate an output value from it. There are decoders for basic Ruby types, compound types such as arrays and hashes, decoders for trying out various decoders and, finally, there is the `map` decoder for decoding one or more output values from a given input value and applying a transformation to them with a block. All these decoders can be composed together into new, more complex decoders.
68
+
69
+ A decoder is, in essence, a function that returns a result based on an input value. Consider how, roughly, the `string` decoder is implemented:
70
+
71
+ ```ruby
72
+ string_decoder = ->(input_value) do
73
+ if input_value.is_a?(String)
74
+ Decoding::Result.ok(input_value)
75
+ else
76
+ Decoding::Result.err("expected String, got #{input_value.class}")
77
+ end
78
+ end
79
+ ```
80
+
81
+ You can use the base decoders along with `map` to write more complex decoder. For example, you could extract a `time_decoder` from the example above:
82
+
83
+ ```ruby
84
+ time_decoder = D.map(D.string) { Time.at(_1.to_i) }
85
+ ```
86
+
87
+ When the shape of the incoming data is unknown, you can try out various decoders in a row to find the first that succeeds using `any`:
88
+
89
+ ```ruby
90
+ string_or_integer = D.any(D.string, D.integer)
91
+ Decoding.decode(string_or_integer, 1) # => Decoding::Ok(1)
92
+ Decoding.decode(string_or_integer, '1') # => Decoding::Ok('1')
93
+ ```
94
+
95
+ You can also base one decoder on a previously decoded value. For example, a payload might contain a version number describing its format. Use `and_then` to decode one value and then construct a new decoder to run against the same input using that value:
96
+
97
+ ```ruby
98
+ multiple_version_decoder = D.and_then(D.field("version", D.string)) do |version|
99
+ if version == "1"
100
+ D.field("name", D.string)
101
+ else
102
+ D.field("fullName", D.string)
103
+ end
104
+ end
105
+ ```
112
106
 
113
107
  Now, you have a decoder that can work inputs using format version 1 and 2:
114
108
 
115
- Decoding.decode(multiple_version_decoder, "version" => "1", "name" => "John")
116
- # => "John"
117
- Decoding.decode(multiple_version_decoder, "version" => "2", "fullName" => "Paul")
118
- # => "Paul"
119
-
120
- The return values of decoding are `Decoding::Result` values, which come in
121
- `Ok` and `Err` subclasses. These describe how the decoding either succeeded or
122
- failed. The `Ok` values contain the decoded result, while the `Err` values
123
- always contain a string error message. It is up to you, as a developer, to
124
- decide how to deal with unsuccessful decoding.
109
+ ```ruby
110
+ Decoding.decode(multiple_version_decoder, "version" => "1", "name" => "John")
111
+ # => "John"
112
+ Decoding.decode(multiple_version_decoder, "version" => "2", "fullName" => "Paul")
113
+ # => "Paul"
114
+ ```
115
+
116
+ The return values of decoding are `Decoding::Result` values, which come in `Ok` and `Err` subclasses. These describe how the decoding either succeeded or failed. The `Ok` values contain the decoded result, while the `Err` values always contain a string error message. It is up to you, as a developer, to decide how to deal with unsuccessful decoding.
117
+
118
+ ## Available decoders
119
+
120
+ The following decoders are included:
121
+
122
+ * Basic types
123
+ * `string`
124
+ * `integer`
125
+ * `float`
126
+ * `numeric`
127
+ * `nil`
128
+ * `true`
129
+ * `false`
130
+ * `boolean`
131
+ * `symbol`
132
+ * `regexp`
133
+ * Utility decoders
134
+ * `succeed`
135
+ * `fail`
136
+ * `original`
137
+ * `map`
138
+ * `decode_hash`
139
+ * `and_then`
140
+ * Compound decoders
141
+ * `any`
142
+ * `optional`
143
+ * `field`
144
+ * `array`
145
+ * `index`
146
+ * `hash`
147
+ * `at`
125
148
 
126
149
  ## Development
127
150
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "forwardable"
4
4
  require_relative "result"
5
+ require_relative "failure"
5
6
 
6
7
  module Decoding
7
8
  # A decoder is a callable object that reads any input value and returns an
@@ -10,6 +11,7 @@ module Decoding
10
11
  # @abstract
11
12
  class Decoder
12
13
  extend Forwardable
14
+
13
15
  def_delegators "Decoding::Result", :all, :ok, :err
14
16
 
15
17
  # @param value [Object]
@@ -18,5 +20,9 @@ module Decoding
18
20
 
19
21
  # @return [Decoding::Decoder<a>]
20
22
  def to_decoder = self
23
+
24
+ # @param str [String]
25
+ # @return [Decoding::Failure]
26
+ def failure(str) = Decoding::Failure.new(str)
21
27
  end
22
28
  end
@@ -10,9 +10,6 @@ module Decoding
10
10
  #
11
11
  # @see Decoding::Decoders.any
12
12
  class Any < Decoder
13
- # @private
14
- Err = Result.err("None of the decoders matched")
15
-
16
13
  # @param decoder [Decoding::Decoder<Object>]
17
14
  # @param decoders [Decoding::Decoder<Object>]
18
15
  def initialize(decoder, *decoders)
@@ -23,7 +20,8 @@ module Decoding
23
20
  # @param value [Object]
24
21
  # @return [Decoding::Result<Object>]
25
22
  def call(value)
26
- @decoders.lazy.map { _1.call(value) }.find(-> { Err }, &:ok?)
23
+ err_proc = -> { err(failure("None of the decoders matched")) }
24
+ @decoders.lazy.map { _1.call(value) }.find(err_proc, &:ok?)
27
25
  end
28
26
  end
29
27
  end
@@ -21,7 +21,7 @@ module Decoding
21
21
  if value.is_a?(::Array)
22
22
  value
23
23
  .each_with_index
24
- .map { |v, i| @decoder.call(v).map_err { |e| "error decoding array item #{i}: #{e}" } }
24
+ .map { |v, i| @decoder.call(v).map_err { _1.push(i) } }
25
25
  .then { all _1 }
26
26
  else
27
27
  err("expected an Array, got: #{value.class}")
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../decoder"
4
+
5
+ module Decoding
6
+ module Decoders
7
+ # Decode a value in deeply nested hashes.
8
+ #
9
+ # @see Decoding::Decoders.field
10
+ class At < Decoder
11
+ # @param keys [Array<Object>]
12
+ # @param decoder [Decoding::Decoder<a>]
13
+ def initialize(*keys, decoder)
14
+ @keys = keys.to_a
15
+ @decoder = decoder.to_decoder
16
+ super()
17
+ end
18
+
19
+ # @param value [Object]
20
+ # @return [Deceoding::Result<a>]
21
+ def call(value)
22
+ first, *rest = @keys.reverse
23
+ nested_decoder = rest.reduce(Decoders::Field.new(first, @decoder)) do |acc, k|
24
+ Decoders::Field.new(k, acc)
25
+ end
26
+ nested_decoder.call(value)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -21,12 +21,12 @@ module Decoding
21
21
  def call(value)
22
22
  if value.is_a?(::Hash)
23
23
  if value.key?(@key)
24
- @decoder.call(value.fetch(@key))
24
+ @decoder.call(value.fetch(@key)).map_err { _1.push(@key) }
25
25
  else
26
- err("expected a Hash with key #{@key}")
26
+ err(failure("expected a Hash with key #{@key}"))
27
27
  end
28
28
  else
29
- err("expected a Hash, got: #{value.inspect}")
29
+ err(failure("expected a Hash, got: #{value.inspect}"))
30
30
  end
31
31
  end
32
32
  end
@@ -25,11 +25,11 @@ module Decoding
25
25
  [
26
26
  @key_decoder
27
27
  .call(k)
28
- .map_err { |e| "error decoding key #{k.inspect}: #{e}" },
28
+ .map_err { |e| failure("error decoding key #{k.inspect}: #{e}") },
29
29
 
30
30
  @value_decoder
31
31
  .call(v)
32
- .map_err { |e| "error decoding value for key #{k.inspect}: #{e}" }
32
+ .map_err { |e| failure("error decoding value for key #{k.inspect}: #{e}") }
33
33
  ]
34
34
  )
35
35
  end
@@ -19,9 +19,9 @@ module Decoding
19
19
  if @pattern === value
20
20
  ok(value)
21
21
  elsif @pattern.is_a?(Class)
22
- err("expected #{@pattern}, got #{value.class}")
22
+ err(failure("expected #{@pattern}, got #{value.class}"))
23
23
  else
24
- err("expected value matching #{@pattern.inspect}, got: #{value.inspect}")
24
+ err(failure("expected value matching #{@pattern.inspect}, got: #{value.inspect}"))
25
25
  end
26
26
  end
27
27
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decoding
4
+ module Decoders
5
+ class Pass < Decoder
6
+ def call(value) = ok(value)
7
+ end
8
+ end
9
+ end
@@ -8,6 +8,8 @@ require_relative "decoders/array"
8
8
  require_relative "decoders/index"
9
9
  require_relative "decoders/hash"
10
10
  require_relative "decoders/and_then"
11
+ require_relative "decoders/at"
12
+ require_relative "decoders/pass"
11
13
  require_relative "result"
12
14
 
13
15
  module Decoding
@@ -26,6 +28,13 @@ module Decoding
26
28
  # @see Decoding::Decoders::Match
27
29
  def string = Decoders::Match.new(String)
28
30
 
31
+ # Decode any string value that matches a regular expression.
32
+ #
33
+ # @param re [Regexp, String]
34
+ # @return [Decoding::Decoder<String>]
35
+ # @see Decoding::Decoders::Match
36
+ def regexp(re) = Decoders::Match.new(Regexp.new(re))
37
+
29
38
  # Decode any integer value.
30
39
  #
31
40
  # @example
@@ -104,7 +113,14 @@ module Decoding
104
113
  # @example
105
114
  # decode(fail("oh no"), "foo") # => Decoding::Err("oh no")
106
115
  # @return [Decoding::Decoder<String>]
107
- def fail(value) = ->(_) { Result.err(value) }
116
+ def fail(value) = ->(_) { Result.err(Decoding::Failure.new(value)) }
117
+
118
+ # A decoder that returns the input value, unaltered.
119
+ #
120
+ # @example
121
+ # decode(original, [1, 2]) # => Decoding::Ok([1, 2])
122
+ # @return [Decoding::Decoder<Object>]
123
+ def original = Decoders::Pass.new
108
124
 
109
125
  # @!group Compound decoders
110
126
 
@@ -199,6 +215,27 @@ module Decoding
199
215
  # @see Decoding::Decoders::Hash
200
216
  def hash(...) = Decoders::Hash.new(...)
201
217
 
218
+ # Decode a value into a hash using multiple decoders.
219
+ #
220
+ # This is a shortcut for:
221
+ #
222
+ # deocde(map(field("id", integer), field("name", string)) { |id, name|
223
+ # { id:, name: }
224
+ # }, { "id" => 1, "name" => "John" })
225
+ # # => Decoding::Ok({ id: 1, name: "John" })
226
+ #
227
+ # @example
228
+ # decode(decode_hash(
229
+ # id: field("id", integer)
230
+ # ), { "id" => 1 })
231
+ # # => Decode::Ok({ id: 1 })
232
+ # @return Decoding::Decoder
233
+ def decode_hash(decoders)
234
+ map(*decoders.values) do |*values|
235
+ decoders.keys.zip(values).to_h
236
+ end
237
+ end
238
+
202
239
  # Create a decoder that depends on a previously decoded value.
203
240
  #
204
241
  # @example
@@ -220,5 +257,22 @@ module Decoding
220
257
  # @return [Decoding::Decoder<b>]
221
258
  # @see Decoding::Decoders::AndThen
222
259
  def and_then(...) = Decoders::AndThen.new(...)
260
+
261
+ # Decode deeply-nested fields.
262
+ #
263
+ # @example
264
+ # decoder = at('a', 'b', 'c', string)
265
+ # decode(decoder, { "a" => { "b" => { "c" => "d" } } })
266
+ # # => Decoding::Ok("d")
267
+ # decode(decoder, { "a" => { "b" => "d" } })
268
+ # # => Decoding::Err("Error at .a.b: expected a Hash, got String")
269
+ # @overload at(*fields, decoder)
270
+ # @param fields [String]
271
+ # @param decoder [Decoding::Decoder<a>]
272
+ # @return [Decoding::Decoder<a>]
273
+ # @see Decoding::Decoders::At
274
+ def at(...)
275
+ Decoders::At.new(...)
276
+ end
223
277
  end
224
278
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decoding
4
+ class Failure
5
+ protected attr_reader :msg
6
+ protected attr_reader :path
7
+
8
+ # @paramn msg [String]
9
+ def initialize(msg)
10
+ @msg = msg
11
+ @path = []
12
+ end
13
+
14
+ def eql?(other)
15
+ other.is_a?(self.class) &&
16
+ msg == other.msg &&
17
+ path == other.path
18
+ end
19
+ alias == eql?
20
+
21
+ # Add segments to the stack of errors.
22
+ #
23
+ # This is useful to create clearer error messages when using compound
24
+ # decoders, such as `array(string)`. If the `string` decoder fails with an
25
+ # error, the `array` decoder can push `3` to the stack to indicate that
26
+ # happened at index 3 in its input value.
27
+ #
28
+ # @param segment [String]
29
+ # @return [Decoding::Failure]
30
+ def push(segment)
31
+ @path << segment
32
+ self
33
+ end
34
+
35
+ def to_s
36
+ if @path.any?
37
+ "Error at .#{@path.reverse.join(".")}: #{@msg}"
38
+ else
39
+ @msg
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Decoding
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/decoding.rb CHANGED
@@ -118,5 +118,5 @@ module Decoding
118
118
  # @param decoder [Decoding::Decoder<a>]
119
119
  # @param value [Object]
120
120
  # @return [Decoding::Result<a>]
121
- def decode(decoder, value) = decoder.call(value)
121
+ def decode(decoder, value) = decoder.call(value).map_err(&:to_s)
122
122
  end
metadata CHANGED
@@ -1,16 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decoding
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arjan van der Gaag
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-06-07 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description:
14
12
  email:
15
13
  - arjan@arjanvandergaag.nl
16
14
  executables: []
@@ -32,11 +30,14 @@ files:
32
30
  - lib/decoding/decoders/and_then.rb
33
31
  - lib/decoding/decoders/any.rb
34
32
  - lib/decoding/decoders/array.rb
33
+ - lib/decoding/decoders/at.rb
35
34
  - lib/decoding/decoders/field.rb
36
35
  - lib/decoding/decoders/hash.rb
37
36
  - lib/decoding/decoders/index.rb
38
37
  - lib/decoding/decoders/map.rb
39
38
  - lib/decoding/decoders/match.rb
39
+ - lib/decoding/decoders/pass.rb
40
+ - lib/decoding/failure.rb
40
41
  - lib/decoding/result.rb
41
42
  - lib/decoding/version.rb
42
43
  - sig/decoding.rbs
@@ -48,7 +49,6 @@ metadata:
48
49
  homepage_uri: https://github.com/avdgaag/decoding
49
50
  source_code_uri: https://github.com/avdgaag/decoding
50
51
  rubygems_mfa_required: 'true'
51
- post_install_message:
52
52
  rdoc_options: []
53
53
  require_paths:
54
54
  - lib
@@ -63,8 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
63
  - !ruby/object:Gem::Version
64
64
  version: '0'
65
65
  requirements: []
66
- rubygems_version: 3.5.3
67
- signing_key:
66
+ rubygems_version: 3.6.9
68
67
  specification_version: 4
69
68
  summary: Decode dynamic values into known Ruby data structures
70
69
  test_files: []