-
Notifications
You must be signed in to change notification settings - Fork 7
Defaults #297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I'll follow this PR format to update my Document PR to stay aligned. We should chat about what you mean by going with option 1.
gems/smithy-client/lib/smithy-client/stubbing/data_applicator.rb
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice - looks good overall - I think the approach makes sense
@@ -1,5 +1,7 @@ | |||
# frozen_string_literal: true | |||
|
|||
require 'base64' | |||
require 'bigdecimal' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be adding bigdecimal as a required, general dependency? If I remember it can cause some issues for users without compilers/jruby and SDKs dont generally support it. Maybe add it as a dependency in client gems only when the model requires it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @hsbt has work to make it compatible everywhere. We can check.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@@ -33,7 +33,7 @@ def initialize(options = {}) | |||
# @return [Base] | |||
attr_accessor :client | |||
|
|||
# @return [Hash] The hash of request parameters. | |||
# @return [Hash, Struct] The request parameters as a Hash or a Struct. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume Struct here would be the runtime input shape?
This is a little confusing - I assume the reason for the union of types here is that at the start of the request we have hash params and after the parameter conversion handler runs, we have runtime shapes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, at the start of the request, we would have a hash OR struct, but eventually it will always be a struct. I'm not sure if we want to do this though - but it allows us to get values from types (like defaults) if we use the input type.
|
||
next unless ref.shape.member?(k) | ||
type = @convert_structures ? ref.shape.type.new : values |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When convert_structures is false, this should copy values right? Otherwise the input is being modified
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dup
is called by the converter prior to this function call if it's a hash.
|
||
def default? | ||
traits = @member.fetch('traits', {}) | ||
traits.key?('smithy.api#default') && !traits.key?('smithy.api#clientOptional') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gets a little confusing for shape only gems / server generation - the smithy docs say
The clientOptional trait applied to a member marked with the default trait causes non-authoritative generators to ignore the default trait.
So, in a client context, we should use it, but in an authoritative (server) type we shouldn't. I'm not sure if theres a good way to handle that in a server/client agnostic generation though and since we're targeting client generation, maybe this is good for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Good call out.. I'm not sure how else to handle this? I'll chat with Michael.
|
||
def document(default) | ||
case default | ||
when nil then 'nil' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should a check for nil default be done above and default just be skipped completely if its nil or is this a special document case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a document case.
input_shape = { | ||
'type' => 'structure', | ||
'traits' => target.fetch('traits', {}).merge({ 'smithy.api#input' => {} }), | ||
'members' => target.fetch('members', {}).merge({}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to do a deep copy of members and traits so that another weld that modifies the synthetic shape don't modify the original. I know the merge will do a shallow copy and I can't remember the details of how these are structured so this may not apply.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This was on my radar. We need a deep copy.
module Smithy | ||
module Welds | ||
# Creates synthetic input and output shapes for operations that do not have them. | ||
class SyntheticInputOutput < Weld |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't remember all of the history/details on this - but I remember with our java generator we had similar logic which we ended up removing because the logic was pushed up stream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it needs to be handled by us - the java ModelTransformer does this effectively for us, but I don't have that. The issue is that defaults only apply to shapes not input/output, but if you have synthetics, it not only helps with model evolution, but it allows you to apply defaults at the non-top-level scope which is required by protocol tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In thinking of speculative "typed document" cases - us manipulating the shape ids on our end will have a trickle effect when we receive a typed document from a service and the underlying discriminator does not match our structures within type registry (since we replaced the shape id with a new one) - unless we intend to keep both synthetic and modeled shapes.
Disregard this comment if this was handled already.
'traits' => { | ||
'smithy.api#required' => {}, | ||
'smithy.api#clientOptional' => {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we move this closer to the tests itself? Feedback: #282 (comment)
Probably could look over the sample_shapes
to see if there's any opportunity to move test-specific traits closer.
module Smithy | ||
module Welds | ||
# Creates synthetic input and output shapes for operations that do not have them. | ||
class SyntheticInputOutput < Weld |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In thinking of speculative "typed document" cases - us manipulating the shape ids on our end will have a trickle effect when we receive a typed document from a service and the underlying discriminator does not match our structures within type registry (since we replaced the shape id with a new one) - unless we intend to keep both synthetic and modeled shapes.
Disregard this comment if this was handled already.
Handle the default trait.
Currently have two approaches to consider:
There are two sub-approaches - one that overrides initialize and the other that sets a defaults instance variable for Smithy::Schema::Structure to use implicitly.
(Note: we could implement both approaches but that seems wasteful)
I chose option 2 here after going down a rabbit hole. It seems cbor protocol should not serialize top level members on the client. Because of this, types would need to know protocol specific information. Protocols have client and server implementations, making it ideal to handle it at runtime as codecs (with a configurable option regarding top level).I chose option 1 again, by using synthetic input/output. These synthetic shapes will have input/output traits, and if those traits are present, defaults generation does not apply.