Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 29 additions & 16 deletions gems/smithy-cbor/lib/smithy-cbor/deserializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def list(ref, values, target = nil)
values.each do |value|
next if value.nil? && !sparse?(ref.shape)

target << (value.nil? ? nil : shape(ref.shape.member, value))
target << shape(ref.shape.member, value)
end
target
end
Expand All @@ -48,43 +48,56 @@ def map(ref, values, target = nil)
values.each do |key, value|
next if value.nil? && !sparse?(ref.shape)

target[key] = value.nil? ? nil : shape(ref.shape.value, value)
target[key] = shape(ref.shape.value, value)
end
target
end

def structure(ref, values, target = nil)
return Schema::EmptyStructure.new if ref.shape == Prelude::Unit

target = ref.shape.type.new if target.nil?
ref.shape.members.each do |member_name, member_ref|
key = member_ref.member_name
next unless values.key?(key)

target[member_name] = shape(member_ref, values[key])
value = values[member_ref.member_name]
value = default(member_ref) if value.nil? && default?(member_ref.traits)
target[member_name] = shape(member_ref, value)
end
target
end

def union(ref, values, target = nil) # rubocop:disable Metrics/AbcSize
raise ArgumentError, "union value includes more than one key, received: #{values.keys}" if values.size > 1
Comment thread
mullermp marked this conversation as resolved.

key, value = values.first
return nil if key.nil?

ref.shape.members.each do |member_name, member_ref|
name = member_ref.member_name
next unless values.key?(name)
value = values[member_ref.member_name]
next if value.nil?

target = ref.shape.member_type(member_name) if target.nil?
return target.new(shape(member_ref, values[name]))
return target.new(shape(member_ref, value))
end

values.delete('__type')
Comment thread
mullermp marked this conversation as resolved.
key, value = values.first
ref.shape.member_type(:unknown).new(key, value)
end

def sparse?(shape)
shape.traits.include?('smithy.api#sparse')
end

def default?(traits)
traits.include?('smithy.api#default')
end

def default(ref)
trait = ref.traits['smithy.api#default']
case ref.shape
when BlobShape then Base64.strict_decode64(trait)
when TimestampShape
case trait
when String then Time.parse(trait)
when Integer then Time.at(trait)
else raise "unhandled timestamp format for default trait: #{trait}"
end
else trait
end
end
end
end
end
48 changes: 36 additions & 12 deletions gems/smithy-cbor/lib/smithy-cbor/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ def initialize(options = {})

def serialize(shape, data)
ref = shape.is_a?(ShapeRef) ? shape : ShapeRef.new(shape: shape)
return nil if ref.shape == Prelude::Unit
return if ref.shape == Prelude::Unit

@top_level = ref
CBOR.encode(shape(ref, data))
end

Expand All @@ -37,27 +38,30 @@ def blob(value)
end

def list(ref, values)
shape = ref.shape
values.collect do |value|
next if value.nil? && !sparse?(ref.shape)
next if value.nil? && !sparse?(shape.traits)

value.nil? ? nil : shape(ref.shape.member, value)
value.nil? ? nil : shape(shape.member, value)
end
end

def map(ref, values)
shape = ref.shape
values.each.with_object({}) do |(key, value), data|
next if value.nil? && !sparse?(ref.shape)
next if value.nil? && !sparse?(shape.traits)

data[key] = value.nil? ? nil : shape(ref.shape.value, value)
data[key] = value.nil? ? nil : shape(shape.value, value)
end
end

def structure(ref, values)
values.each_pair.with_object({}) do |(key, value), data|
if ref.shape.member?(key) && !value.nil?
member_ref = ref.shape.member(key)
data[member_ref.member_name] = shape(member_ref, value)
end
ref.shape.members.each_with_object({}) do |(member_name, member_ref), data|
value = values[member_name]
value ||= default(member_ref) if default?(ref, member_ref.traits)
next if value.nil?

data[member_ref.member_name] = shape(member_ref, value)
end
end

Expand All @@ -76,8 +80,28 @@ def union(ref, values) # rubocop:disable Metrics/AbcSize
data
end

def sparse?(shape)
shape.traits.include?('smithy.api#sparse')
def sparse?(traits)
traits.include?('smithy.api#sparse')
end

def default?(ref, traits)
return false if ref == @top_level

traits.include?('smithy.api#default') && !traits.include?('smithy.api#clientOptional')
end

def default(ref)
trait = ref.traits['smithy.api#default']
case ref.shape
when BlobShape then Base64.strict_decode64(trait)
when TimestampShape
case trait
when String then Time.parse(trait)
when Integer then Time.at(trait)
else raise ArgumentError, "Invalid default value for Timestamp: #{trait.inspect}"
end
else trait
end
end
end
end
Expand Down
18 changes: 0 additions & 18 deletions gems/smithy-cbor/spec/smithy-cbor/codec_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,31 +83,13 @@ module CBOR
expect(subject.deserialize(shape, bytes).union).to eq(nil)
end

it 'serializes an empty union' do
data = { union: {} }
bytes = subject.serialize(shape, data)
expect(subject.deserialize(shape, bytes).union).to eq(nil)
end

it 'serializes nil union values' do
data = { union: { string: nil } }
bytes = subject.serialize(shape, data)
expect(subject.deserialize(shape, bytes).to_h).to eq(data)
end

it 'deserializes unknown union members' do
unknown_union_type = shape.member(:union).shape.member_type(:unknown)
data = { union: { 'someThing' => 'someValue' } }
deserialized = subject.deserialize(shape, CBOR.encode(data))
expect(deserialized.union).to be_a(unknown_union_type)
expect(deserialized.union.to_h).to eq(unknown: { name: 'someThing', value: 'someValue' })
end

it 'raises when deserializing unions with more than one member' do
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we're removing these tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was wrong behavior when I had chatted with Kevin. I'm not really sure if we should be raising. Let me think on this.

data = { union: { string: 'string', integer: 1 } }
expect { subject.deserialize(shape, CBOR.encode(data)) }
.to raise_error(ArgumentError, /union value includes more than one key/)
end
end

context 'lists' do
Expand Down
1 change: 1 addition & 0 deletions gems/smithy-client/lib/smithy-client.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require 'base64'
require 'jmespath'

require 'smithy-cbor'
Expand Down
1 change: 0 additions & 1 deletion gems/smithy-client/lib/smithy-client/param_converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ def structure(ref, values)
if values.respond_to?(:each_pair)
values.each_pair do |k, v|
next if v.nil?

next unless ref.shape.member?(k)

values[k] = member(ref.shape.member(k), v)
Expand Down
3 changes: 2 additions & 1 deletion gems/smithy-client/lib/smithy-client/param_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ def valid_union?(ref, values, errors, context)

def validate_required_members(ref, values, errors, context)
ref.shape.members.each do |name, member_ref|
next unless member_ref.traits.include?('smithy.api#required')
traits = member_ref.traits
next unless traits.include?('smithy.api#required') && !traits.include?('smithy.api#clientOptional')

if values[name].nil?
param = "#{context}[#{name.inspect}]"
Expand Down
4 changes: 2 additions & 2 deletions gems/smithy-client/lib/smithy-client/rpc_v2_cbor/protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def stub_data(service, operation, data)
ResponseStubber.new(@options).stub_data(service, operation, data)
end

def stub_error(error_code)
ResponseStubber.new(@options).stub_error(error_code)
def stub_error(service, error_code)
ResponseStubber.new(@options).stub_error(service, error_code)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def stub_data(_service, operation, data)
resp
end

def stub_error(error_code)
def stub_error(_service, error_code)
resp = HTTP::Response.new
resp.status_code = 400
resp.headers['Smithy-Protocol'] = 'rpc-v2-cbor'
Expand Down
2 changes: 2 additions & 0 deletions gems/smithy-client/lib/smithy-client/signers/http_basic.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'base64'

Comment thread
mullermp marked this conversation as resolved.
module Smithy
module Client
module Signers
Expand Down
24 changes: 12 additions & 12 deletions gems/smithy-client/lib/smithy-client/stubbing/data_applicator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,30 @@ def apply(data, stub)

private

def shape(ref, value)
case ref.shape
when StructureShape then structure(ref.shape, value, ref.shape.type.new)
Comment thread
mullermp marked this conversation as resolved.
Outdated
when ListShape then list(ref, value)
when MapShape then map(ref, value)
else value
end
end

def list(ref, value)
value.each_with_object([]) do |v, list|
list << member(ref.shape.member, v)
list << shape(ref.shape.member, v)
end
end

def map(ref, value)
value.each_with_object({}) do |(k, v), map|
map[k.to_s] = member(ref.shape.value, v)
end
end

def member(ref, value)
case ref.shape
when StructureShape then structure(ref.shape, value, ref.shape.type.new)
when ListShape then list(ref, value)
when MapShape then map(ref, value)
else value
map[k.to_s] = shape(ref.shape.value, v)
end
end

def structure(ref, data, stub)
data.each do |key, value|
stub[key] = member(ref.shape.member(key), value)
stub[key] = shape(ref.shape.member(key), value)
end
stub
end
Expand Down
17 changes: 9 additions & 8 deletions gems/smithy-client/lib/smithy-client/stubbing/empty_stub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ def stub
private

def shape(ref, visited)
return nil if visited.include?(ref.shape)
shape = ref.shape
return nil if visited.include?(shape)

visited += [ref.shape]
visited += [shape]

case ref.shape
case shape
when ListShape then []
when MapShape then {}
when StructureShape then structure(ref, visited)
Expand All @@ -34,19 +35,19 @@ def shape(ref, visited)
end

def structure(ref, visited)
return Schema::EmptyStructure.new if ref.shape == Prelude::Unit

ref.shape.members.each_with_object(ref.shape.type.new) do |(member_name, member_ref), struct|
shape = ref.shape
shape.members.each_with_object(shape.type.new) do |(member_name, member_ref), struct|
struct[member_name] = shape(member_ref, visited)
end
end

def union(ref, visited)
member_name, member_ref = ref.shape.members.first
shape = ref.shape
member_name, member_ref = shape.members.first
return unless member_name

value = shape(member_ref, visited)
klass = ref.shape.member_type(member_name)
klass = shape.member_type(member_name)
klass.new(value)
end

Expand Down
2 changes: 1 addition & 1 deletion gems/smithy-client/lib/smithy-client/stubbing/protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def stub_data(_service, _operation, data)
resp
end

def stub_error(error_code)
def stub_error(_service, error_code)
resp = HTTP::Response.new
resp.status_code = 500
resp.body = StringIO.new(error_code.to_json)
Expand Down
2 changes: 1 addition & 1 deletion gems/smithy-client/lib/smithy-client/stubs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def convert_stub(operation_name, stub, context)
end

def service_error_stub(error_code)
{ http: @config.protocol.stub_error(error_code) }
{ http: @config.protocol.stub_error(@config.service, error_code) }
end

def http_response_stub(operation_name, data)
Expand Down
1 change: 1 addition & 0 deletions gems/smithy-client/smithy-client.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Gem::Specification.new do |spec|
spec.license = 'Apache-2.0'
spec.files = Dir['CHANGELOG.md', 'VERSION', 'lib/**/*']

spec.add_dependency('base64')
spec.add_dependency('jmespath', '~> 1', '>= 1.6.1') # necessary for secure jmespath JSON parsing

spec.add_dependency('smithy-cbor', '1.0.0.pre0')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def match_errors(error, expected_errors)
end

it 'accepts a modeled type' do
structure = sample_client.const_get(:Types).const_get(:Structure).new({})
structure = sample_client.const_get(:Types).const_get(:Structure).new
validate({ structure: structure })
end
end
Expand Down
5 changes: 4 additions & 1 deletion gems/smithy-client/spec/support/client_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ def sample_shapes
'short' => { 'target' => 'smithy.api#Short' },
'streamingBlob' => {
'target' => 'smithy.ruby.tests#StreamingBlob',
'traits' => { 'smithy.api#default' => 'streamingBlob' }
'traits' => {
'smithy.api#required' => {},
'smithy.api#clientOptional' => {}
}
},
'string' => { 'target' => 'smithy.api#String' },
'structure' => { 'target' => 'smithy.ruby.tests#Structure' },
Expand Down
Loading