smithy-cbor 1.0.0.pre1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cad452fa4f5b9fee884f71ccf900eb6e2f0a74ca6e4cd45583e9efdc618cf461
4
+ data.tar.gz: 3b62d399313458de8786f5b3148fa23cb2487967acaf5ce26bdbe291153abd28
5
+ SHA512:
6
+ metadata.gz: 4674b1fe815460d442e3e06e1b1ac9b6b8b9d07d5a21ddc5fc807efbb65dd11ce5564f10cba95c473d5c9beafbafab264507e0b9cd7c968e7ddab15e5bae6199
7
+ data.tar.gz: aa410cf7523ea7f2af0524e4f23b06a1331efd4012bc5b2b9c89973239faea89c55a7b06c4fddf590f5fba52189e7dee2058334edd96f6c1b5aaaee624a383f2
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ Unreleased Changes
2
+ ------------------
3
+
4
+ * Feature - Initial version of this gem.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0.pre1
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'deserializer'
4
+ require_relative 'serializer'
5
+
6
+ module Smithy
7
+ module CBOR
8
+ # Codec that serializes and deserializes in CBOR format.
9
+ class Codec
10
+ # @param [Hash] options
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ # @param [Shape] shape
16
+ # @param [Object] data
17
+ # @return [String, nil]
18
+ def serialize(shape, data)
19
+ Serializer.new(@options).serialize(shape, data)
20
+ end
21
+
22
+ # @param [Shape] shape
23
+ # @param [String] bytes
24
+ # @param [Object] target
25
+ # @return [Object]
26
+ def deserialize(shape, bytes, target = nil)
27
+ Deserializer.new(@options).deserialize(shape, bytes, target)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+
5
+ module Smithy
6
+ module CBOR
7
+ # @api private
8
+ class Decoder # rubocop:disable Metrics/ClassLength
9
+ FIVE_BIT_MASK = 0x1F
10
+ TAG_TYPE_EPOCH = 1
11
+ TAG_TYPE_BIGNUM = 2
12
+ TAG_TYPE_NEG_BIGNUM = 3
13
+ TAG_TYPE_BIGDEC = 4
14
+
15
+ def initialize(bytes)
16
+ @buffer = bytes
17
+ @pos = 0
18
+ end
19
+
20
+ def decode
21
+ return nil if @buffer.nil? || @buffer.empty?
22
+
23
+ val = decode_item
24
+ return val unless @pos != @buffer.size
25
+
26
+ raise ExtraBytesError.new(@pos, @buffer.size)
27
+ end
28
+
29
+ private
30
+
31
+ # high level, generic decode. Based on the next type.
32
+ # Consumes and returns the next item as a ruby object.
33
+ # rubocop:disable Metrics
34
+ def decode_item
35
+ case (next_type = peek_type)
36
+ when :array
37
+ read_array.times.map { decode_item }
38
+ when :map
39
+ read_map.times.to_h { [read_string, decode_item] }
40
+ when :indefinite_array then process_indefinite_array
41
+ when :indefinite_map then process_indefinite_map
42
+ when :indefinite_binary_string then process_indefinite_binary
43
+ when :indefinite_string then process_indefinite_string
44
+ when :tag then process_tag
45
+ when :break_stop_code then raise UnexpectedBreakCodeError
46
+ else send("read_#{next_type}")
47
+ end
48
+ end
49
+ # rubocop:enable Metrics
50
+
51
+ def peek(n_bytes)
52
+ return @buffer[@pos, n_bytes] if (@pos + n_bytes) <= @buffer.bytesize
53
+
54
+ raise OutOfBytesError.new(n_bytes, @buffer.bytesize - @pos)
55
+ end
56
+
57
+ # low level streaming interface
58
+ # rubocop:disable Metrics
59
+ def peek_type
60
+ ib = peek(1).ord
61
+ add_info = ib & FIVE_BIT_MASK
62
+ major_type = ib >> 5
63
+ case major_type
64
+ when 0, 1 then :integer
65
+ when 2
66
+ add_info == 31 ? :indefinite_binary_string : :binary_string
67
+ when 3
68
+ add_info == 31 ? :indefinite_string : :string
69
+ when 4
70
+ add_info == 31 ? :indefinite_array : :array
71
+ when 5
72
+ add_info == 31 ? :indefinite_map : :map
73
+ when 6 then :tag
74
+ when 7 then process_major_type_simple(add_info)
75
+ end
76
+ end
77
+
78
+ # simple or float
79
+ def process_major_type_simple(add_info)
80
+ case add_info
81
+ when 20, 21 then :boolean
82
+ when 22 then :nil
83
+ when 23 then :undefined # for smithy, this should be parsed as nil
84
+ when 25 then :half
85
+ when 26 then :float
86
+ when 27 then :double
87
+ when 31 then :break_stop_code
88
+ else :reserved_undefined
89
+ end
90
+ end
91
+ # rubocop:enable Metrics
92
+
93
+ def process_indefinite_array
94
+ read_start_indefinite_array
95
+ value = []
96
+ value << decode_item until peek_type == :break_stop_code
97
+ read_end_indefinite_collection
98
+ value
99
+ end
100
+
101
+ def process_indefinite_binary
102
+ read_info
103
+ value = String.new
104
+ value << read_binary_string until peek_type == :break_stop_code
105
+ read_end_indefinite_collection
106
+ value
107
+ end
108
+
109
+ def process_indefinite_map
110
+ read_start_indefinite_map
111
+ value = {}
112
+ value[read_string] = decode_item until peek_type == :break_stop_code
113
+ read_end_indefinite_collection
114
+ value
115
+ end
116
+
117
+ def process_indefinite_string
118
+ read_info
119
+ value = String.new
120
+ value << read_string until peek_type == :break_stop_code
121
+ read_end_indefinite_collection
122
+ value.force_encoding(Encoding::UTF_8)
123
+ end
124
+
125
+ def process_tag
126
+ case (tag = read_tag)
127
+ when TAG_TYPE_EPOCH
128
+ item = decode_item
129
+ Time.at(item)
130
+ when TAG_TYPE_BIGNUM, TAG_TYPE_NEG_BIGNUM
131
+ read_bignum(tag)
132
+ when TAG_TYPE_BIGDEC
133
+ read_big_decimal
134
+ else
135
+ Tagged.new(tag, decode_item)
136
+ end
137
+ end
138
+
139
+ # returns only the length of the array, caller must read the correct
140
+ # number of values after this
141
+ def read_array
142
+ _major_type, add_info = read_info
143
+ read_count(add_info)
144
+ end
145
+
146
+ # A decimal fraction or a bigfloat is represented as a tagged array
147
+ # that contains exactly two integer numbers:
148
+ # an exponent e and a mantissa m
149
+ # See: https://www.rfc-editor.org/rfc/rfc8949.html#name-decimal-fractions-and-bigfl
150
+ def read_big_decimal
151
+ unless (s = read_array) == 2
152
+ raise Error, "Expected array of length 2 but length is: #{s}"
153
+ end
154
+
155
+ e = read_integer
156
+ m = read_integer
157
+ BigDecimal(m) * (BigDecimal(10)**BigDecimal(e))
158
+ end
159
+
160
+ # tag type 2 or 3
161
+ def read_bignum(tag_value)
162
+ _major_type, add_info = read_info
163
+ bstr = take(read_count(add_info))
164
+ v = bstr.bytes.inject(0) do |sum, b|
165
+ sum <<= 8
166
+ sum + b
167
+ end
168
+ case tag_value
169
+ when 2 then v
170
+ when 3 then -1 - v
171
+ end
172
+ end
173
+
174
+ def read_boolean # rubocop:disable Naming/PredicateMethod
175
+ _major_type, add_info = read_info
176
+ case add_info
177
+ when 20 then false
178
+ when 21 then true
179
+ end
180
+ end
181
+
182
+ def read_binary_string
183
+ _major_type, add_info = read_info
184
+ take(read_count(add_info)).force_encoding(Encoding::BINARY)
185
+ end
186
+
187
+ def read_count(add_info)
188
+ case add_info
189
+ when 0..23 then add_info
190
+ when 24 then take(1).ord
191
+ when 25 then take(2).unpack1('n')
192
+ when 26 then take(4).unpack1('N')
193
+ when 27 then take(8).unpack1('Q>')
194
+ else raise UnexpectedAdditionalInformationError, add_info
195
+ end
196
+ end
197
+
198
+ def read_double
199
+ read_info
200
+ take(8).unpack1('G')
201
+ end
202
+
203
+ # returns nothing but consumes and checks the type/info.
204
+ def read_end_indefinite_collection
205
+ read_info
206
+ end
207
+
208
+ def read_float
209
+ read_info
210
+ take(4).unpack1('g')
211
+ end
212
+
213
+ # 16 bit IEEE 754 half-precision floats
214
+ # Support decoding only
215
+ # format:
216
+ # sign - 1 bit
217
+ # exponent - 5 bits
218
+ # precision - 10 bits
219
+ def read_half
220
+ read_info
221
+ b16 = take(2).unpack1('n')
222
+ exp = (b16 >> 10) & 0x1f
223
+ mant = b16 & 0x3ff
224
+ val =
225
+ case exp
226
+ when 0
227
+ Math.ldexp(mant, -24)
228
+ when 31
229
+ mant.zero? ? Float::INFINITY : Float::NAN
230
+ else
231
+ # exp bias is 15, but to use ldexp we divide
232
+ # by 1024 (2^10) to get exp-15-10
233
+ Math.ldexp(1024 + mant, exp - 25)
234
+ end
235
+ if b16[15].zero?
236
+ val
237
+ else
238
+ -val
239
+ end
240
+ end
241
+
242
+ # return a tuple of major_type, add_info
243
+ def read_info
244
+ ib = take(1).ord
245
+ [ib >> 5, ib & FIVE_BIT_MASK]
246
+ end
247
+
248
+ def read_integer
249
+ major_type, add_info = read_info
250
+
251
+ val = read_count(add_info)
252
+ case major_type
253
+ when 0 then val
254
+ when 1 then -1 - val
255
+ end
256
+ end
257
+
258
+ def read_nil
259
+ read_info
260
+ nil
261
+ end
262
+
263
+ # returns only the length of the array, caller must read the correct
264
+ # number of key value pairs after this
265
+ def read_map
266
+ _major_type, add_info = read_info
267
+ read_count(add_info)
268
+ end
269
+
270
+ # returns nothing but consumes and checks the type/info.
271
+ # Caller must keep reading until encountering the stop sequence
272
+ def read_start_indefinite_array
273
+ read_info
274
+ end
275
+
276
+ # returns nothing but consumes and checks the type/info.
277
+ # Caller must keep reading until encountering the stop sequence
278
+ def read_start_indefinite_map
279
+ read_info
280
+ end
281
+
282
+ def read_string
283
+ _major_type, add_info = read_info
284
+ take(read_count(add_info)).force_encoding(Encoding::UTF_8)
285
+ end
286
+
287
+ # returns only the tag, caller must interpret the tag and read another
288
+ # value as appropriate
289
+ def read_tag
290
+ _major_type, add_info = read_info
291
+ read_count(add_info)
292
+ end
293
+
294
+ def read_reserved_undefined
295
+ _major_type, add_info = read_info
296
+ raise Error, "Undefined reserved additional information: #{add_info}"
297
+ end
298
+
299
+ def read_undefined
300
+ read_info
301
+ :undefined
302
+ end
303
+
304
+ def take(n_bytes)
305
+ opos = @pos
306
+ @pos += n_bytes
307
+
308
+ return @buffer[opos, n_bytes] if @pos <= @buffer.bytesize
309
+
310
+ raise OutOfBytesError.new(n_bytes, @buffer.bytesize - @pos)
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Smithy
6
+ module CBOR
7
+ # @api private
8
+ class Deserializer
9
+ include Schema::Shapes
10
+
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ def deserialize(shape, bytes, target)
16
+ return {} if bytes.empty?
17
+
18
+ ref = shape.is_a?(ShapeRef) ? shape : ShapeRef.new(shape: shape)
19
+ shape(ref, CBOR.decode(bytes), target)
20
+ end
21
+
22
+ private
23
+
24
+ def shape(ref, value, target = nil)
25
+ return nil if value.nil?
26
+
27
+ case ref.shape
28
+ when ListShape then list(ref, value, target)
29
+ when MapShape then map(ref, value, target)
30
+ when StructureShape then structure(ref, value, target)
31
+ when UnionShape then union(ref, value, target)
32
+ else value
33
+ end
34
+ end
35
+
36
+ def list(ref, values, target = nil)
37
+ target = [] if target.nil?
38
+ values.each do |value|
39
+ next if value.nil? && !sparse?(ref.shape)
40
+
41
+ target << shape(ref.shape.member, value)
42
+ end
43
+ target
44
+ end
45
+
46
+ def map(ref, values, target = nil)
47
+ target = {} if target.nil?
48
+ values.each do |key, value|
49
+ next if value.nil? && !sparse?(ref.shape)
50
+
51
+ target[key] = shape(ref.shape.value, value)
52
+ end
53
+ target
54
+ end
55
+
56
+ def structure(ref, values, target = nil)
57
+ target = ref.shape.type.new if target.nil?
58
+ ref.shape.members.each do |member_name, member_ref|
59
+ value = values[member_ref.member_name]
60
+ target[member_name] = shape(member_ref, value) unless value.nil?
61
+ end
62
+ target
63
+ end
64
+
65
+ def union(ref, values, target = nil) # rubocop:disable Metrics/AbcSize
66
+ ref.shape.members.each do |member_name, member_ref|
67
+ value = values[member_ref.member_name]
68
+ next if value.nil?
69
+
70
+ target = ref.shape.member_type(member_name) if target.nil?
71
+ return target.new(member_name => shape(member_ref, value))
72
+ end
73
+
74
+ values.delete('__type')
75
+ key, value = values.first
76
+ ref.shape.member_type(:unknown).new(unknown: { key => value })
77
+ end
78
+
79
+ def sparse?(shape)
80
+ shape.traits.key?('smithy.api#sparse')
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+
5
+ module Smithy
6
+ module CBOR
7
+ # @api private
8
+ class Encoder
9
+ MAJOR_TYPE_UNSIGNED_INT = 0x00 # 000_00000 - Major Type 0 - unsigned int
10
+ MAJOR_TYPE_NEGATIVE_INT = 0x20 # 001_00000 - Major Type 1 - negative int
11
+ MAJOR_TYPE_BYTE_STR = 0x40 # 010_00000 - Major Type 2 (Byte String)
12
+ MAJOR_TYPE_STR = 0x60 # 011_00000 - Major Type 3 (Text String)
13
+ MAJOR_TYPE_ARRAY = 0x80 # 100_00000 - Major Type 4 (Array)
14
+ MAJOR_TYPE_MAP = 0xa0 # 101_00000 - Major Type 5 (Map)
15
+ MAJOR_TYPE_TAG = 0xc0 # 110_00000 - Major type 6 (Tag)
16
+ MAJOR_TYPE_SIMPLE = 0xe0 # 111_00000 - Major type 7 (111) + 5 bit 0
17
+
18
+ FLOAT_BYTES = 0xfa # 111_11010 - Major type 7 (Float) + value: 26
19
+ DOUBLE_BYTES = 0xfb # 111_ 11011 - Major type 7 (Float) + value: 26
20
+
21
+ # https://www.rfc-editor.org/rfc/rfc8949.html#tags
22
+ TAG_TYPE_EPOCH = 1
23
+ TAG_BIGNUM_BASE = 2
24
+ TAG_TYPE_BIGDEC = 4
25
+
26
+ MAX_INTEGER = 18_446_744_073_709_551_616 # 2^64
27
+
28
+ def initialize
29
+ @buffer = String.new
30
+ end
31
+
32
+ # @return the encoded bytes in CBOR format for all added data
33
+ def bytes
34
+ @buffer
35
+ end
36
+
37
+ # generic method for adding generic Ruby data based on its type
38
+ # rubocop:disable Metrics
39
+ def add(value)
40
+ case value
41
+ when BigDecimal then add_big_decimal(value)
42
+ when Integer then add_auto_integer(value)
43
+ when Numeric then add_auto_float(value)
44
+ when Symbol then add_string(value.to_s)
45
+ when true, false then add_boolean(value)
46
+ when nil then add_nil
47
+ when Tagged then process_tag(value)
48
+ when String then process_string(value)
49
+ when Array then add_array(value)
50
+ when Hash then add_hash(value)
51
+ when Time then add_time(value)
52
+ else raise UnknownTypeError, value
53
+ end
54
+ self
55
+ end
56
+ # rubocop:enable Metrics
57
+
58
+ private
59
+
60
+ def add_array(value)
61
+ start_array(value.size)
62
+ value.each { |di| add(di) }
63
+ end
64
+
65
+ def add_auto_float(value)
66
+ if value.nan?
67
+ @buffer << FLOAT_BYTES << [value].pack('g')
68
+ else
69
+ ss = [value].pack('g') # single-precision
70
+ if ss.unpack1('g') == value
71
+ @buffer << FLOAT_BYTES << ss
72
+ else
73
+ @buffer << [DOUBLE_BYTES, value].pack('CG') # double-precision
74
+ end
75
+ end
76
+ end
77
+
78
+ def add_auto_integer(value)
79
+ major_type =
80
+ if value.negative?
81
+ value = -1 - value
82
+ MAJOR_TYPE_NEGATIVE_INT
83
+ else
84
+ MAJOR_TYPE_UNSIGNED_INT
85
+ end
86
+
87
+ if value >= MAX_INTEGER
88
+ s = bignum_to_bytes(value)
89
+ head(MAJOR_TYPE_TAG, TAG_BIGNUM_BASE + (major_type >> 5))
90
+ head(MAJOR_TYPE_BYTE_STR, s.bytesize)
91
+ @buffer << s
92
+ else
93
+ head(major_type, value)
94
+ end
95
+ end
96
+
97
+ # A decimal fraction or a bigfloat is represented as a tagged array
98
+ # that contains exactly two integer numbers:
99
+ # an exponent e and a mantissa m
100
+ # decimal fractions are always represented with a base of 10
101
+ # See: https://www.rfc-editor.org/rfc/rfc8949.html#name-decimal-fractions-and-bigfl
102
+ def add_big_decimal(value)
103
+ if value.infinite? == 1
104
+ return add_float(value.infinite? * Float::INFINITY)
105
+ elsif value.nan?
106
+ return add_float(Float::NAN)
107
+ end
108
+
109
+ head(MAJOR_TYPE_TAG, TAG_TYPE_BIGDEC)
110
+ sign, digits, _base, exp = value.split
111
+ # Ruby BigDecimal digits of XXX are used as 0.XXX, convert
112
+ exp -= digits.size
113
+ digits = sign * digits.to_i
114
+ start_array(2)
115
+ add_auto_integer(exp)
116
+ add_auto_integer(digits)
117
+ end
118
+
119
+ def add_boolean(value)
120
+ value ? head(MAJOR_TYPE_SIMPLE, 21) : head(MAJOR_TYPE_SIMPLE, 20)
121
+ end
122
+
123
+ # Encoding MUST already be Encoding::BINARY
124
+ def add_byte_string(value)
125
+ head(MAJOR_TYPE_BYTE_STR, value.bytesize)
126
+ @buffer << value
127
+ end
128
+
129
+ def add_double(value)
130
+ @buffer << [DOUBLE_BYTES, value].pack('CG') # double-precision
131
+ end
132
+
133
+ def add_float(value)
134
+ @buffer << [FLOAT_BYTES, value].pack('Cg') # single-precision
135
+ end
136
+
137
+ def add_hash(value)
138
+ start_map(value.size)
139
+ value.each do |k, v|
140
+ add(k)
141
+ add(v)
142
+ end
143
+ end
144
+
145
+ def add_nil
146
+ head(MAJOR_TYPE_SIMPLE, 22)
147
+ end
148
+
149
+ def add_string(value)
150
+ value = value.encode(Encoding::UTF_8).force_encoding(Encoding::BINARY)
151
+ head(MAJOR_TYPE_STR, value.bytesize)
152
+ @buffer << value
153
+ end
154
+
155
+ def add_tag(tag)
156
+ head(MAJOR_TYPE_TAG, tag)
157
+ end
158
+
159
+ def add_time(value)
160
+ head(MAJOR_TYPE_TAG, TAG_TYPE_EPOCH)
161
+ epoch = value.to_f
162
+ add_double(epoch)
163
+ end
164
+
165
+ def bignum_to_bytes(value)
166
+ s = String.new
167
+ while value != 0
168
+ s << (value & 0xFF)
169
+ value >>= 8
170
+ end
171
+ s.reverse!
172
+ end
173
+
174
+ def head(major_type, value)
175
+ @buffer <<
176
+ case value
177
+ when 0...24
178
+ [major_type + value].pack('C') # 8-bit unsigned
179
+ when 0...256
180
+ [major_type + 24, value].pack('CC')
181
+ when 0...65_536
182
+ [major_type + 25, value].pack('Cn')
183
+ when 0...4_294_967_296
184
+ [major_type + 26, value].pack('CN')
185
+ when 0...MAX_INTEGER
186
+ [major_type + 27, value].pack('CQ>')
187
+ else
188
+ raise Error, "Value is too large to encode: #{value}"
189
+ end
190
+ end
191
+
192
+ def process_string(value)
193
+ if value.encoding == Encoding::BINARY
194
+ add_byte_string(value)
195
+ else
196
+ add_string(value)
197
+ end
198
+ end
199
+
200
+ def process_tag(value)
201
+ add_tag(value.tag)
202
+ add(value.value)
203
+ end
204
+
205
+ # caller is responsible for adding length values
206
+ def start_array(length)
207
+ head(MAJOR_TYPE_ARRAY, length)
208
+ end
209
+
210
+ # caller is responsible for adding length key/value pairs
211
+ def start_map(length)
212
+ head(MAJOR_TYPE_MAP, length)
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Smithy
6
+ module CBOR
7
+ # @api private
8
+ class Serializer
9
+ include Schema::Shapes
10
+
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ def serialize(shape, data)
16
+ ref = shape.is_a?(ShapeRef) ? shape : ShapeRef.new(shape: shape)
17
+ return if ref.shape == Prelude::Unit
18
+
19
+ CBOR.encode(shape(ref, data))
20
+ end
21
+
22
+ private
23
+
24
+ def shape(ref, value)
25
+ case ref.shape
26
+ when BlobShape then blob(value)
27
+ when ListShape then list(ref, value)
28
+ when MapShape then map(ref, value)
29
+ when StructureShape then structure(ref, value)
30
+ when UnionShape then union(ref, value)
31
+ else value
32
+ end
33
+ end
34
+
35
+ def blob(value)
36
+ value.respond_to?(:read) ? value.read : value
37
+ end
38
+
39
+ def list(ref, values)
40
+ return if values.nil?
41
+
42
+ shape = ref.shape
43
+ values.collect do |value|
44
+ shape(shape.member, value)
45
+ end
46
+ end
47
+
48
+ def map(ref, values)
49
+ return if values.nil?
50
+
51
+ shape = ref.shape
52
+ values.each.with_object({}) do |(key, value), data|
53
+ data[key] = shape(shape.value, value)
54
+ end
55
+ end
56
+
57
+ def structure(ref, values)
58
+ return if values.nil?
59
+
60
+ ref.shape.members.each_with_object({}) do |(member_name, member_ref), data|
61
+ value = values[member_name]
62
+ next if value.nil?
63
+
64
+ data[member_ref.member_name] = shape(member_ref, value)
65
+ end
66
+ end
67
+
68
+ def union(ref, values) # rubocop:disable Metrics/AbcSize
69
+ return if values.nil?
70
+
71
+ data = {}
72
+ if values.is_a?(Schema::Union)
73
+ _name, member_ref = ref.shape.member_by_type(values.class)
74
+ data[member_ref.member_name] = shape(member_ref, values.value)
75
+ else
76
+ key, value = values.first
77
+ if ref.shape.member?(key)
78
+ member_ref = ref.shape.member(key)
79
+ data[member_ref.member_name] = shape(member_ref, value)
80
+ end
81
+ end
82
+ data
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'smithy-schema'
4
+
5
+ require_relative 'smithy-cbor/codec'
6
+ require_relative 'smithy-cbor/decoder'
7
+ require_relative 'smithy-cbor/encoder'
8
+
9
+ module Smithy
10
+ # Smithy::CBOR is a purpose-built set of utilities for working with CBOR.
11
+ # It does not support all features of generic CBOR parsing and serialization.
12
+ module CBOR
13
+ VERSION = File.read(File.expand_path('../VERSION', __dir__.to_s)).strip
14
+
15
+ # CBOR Tagged data (Major type 6).
16
+ # A Tag consists of a tag number and a value.
17
+ # In the extended generic data model, a tag number's definition
18
+ # describes the additional semantics conveyed with the tag number.
19
+ class Tagged
20
+ # @param [Integer] tag The tag number.
21
+ # @param [Object] value The tag's content.
22
+ def initialize(tag, value)
23
+ @tag = tag
24
+ @value = value
25
+ end
26
+
27
+ # The tag number.
28
+ # @return [Integer]
29
+ attr_accessor :tag
30
+
31
+ # The tag's content.
32
+ # @return [Object]
33
+ attr_accessor :value
34
+ end
35
+
36
+ # Generic CBOR error, super class for specific encode/decode related errors.
37
+ class Error < StandardError; end
38
+
39
+ # Malformed buffer, expected more bytes
40
+ class OutOfBytesError < Error
41
+ def initialize(requested_bytes, left)
42
+ super("Out of bytes. Trying to read #{requested_bytes} bytes but buffer contains only #{left}")
43
+ end
44
+ end
45
+
46
+ # unknown or unsupported typed
47
+ class UnknownTypeError < Error
48
+ def initialize(type)
49
+ super("Unable to encode #{type}")
50
+ end
51
+ end
52
+
53
+ # Malformed buffer, more bytes than expected
54
+ class ExtraBytesError < Error
55
+ def initialize(pos, size)
56
+ super("Extra bytes follow after decoding item. Read #{pos} / #{size} bytes")
57
+ end
58
+ end
59
+
60
+ # Malformed buffer, unexpected break code
61
+ class UnexpectedBreakCodeError < Error; end
62
+
63
+ # malformed buffer, unexpected additional information
64
+ class UnexpectedAdditionalInformationError < Error
65
+ def initialize(add_info)
66
+ super("Unexpected additional information: #{add_info}")
67
+ end
68
+ end
69
+
70
+ class << self
71
+ # @param [nil, BigDecimal, Time, Tagged, String, Hash, Array] data
72
+ # @return [String] bytes
73
+ def encode(data)
74
+ Encoder.new.add(data).bytes
75
+ end
76
+
77
+ # @param [String] bytes
78
+ # @return [nil, BigDecimal, Time, Tagged, String, Hash, Array]
79
+ def decode(bytes)
80
+ Decoder.new(bytes.force_encoding(Encoding::BINARY)).decode
81
+ end
82
+ end
83
+ end
84
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smithy-cbor
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre1
5
+ platform: ruby
6
+ authors:
7
+ - Amazon Web Services
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-06-26 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: smithy-schema
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - '='
17
+ - !ruby/object:Gem::Version
18
+ version: 1.0.0.pre1
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - '='
24
+ - !ruby/object:Gem::Version
25
+ version: 1.0.0.pre1
26
+ description: Smithy is a code generation toolkit for creating Client and Server SDKs
27
+ from Smithy models.
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - CHANGELOG.md
33
+ - VERSION
34
+ - lib/smithy-cbor.rb
35
+ - lib/smithy-cbor/codec.rb
36
+ - lib/smithy-cbor/decoder.rb
37
+ - lib/smithy-cbor/deserializer.rb
38
+ - lib/smithy-cbor/encoder.rb
39
+ - lib/smithy-cbor/serializer.rb
40
+ homepage: https://github.com/smithy-lang/smithy-ruby
41
+ licenses:
42
+ - Apache-2.0
43
+ metadata: {}
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '3.3'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.6.2
59
+ specification_version: 4
60
+ summary: CBOR utilities and codec for Smithy generated clients and servers
61
+ test_files: []