From c65b1a479b9ddc39d277e1b3793a8f70082f1143 Mon Sep 17 00:00:00 2001 From: Matt Muller Date: Sat, 17 May 2025 14:30:05 -0400 Subject: [PATCH 1/6] More cleanups --- .../smithy-cbor/lib/smithy-cbor/serializer.rb | 20 +-- .../lib/smithy-client/param_converter.rb | 1 + .../smithy-json/lib/smithy-json/serializer.rb | 20 +-- .../smithy-schema/lib/smithy-schema/shapes.rb | 136 ++++++++++---- .../sig/smithy-schema/shapes.rbs | 33 ++-- .../spec/smithy-schema/shapes_spec.rb | 169 ++++++++++++------ gems/smithy/lib/smithy/model/yard.rb | 34 ++-- .../lib/smithy/templates/client/client.erb | 4 +- gems/smithy/lib/smithy/views/client/client.rb | 38 ++-- gems/smithy/lib/smithy/views/client/schema.rb | 8 +- .../lib/smithy/views/client/schema_rbs.rb | 2 +- gems/smithy/lib/smithy/views/client/types.rb | 6 +- gems/smithy/lib/smithy/welds.rb | 5 +- .../lib/smithy/welds/auth/anonymous_auth.rb | 2 +- .../smithy/welds/auth/http_api_key_auth.rb | 2 +- .../lib/smithy/welds/auth/http_basic_auth.rb | 2 +- .../lib/smithy/welds/auth/http_bearer_auth.rb | 2 +- .../lib/smithy/welds/auth/http_digest_auth.rb | 2 +- gems/smithy/lib/smithy/welds/plugins.rb | 2 +- gems/smithy/lib/smithy/welds/rpc_v2_cbor.rb | 2 +- .../default_endpoint_rules.json | 0 .../default_endpoint_tests.json | 0 .../welds/{ => transforms}/endpoints.rb | 2 +- .../synthetic_input_output.rb | 2 +- .../spec/fixtures/rename_shapes/model.json | 53 ++++++ .../spec/fixtures/rename_shapes/model.smithy | 29 +++ .../spec/interfaces/client/client_spec.rb | 2 +- .../interfaces/client/stub_responses_spec.rb | 2 +- .../spec/interfaces/rename_shapes_spec.rb | 79 ++++++++ .../synthetic_input_output_spec.rb | 18 +- gems/smithy/spec/smithy/plan_spec.rb | 2 +- projections/shapes/lib/shapes/client.rb | 2 +- projections/weather/lib/weather/client.rb | 8 +- 33 files changed, 497 insertions(+), 192 deletions(-) rename gems/smithy/lib/smithy/welds/{ => transforms}/default_endpoint_rules.json (100%) rename gems/smithy/lib/smithy/welds/{ => transforms}/default_endpoint_tests.json (100%) rename gems/smithy/lib/smithy/welds/{ => transforms}/endpoints.rb (95%) rename gems/smithy/lib/smithy/welds/{ => transforms}/synthetic_input_output.rb (96%) create mode 100644 gems/smithy/spec/fixtures/rename_shapes/model.json create mode 100644 gems/smithy/spec/fixtures/rename_shapes/model.smithy create mode 100644 gems/smithy/spec/interfaces/rename_shapes_spec.rb rename gems/smithy/spec/interfaces/{welds => }/synthetic_input_output_spec.rb (82%) diff --git a/gems/smithy-cbor/lib/smithy-cbor/serializer.rb b/gems/smithy-cbor/lib/smithy-cbor/serializer.rb index c0d09f34d..6ec9c377a 100644 --- a/gems/smithy-cbor/lib/smithy-cbor/serializer.rb +++ b/gems/smithy-cbor/lib/smithy-cbor/serializer.rb @@ -37,24 +37,26 @@ def blob(value) end def list(ref, values) + return if values.nil? + shape = ref.shape values.collect do |value| - next if value.nil? && !sparse?(shape.traits) - - value.nil? ? nil : shape(shape.member, value) + shape(shape.member, value) end end def map(ref, values) + return if values.nil? + shape = ref.shape values.each.with_object({}) do |(key, value), data| - next if value.nil? && !sparse?(shape.traits) - - data[key] = value.nil? ? nil : shape(shape.value, value) + data[key] = shape(shape.value, value) end end def structure(ref, values) + return if values.nil? + ref.shape.members.each_with_object({}) do |(member_name, member_ref), data| value = values[member_name] next if value.nil? @@ -64,6 +66,8 @@ def structure(ref, values) end def union(ref, values) # rubocop:disable Metrics/AbcSize + return if values.nil? + data = {} if values.is_a?(Schema::Union) _name, member_ref = ref.shape.member_by_type(values.class) @@ -77,10 +81,6 @@ def union(ref, values) # rubocop:disable Metrics/AbcSize end data end - - def sparse?(traits) - traits.include?('smithy.api#sparse') - end end end end diff --git a/gems/smithy-client/lib/smithy-client/param_converter.rb b/gems/smithy-client/lib/smithy-client/param_converter.rb index c2615f208..1a20dd6d9 100644 --- a/gems/smithy-client/lib/smithy-client/param_converter.rb +++ b/gems/smithy-client/lib/smithy-client/param_converter.rb @@ -246,6 +246,7 @@ def each_base_class(shape_class, &) end add(UnionShape, Hash) { |h, _| h.dup } + add(UnionShape, Schema::Union) end end end diff --git a/gems/smithy-json/lib/smithy-json/serializer.rb b/gems/smithy-json/lib/smithy-json/serializer.rb index 163add533..05fb8aae8 100644 --- a/gems/smithy-json/lib/smithy-json/serializer.rb +++ b/gems/smithy-json/lib/smithy-json/serializer.rb @@ -52,18 +52,18 @@ def float(value) def list(ref, values) return if values.nil? + shape = ref.shape values.collect do |value| - next if value.nil? && !sparse?(ref.shape) - - shape(ref.shape.member, value) + shape(shape.member, value) end end def map(ref, values) - values.each.with_object({}) do |(key, value), data| - next if value.nil? && !sparse?(ref.shape) + return if values.nil? - data[key] = shape(ref.shape.value, value) + shape = ref.shape + values.each.with_object({}) do |(key, value), data| + data[key] = shape(shape.value, value) end end @@ -87,7 +87,9 @@ def timestamp(ref, value) end end - def union(ref, values) + def union(ref, values) # rubocop:disable Metrics/AbcSize + return if values.nil? + data = {} if values.is_a?(Smithy::Schema::Union) _name, member_ref = ref.shape.member_by_type(values.class) @@ -102,10 +104,6 @@ def union(ref, values) data end - def sparse?(shape) - shape.traits.include?('smithy.api#sparse') - end - def location_name(ref) return ref.member_name unless @json_name diff --git a/gems/smithy-schema/lib/smithy-schema/shapes.rb b/gems/smithy-schema/lib/smithy-schema/shapes.rb index bbcfed330..cc66b3bbf 100644 --- a/gems/smithy-schema/lib/smithy-schema/shapes.rb +++ b/gems/smithy-schema/lib/smithy-schema/shapes.rb @@ -60,34 +60,6 @@ def []=(key, value) end end - # Represents an aggregate shape that has members. - class Structure < Shape - def initialize(options = {}) - super - @members = {} - end - - # @return [Hash] - attr_accessor :members - - # @return [ShapeRef] - def add_member(name, shape_ref) - @members[name] = shape_ref - end - - # @param [Symbol] name - # @return [Boolean] - def member?(name) - @members.key?(name) - end - - # @param [Symbol] name - # @return [ShapeRef, nil] - def member(name) - @members[name] - end - end - # Represents a slim variation of the Service shape. class ServiceShape < Shape include Enumerable @@ -170,13 +142,63 @@ class BooleanShape < Shape; end class DocumentShape < Shape; end # Represents an Enum shape. - class EnumShape < Structure; end + class EnumShape < Shape + def initialize(options = {}) + super + @members = {} + end + + # @return [Hash] + attr_accessor :members + + # @return [ShapeRef] + def add_member(name, shape_ref) + @members[name] = shape_ref + end + + # @param [Symbol] name + # @return [Boolean] + def member?(name) + @members.key?(name) + end + + # @param [Symbol] name + # @return [ShapeRef, nil] + def member(name) + @members[name] + end + end # Represents the following shapes: Byte, Short, Integer, Long, BigInteger. class IntegerShape < Shape; end # Represents an IntEnum shape. - class IntEnumShape < Structure; end + class IntEnumShape < Shape + def initialize(options = {}) + super + @members = {} + end + + # @return [Hash] + attr_accessor :members + + # @return [ShapeRef] + def add_member(name, shape_ref) + @members[name] = shape_ref + end + + # @param [Symbol] name + # @return [Boolean] + def member?(name) + @members.key?(name) + end + + # @param [Symbol] name + # @return [ShapeRef, nil] + def member(name) + @members[name] + end + end # Represents both Float and Double shapes. class FloatShape < Shape; end @@ -200,41 +222,77 @@ class MapShape < Shape class StringShape < Shape; end # Represents a Structure shape. - class StructureShape < Structure + class StructureShape < Shape def initialize(options = {}) super - @type = options[:type] + @members = {} end + # @return [Hash] + attr_accessor :members + # @return [Class] attr_accessor :type + + # @return [ShapeRef] + def add_member(name, shape_ref) + @members[name] = shape_ref + end + + # @param [Symbol] name + # @return [Boolean] + def member?(name) + @members.key?(name) + end + + # @param [Symbol] name + # @return [ShapeRef, nil] + def member(name) + @members[name] + end end # Represents a Timestamp shape. class TimestampShape < Shape; end # Represents both Union and EventStream shapes. - class UnionShape < Structure + class UnionShape < Shape def initialize(options = {}) super + @members = {} @member_types = {} @members_by_type = {} end - # @return [Class] - attr_accessor :type + # @return [Hash] + attr_accessor :members # @return [Hash] attr_reader :member_types - # @return [Hash] + # @return [Hash] attr_reader :members_by_type + # @return [Class] + attr_accessor :type + # @return [ShapeRef] def add_member(name, type, shape_ref) @member_types[name] = type @members_by_type[type] = [name, shape_ref] - super(name, shape_ref) + @members[name] = shape_ref + end + + # @param [Symbol] name + # @return [Boolean] + def member?(name) + @members.key?(name) + end + + # @param [Symbol] name + # @return [ShapeRef, nil] + def member(name) + @members[name] end # @param [Symbol] name @@ -307,9 +365,9 @@ module Prelude Timestamp = TimestampShape.new(id: 'smithy.api#Timestamp') Unit = StructureShape.new( id: 'smithy.api#Unit', - traits: { 'smithy.api#unitType' => {} }, - type: Schema::EmptyStructure + traits: { 'smithy.api#unitType' => {} } ) + Unit.type = Schema::EmptyStructure end end end diff --git a/gems/smithy-schema/sig/smithy-schema/shapes.rbs b/gems/smithy-schema/sig/smithy-schema/shapes.rbs index 1436ab126..26ea9e741 100644 --- a/gems/smithy-schema/sig/smithy-schema/shapes.rbs +++ b/gems/smithy-schema/sig/smithy-schema/shapes.rbs @@ -20,13 +20,6 @@ module Smithy def []=: (Symbol, Object) -> void end - class Structure < Shape - attr_accessor members: Hash[Symbol, ShapeRef] - def add_member: (Symbol, ShapeRef) -> ShapeRef - def member?: (Symbol?) -> bool - def member: (Symbol) -> ShapeRef? - end - class ServiceShape < Shape include Enumerable[Shapes::OperationShape] attr_accessor name: String @@ -56,13 +49,21 @@ module Smithy class DocumentShape < Shape end - class EnumShape < Structure + class EnumShape < Shape + attr_accessor members: Hash[Symbol, ShapeRef] + def add_member: (Symbol, ShapeRef) -> ShapeRef + def member?: (Symbol?) -> bool + def member: (Symbol) -> ShapeRef? end class IntegerShape < Shape end - class IntEnumShape < Structure + class IntEnumShape < Shape + attr_accessor members: Hash[Symbol, ShapeRef] + def add_member: (Symbol, ShapeRef) -> ShapeRef + def member?: (Symbol?) -> bool + def member: (Symbol) -> ShapeRef? end class FloatShape < Shape @@ -80,19 +81,25 @@ module Smithy class StringShape < Shape end - class StructureShape < Structure + class StructureShape < Shape + attr_accessor members: Hash[Symbol, ShapeRef] attr_accessor type: Class + def add_member: (Symbol, ShapeRef) -> ShapeRef + def member?: (Symbol?) -> bool + def member: (Symbol) -> ShapeRef? end class TimestampShape < Shape end - class UnionShape < Structure - attr_accessor type: Class + class UnionShape < Shape + attr_accessor members: Hash[Symbol, ShapeRef] attr_accessor member_types: Hash[Symbol, Class] attr_accessor members_by_type: Hash[Class, [Symbol, ShapeRef]] - + attr_accessor type: Class def add_member: (Symbol, Class, ShapeRef) -> ShapeRef + def member?: (Symbol?) -> bool + def member: (Symbol) -> ShapeRef? def member_type?: (Symbol) -> bool def member_type: (Symbol) -> Class? def member_by_type?: (Class) -> bool diff --git a/gems/smithy-schema/spec/smithy-schema/shapes_spec.rb b/gems/smithy-schema/spec/smithy-schema/shapes_spec.rb index 9128d4066..9527334f8 100644 --- a/gems/smithy-schema/spec/smithy-schema/shapes_spec.rb +++ b/gems/smithy-schema/spec/smithy-schema/shapes_spec.rb @@ -68,42 +68,6 @@ module Shapes end end - describe Structure do - subject { Structure.new } - - it 'is a subclass of Shape' do - expect(subject).to be_kind_of(Shape) - end - - it 'defaults members to empty hash' do - expect(subject.members).to be_empty - end - - describe '#add_member' do - it 'adds a member reference' do - shape_ref = ShapeRef.new(shape: StringShape.new) - subject.add_member(:foo, shape_ref) - expect(subject.members[:foo]).to be_kind_of(ShapeRef) - end - end - - describe '#member?' do - it 'returns true if member exists' do - shape_ref = ShapeRef.new(shape: StringShape.new) - subject.add_member(:foo, shape_ref) - expect(subject.member?(:foo)).to be(true) - end - end - - describe '#member' do - it 'returns the member' do - shape_ref = ShapeRef.new(shape: StringShape.new) - subject.add_member(:foo, shape_ref) - expect(subject.member(:foo)).to be_kind_of(ShapeRef) - end - end - end - describe ServiceShape do subject { ServiceShape.new } @@ -265,8 +229,36 @@ module Shapes describe EnumShape do subject { EnumShape.new } - it 'is a subclass of Structure' do - expect(subject).to be_kind_of(Structure) + it 'is a subclass of Shape' do + expect(subject).to be_kind_of(Shape) + end + + it 'defaults members to empty hash' do + expect(subject.members).to be_empty + end + + describe '#add_member' do + it 'adds a member reference' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.members[:foo]).to be_kind_of(ShapeRef) + end + end + + describe '#member?' do + it 'returns true if member exists' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.member?(:foo)).to be(true) + end + end + + describe '#member' do + it 'returns the member' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.member(:foo)).to be_kind_of(ShapeRef) + end end end @@ -281,8 +273,36 @@ module Shapes describe IntEnumShape do subject { IntEnumShape.new } - it 'is a subclass of Structure' do - expect(subject).to be_kind_of(Structure) + it 'is a subclass of Shape' do + expect(subject).to be_kind_of(Shape) + end + + it 'defaults members to empty hash' do + expect(subject.members).to be_empty + end + + describe '#add_member' do + it 'adds a member reference' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.members[:foo]).to be_kind_of(ShapeRef) + end + end + + describe '#member?' do + it 'returns true if member exists' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.member?(:foo)).to be(true) + end + end + + describe '#member' do + it 'returns the member' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.member(:foo)).to be_kind_of(ShapeRef) + end end end @@ -361,8 +381,12 @@ module Shapes describe StructureShape do subject { StructureShape.new } - it 'is a subclass of Structure' do - expect(subject).to be_kind_of(Structure) + it 'is a subclass of Shape' do + expect(subject).to be_kind_of(Shape) + end + + it 'defaults members to empty hash' do + expect(subject.members).to be_empty end describe '#type accessor' do @@ -371,6 +395,30 @@ module Shapes expect(subject.type).to be(Class) end end + + describe '#add_member' do + it 'adds a member reference' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.members[:foo]).to be_kind_of(ShapeRef) + end + end + + describe '#member?' do + it 'returns true if member exists' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.member?(:foo)).to be(true) + end + end + + describe '#member' do + it 'returns the member' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, shape_ref) + expect(subject.member(:foo)).to be_kind_of(ShapeRef) + end + end end describe TimestampShape do @@ -386,18 +434,20 @@ module Shapes let(:union_type) { Class } - it 'is a subclass of Structure' do - expect(subject).to be_kind_of(Structure) + it 'is a subclass of Shape' do + expect(subject).to be_kind_of(Shape) end - describe '#initialize' do - it 'defaults member_types to empty hash' do - expect(subject.member_types).to be_empty - end + it 'defaults members to empty hash' do + expect(subject.members).to be_empty + end - it 'defaults members_by_type to empty hash' do - expect(subject.members_by_type).to be_empty - end + it 'defaults member_types to empty hash' do + expect(subject.member_types).to be_empty + end + + it 'defaults members_by_type to empty hash' do + expect(subject.members_by_type).to be_empty end describe '#type accessor' do @@ -411,11 +461,28 @@ module Shapes it 'adds a member with its type' do shape_ref = ShapeRef.new(shape: StringShape.new) subject.add_member(:foo, union_type, shape_ref) + expect(subject.members[:foo]).to be(shape_ref) expect(subject.member_types[:foo]).to be(union_type) expect(subject.members_by_type[union_type]).to eq([:foo, shape_ref]) end end + describe '#member?' do + it 'returns true if member exists' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, union_type, shape_ref) + expect(subject.member?(:foo)).to be(true) + end + end + + describe '#member' do + it 'returns the member' do + shape_ref = ShapeRef.new(shape: StringShape.new) + subject.add_member(:foo, union_type, shape_ref) + expect(subject.member(:foo)).to be_kind_of(ShapeRef) + end + end + describe '#member_type?' do it 'returns true if member type exists' do shape_ref = ShapeRef.new(shape: StringShape.new) diff --git a/gems/smithy/lib/smithy/model/yard.rb b/gems/smithy/lib/smithy/model/yard.rb index bc8910173..902000e75 100644 --- a/gems/smithy/lib/smithy/model/yard.rb +++ b/gems/smithy/lib/smithy/model/yard.rb @@ -6,7 +6,7 @@ module Model class YARD class << self # rubocop:disable Metrics/CyclomaticComplexity - def type(model, id, shape) + def type(service, model, id, shape) case shape['type'] when 'blob', 'string', 'enum' then 'String' when 'boolean' then 'Boolean' @@ -14,39 +14,33 @@ def type(model, id, shape) when 'float', 'double' then 'Float' when 'timestamp' then 'Time' when 'document' then 'JSON' - when 'list' - list_type(model, shape) - when 'map' - map_type(model, shape) - when 'structure', 'union' - structure_type(id) - else - 'Object' + when 'list' then list_type(service, model, shape) + when 'map' then map_type(service, model, shape) + when 'structure', 'union' then structure_type(service, id) + else 'Object' end end # rubocop:enable Metrics/CyclomaticComplexity private - def structure_type(id) - if id == 'smithy.api#Unit' - 'Smithy::Schema::EmptyStructure' - else - "Types::#{Model::Shape.name(id)}" - end + def structure_type(service, id) + return 'Smithy::Schema::EmptyStructure' if id == 'smithy.api#Unit' + + "Types::#{(service.dig('rename', id) || Model::Shape.name(id)).camelize}" end - def map_type(model, shape) + def map_type(service, model, shape) key_target = Model.shape(model, shape['key']['target']) value_target = Model.shape(model, shape['value']['target']) - key_type = type(model, shape['key']['target'], key_target) - value_type = type(model, shape['value']['target'], value_target) + key_type = type(service, model, shape['key']['target'], key_target) + value_type = type(service, model, shape['value']['target'], value_target) "Hash<#{key_type}, #{value_type}>" end - def list_type(model, shape) + def list_type(service, model, shape) member_target = Model.shape(model, shape['member']['target']) - "Array<#{type(model, shape['member']['target'], member_target)}>" + "Array<#{type(service, model, shape['member']['target'], member_target)}>" end end end diff --git a/gems/smithy/lib/smithy/templates/client/client.erb b/gems/smithy/lib/smithy/templates/client/client.erb index b23ef216b..f76920bb9 100644 --- a/gems/smithy/lib/smithy/templates/client/client.erb +++ b/gems/smithy/lib/smithy/templates/client/client.erb @@ -30,8 +30,8 @@ module <%= module_name %> <% operation.docstrings.each do |docstring| -%> # <%= docstring %> <% end -%> - def <%= operation.name %>(params = {}, options = {}) - input = build_input(:<%= operation.name %>, params) + def <%= operation.method_name %>(params = {}, options = {}) + input = build_input(:<%= operation.method_name %>, params) input.send_request(options) end diff --git a/gems/smithy/lib/smithy/views/client/client.rb b/gems/smithy/lib/smithy/views/client/client.rb index 9d3722e0b..3ab005748 100644 --- a/gems/smithy/lib/smithy/views/client/client.rb +++ b/gems/smithy/lib/smithy/views/client/client.rb @@ -8,6 +8,7 @@ class Client < View def initialize(plan, code_generated_plugins) @plan = plan @model = plan.model + _, @service = plan.service.first @plugins = PluginList.new(plan, code_generated_plugins) super() end @@ -50,7 +51,7 @@ def operations Model::ServiceIndex .new(@model) .operations_for(@plan.service) - .map { |id, operation| Operation.new(@model, id, operation) } + .map { |id, operation| Operation.new(@service, @model, id, operation) } end def gem_name @@ -95,7 +96,8 @@ def option_default(option) # @api private class Operation - def initialize(model, id, operation) + def initialize(service, model, id, operation) + @service = service @model = model @id = id @operation = operation @@ -106,12 +108,12 @@ def docstrings lines.concat(documentation) lines.concat(params_docstrings) lines.concat(return_docstring) - lines.concat(OperationExamples.new(@model, name, @operation).docstrings) - lines.concat(RequestResponseExample.new(@model, name, @operation).docstrings) + lines.concat(OperationExamples.new(@model, method_name, @operation).docstrings) + lines.concat(RequestResponseExample.new(@model, method_name, @operation).docstrings) lines end - def name + def method_name Model::Shape.name(@id).underscore end @@ -124,14 +126,16 @@ def documentation .split("\n") end - def params_docstrings - lines = ['@param [Hash] params'] + def params_docstrings # rubocop:disable Metrics/AbcSize input = Model.shape(@model, @operation['input']['target']) - input['members'].each do |name, member| - target = Model.shape(@model, member['target']) - type = Model::YARD.type(@model, member['target'], target) - lines << "@option params [#{type}] :#{name.underscore}" - param_docstring(member).each do |docstring| + input_type = Model::YARD.type(@service, @model, @operation['input']['target'], input) + + lines = ["@param [Hash, #{input_type}] params"] + input['members'].each do |member_name, member_shape| + member = Model.shape(@model, member_shape['target']) + member_type = Model::YARD.type(@service, @model, member_shape['target'], member) + lines << "@option params [#{member_type}] :#{member_name.underscore}" + param_docstring(member_shape).each do |docstring| lines << " #{docstring}" end end @@ -141,16 +145,16 @@ def params_docstrings def return_docstring lines = [] output = Model.shape(@model, @operation['output']['target']) - lines << "@return [#{Model::YARD.type(@model, @operation['output']['target'], output)}]" + lines << "@return [#{Model::YARD.type(@service, @model, @operation['output']['target'], output)}]" lines end - def param_docstring(member) - documentation = member.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") + def param_docstring(member_shape) + documentation = member_shape.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") return documentation unless documentation.empty? - target = Model.shape(@model, member['target']) - target.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") + member = Model.shape(@model, member_shape['target']) + member.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") end end end diff --git a/gems/smithy/lib/smithy/views/client/schema.rb b/gems/smithy/lib/smithy/views/client/schema.rb index a3e795068..bb5eb336a 100644 --- a/gems/smithy/lib/smithy/views/client/schema.rb +++ b/gems/smithy/lib/smithy/views/client/schema.rb @@ -161,7 +161,7 @@ def initialize(service, id, shape) attr_reader :type def name - @service.dig('rename', @id) || Model::Shape.name(@id).camelize + (@service.dig('rename', @id) || Model::Shape.name(@id)).camelize end def initializer @@ -180,7 +180,7 @@ def initialize(service, id, shape) attr_reader :members def type_class - "Types::#{@service.dig('rename', @id) || Model::Shape.name(@id).camelize}" + "Types::#{(@service.dig('rename', @id) || Model::Shape.name(@id)).camelize}" end def http_payload? @@ -261,7 +261,7 @@ def initialize(service, id, shape) attr_reader :members def type_class - "Types::#{@service.dig('rename', @id) || Model::Shape.name(@id).camelize}" + "Types::#{(@service.dig('rename', @id) || Model::Shape.name(@id)).camelize}" end def union_type(shape_ref) @@ -319,7 +319,7 @@ def initialize(service, member_name, shape_ref) def target(id) return PRELUDE_SHAPES_MAP[id] if PRELUDE_SHAPES_MAP.key?(id) - @service.dig('rename', id) || Model::Shape.name(id).camelize + (@service.dig('rename', id) || Model::Shape.name(id)).camelize end def initializer diff --git a/gems/smithy/lib/smithy/views/client/schema_rbs.rb b/gems/smithy/lib/smithy/views/client/schema_rbs.rb index ef8d9502f..4cf44b8aa 100644 --- a/gems/smithy/lib/smithy/views/client/schema_rbs.rb +++ b/gems/smithy/lib/smithy/views/client/schema_rbs.rb @@ -71,7 +71,7 @@ def initialize(service, id, shape) end def name - @service.dig('rename', @id) || Model::Shape.name(@id).camelize + (@service.dig('rename', @id) || Model::Shape.name(@id)).camelize end def shape_class diff --git a/gems/smithy/lib/smithy/views/client/types.rb b/gems/smithy/lib/smithy/views/client/types.rb index eebffb74c..53a8bc71f 100644 --- a/gems/smithy/lib/smithy/views/client/types.rb +++ b/gems/smithy/lib/smithy/views/client/types.rb @@ -30,7 +30,7 @@ def initialize(service, model, id, shape) @shape = shape @type = shape['type'] @name = service.fetch('rename', {})[id] || Model::Shape.name(id).camelize - @members = shape['members'].map { |name, member| Member.new(model, name, member) } + @members = shape['members'].map { |name, member| Member.new(service, model, name, member) } end attr_reader :type, :name, :members @@ -61,12 +61,12 @@ def attribute_docstrings # @api private class Member - def initialize(model, name, member) + def initialize(service, model, name, member) @name = name @member = member @member_traits = member.fetch('traits', {}) @target = Model.shape(model, member['target']) - @doc_type = Model::YARD.type(model, member['target'], @target) + @doc_type = Model::YARD.type(service, model, member['target'], @target) end attr_reader :name diff --git a/gems/smithy/lib/smithy/welds.rb b/gems/smithy/lib/smithy/welds.rb index 481d44874..f21bd9d24 100644 --- a/gems/smithy/lib/smithy/welds.rb +++ b/gems/smithy/lib/smithy/welds.rb @@ -6,10 +6,11 @@ require_relative 'welds/auth/http_bearer_auth' require_relative 'welds/auth/http_digest_auth' -require_relative 'welds/endpoints' +require_relative 'welds/transforms/endpoints' +require_relative 'welds/transforms/synthetic_input_output' + require_relative 'welds/plugins' require_relative 'welds/rpc_v2_cbor' -require_relative 'welds/synthetic_input_output' # require_relative 'welds/rubocop' module Smithy diff --git a/gems/smithy/lib/smithy/welds/auth/anonymous_auth.rb b/gems/smithy/lib/smithy/welds/auth/anonymous_auth.rb index 8e42bab79..9553d65c3 100644 --- a/gems/smithy/lib/smithy/welds/auth/anonymous_auth.rb +++ b/gems/smithy/lib/smithy/welds/auth/anonymous_auth.rb @@ -7,7 +7,7 @@ module Welds # Adds default Anonymous (optional) auth. class AnonymousAuth < Weld def for?(_service) - say_status :insert, 'Adding the AnonymousAuth plugin', @plan.quiet + say_status :insert, 'Adding the AnonymousAuth plugin', :yellow unless @plan.quiet true end diff --git a/gems/smithy/lib/smithy/welds/auth/http_api_key_auth.rb b/gems/smithy/lib/smithy/welds/auth/http_api_key_auth.rb index fbd89a0cd..8a6ee40c7 100644 --- a/gems/smithy/lib/smithy/welds/auth/http_api_key_auth.rb +++ b/gems/smithy/lib/smithy/welds/auth/http_api_key_auth.rb @@ -10,7 +10,7 @@ def for?(service) _id, service = service.first return false unless service.fetch('traits', {}).include?('smithy.api#httpApiKeyAuth') - say_status :insert, 'Adding the HttpApiKeyAuth plugin', @plan.quiet + say_status :insert, 'Adding the HttpApiKeyAuth plugin', :yellow unless @plan.quiet true end diff --git a/gems/smithy/lib/smithy/welds/auth/http_basic_auth.rb b/gems/smithy/lib/smithy/welds/auth/http_basic_auth.rb index b14e436c6..afcb3b060 100644 --- a/gems/smithy/lib/smithy/welds/auth/http_basic_auth.rb +++ b/gems/smithy/lib/smithy/welds/auth/http_basic_auth.rb @@ -10,7 +10,7 @@ def for?(service) _id, service = service.first return false unless service.fetch('traits', {}).include?('smithy.api#httpBasicAuth') - say_status :insert, 'Adding the HttpBasicAuth plugin', @plan.quiet + say_status :insert, 'Adding the HttpBasicAuth plugin', :yellow unless @plan.quiet true end diff --git a/gems/smithy/lib/smithy/welds/auth/http_bearer_auth.rb b/gems/smithy/lib/smithy/welds/auth/http_bearer_auth.rb index 5b6445374..de1a0413d 100644 --- a/gems/smithy/lib/smithy/welds/auth/http_bearer_auth.rb +++ b/gems/smithy/lib/smithy/welds/auth/http_bearer_auth.rb @@ -10,7 +10,7 @@ def for?(service) _id, service = service.first return false unless service.fetch('traits', {}).include?('smithy.api#httpBearerAuth') - say_status :insert, 'Adding the HttpBearerAuth plugin', @plan.quiet + say_status :insert, 'Adding the HttpBearerAuth plugin', :yellow unless @plan.quiet true end diff --git a/gems/smithy/lib/smithy/welds/auth/http_digest_auth.rb b/gems/smithy/lib/smithy/welds/auth/http_digest_auth.rb index 8600dc214..c639087f4 100644 --- a/gems/smithy/lib/smithy/welds/auth/http_digest_auth.rb +++ b/gems/smithy/lib/smithy/welds/auth/http_digest_auth.rb @@ -10,7 +10,7 @@ def for?(service) _id, service = service.first return false unless service.fetch('traits', {}).include?('smithy.api#httpDigestAuth') - say_status :insert, 'Adding the HttpDigestAuth plugin', @plan.quiet + say_status :insert, 'Adding the HttpDigestAuth plugin', :yellow unless @plan.quiet true end diff --git a/gems/smithy/lib/smithy/welds/plugins.rb b/gems/smithy/lib/smithy/welds/plugins.rb index a9b693270..70ab3800f 100644 --- a/gems/smithy/lib/smithy/welds/plugins.rb +++ b/gems/smithy/lib/smithy/welds/plugins.rb @@ -22,7 +22,7 @@ module Welds # Provides default plugins. class Plugins < Weld def for?(_service) - say_status :insert, 'Adding default plugins', @plan.quiet + say_status :insert, 'Adding default plugins', :yellow unless @plan.quiet true end diff --git a/gems/smithy/lib/smithy/welds/rpc_v2_cbor.rb b/gems/smithy/lib/smithy/welds/rpc_v2_cbor.rb index 0c46ab5c1..fecbaa2b4 100644 --- a/gems/smithy/lib/smithy/welds/rpc_v2_cbor.rb +++ b/gems/smithy/lib/smithy/welds/rpc_v2_cbor.rb @@ -8,7 +8,7 @@ def for?(service) _, service = service.first return false unless service.fetch('traits', {}).include?('smithy.protocols#rpcv2Cbor') - say_status :insert, 'Adding the RPCv2 CBOR protocol', @plan.quiet + say_status :insert, 'Adding the RPCv2 CBOR protocol', :yellow unless @plan.quiet true end diff --git a/gems/smithy/lib/smithy/welds/default_endpoint_rules.json b/gems/smithy/lib/smithy/welds/transforms/default_endpoint_rules.json similarity index 100% rename from gems/smithy/lib/smithy/welds/default_endpoint_rules.json rename to gems/smithy/lib/smithy/welds/transforms/default_endpoint_rules.json diff --git a/gems/smithy/lib/smithy/welds/default_endpoint_tests.json b/gems/smithy/lib/smithy/welds/transforms/default_endpoint_tests.json similarity index 100% rename from gems/smithy/lib/smithy/welds/default_endpoint_tests.json rename to gems/smithy/lib/smithy/welds/transforms/default_endpoint_tests.json diff --git a/gems/smithy/lib/smithy/welds/endpoints.rb b/gems/smithy/lib/smithy/welds/transforms/endpoints.rb similarity index 95% rename from gems/smithy/lib/smithy/welds/endpoints.rb rename to gems/smithy/lib/smithy/welds/transforms/endpoints.rb index 2a006b8ab..d9100234b 100644 --- a/gems/smithy/lib/smithy/welds/endpoints.rb +++ b/gems/smithy/lib/smithy/welds/transforms/endpoints.rb @@ -8,7 +8,7 @@ def pre_process(model) id, service = model['shapes'].select { |_k, s| s['type'] == 'service' }.first return if service['traits'] && service['traits']['smithy.rules#endpointRuleSet'] - say_status :insert, "Adding default endpoint rules to #{id}", @plan.quiet + say_status :insert, "Adding default endpoint rules to service #{id}", :yellow unless @plan.quiet add_default_endpoints(service) end diff --git a/gems/smithy/lib/smithy/welds/synthetic_input_output.rb b/gems/smithy/lib/smithy/welds/transforms/synthetic_input_output.rb similarity index 96% rename from gems/smithy/lib/smithy/welds/synthetic_input_output.rb rename to gems/smithy/lib/smithy/welds/transforms/synthetic_input_output.rb index 41786ad80..5c051fcca 100644 --- a/gems/smithy/lib/smithy/welds/synthetic_input_output.rb +++ b/gems/smithy/lib/smithy/welds/transforms/synthetic_input_output.rb @@ -7,7 +7,7 @@ module Welds # Creates synthetic input and output shapes for operations that do not have them. class SyntheticInputOutput < Weld def pre_process(model) - say_status :insert, 'Creating synthetic input and output shapes', @plan.quiet + say_status :modify, 'Creating synthetic input and output shapes', :yellow unless @plan.quiet create_synthetic_input_output_shapes(model) end diff --git a/gems/smithy/spec/fixtures/rename_shapes/model.json b/gems/smithy/spec/fixtures/rename_shapes/model.json new file mode 100644 index 000000000..e262f0b7b --- /dev/null +++ b/gems/smithy/spec/fixtures/rename_shapes/model.json @@ -0,0 +1,53 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.ruby.tests#Operation": { + "type": "operation", + "input": { + "target": "smithy.ruby.tests#OperationInput" + }, + "output": { + "target": "smithy.ruby.tests#OperationOutput" + } + }, + "smithy.ruby.tests#OperationInput": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#input": {} + } + }, + "smithy.ruby.tests#OperationOutput": { + "type": "structure", + "members": { + "structure": { + "target": "smithy.ruby.tests#Structure" + } + }, + "traits": { + "smithy.api#output": {} + } + }, + "smithy.ruby.tests#RenameShapes": { + "type": "service", + "operations": [ + { + "target": "smithy.ruby.tests#Operation" + } + ], + "rename": { + "smithy.ruby.tests#OperationInput": "RenamedOperationInput", + "smithy.ruby.tests#OperationOutput": "RenamedOperationOutput", + "smithy.ruby.tests#Structure": "RenamedStructure" + } + }, + "smithy.ruby.tests#Structure": { + "type": "structure", + "members": { + "nested": { + "target": "smithy.ruby.tests#Structure" + } + } + } + } +} diff --git a/gems/smithy/spec/fixtures/rename_shapes/model.smithy b/gems/smithy/spec/fixtures/rename_shapes/model.smithy new file mode 100644 index 000000000..5879c4dc6 --- /dev/null +++ b/gems/smithy/spec/fixtures/rename_shapes/model.smithy @@ -0,0 +1,29 @@ +$version: "2" + +namespace smithy.ruby.tests + +service RenameShapes { + operations: [Operation] + rename: { + "smithy.ruby.tests#OperationInput": "RenamedOperationInput", + "smithy.ruby.tests#OperationOutput": "RenamedOperationOutput", + "smithy.ruby.tests#Structure": "RenamedStructure" + } +} + +operation Operation { + input: OperationInput + output: OperationOutput +} + +@input +structure OperationInput {} + +@output +structure OperationOutput { + structure: Structure +} + +structure Structure { + nested: Structure +} diff --git a/gems/smithy/spec/interfaces/client/client_spec.rb b/gems/smithy/spec/interfaces/client/client_spec.rb index 69de29d43..7fa880383 100644 --- a/gems/smithy/spec/interfaces/client/client_spec.rb +++ b/gems/smithy/spec/interfaces/client/client_spec.rb @@ -52,7 +52,7 @@ it 'generates operation and param documentation' do expected = <<~DOC Operation documentation - @param [Hash] params + @param [Hash, Types::OperationInput] params @option params [String] :baz Member documentation @option params [String] :bar diff --git a/gems/smithy/spec/interfaces/client/stub_responses_spec.rb b/gems/smithy/spec/interfaces/client/stub_responses_spec.rb index 487d69bbe..ec8af6184 100644 --- a/gems/smithy/spec/interfaces/client/stub_responses_spec.rb +++ b/gems/smithy/spec/interfaces/client/stub_responses_spec.rb @@ -50,7 +50,7 @@ it 'can return default stubbed data' do stub = subject.stub_data(:operation) - expect(stub.to_h).to include(default_stub_data) + expect(stub.to_h).to eq(default_stub_data) end it 'can set stubbed data mixed with defaults' do diff --git a/gems/smithy/spec/interfaces/rename_shapes_spec.rb b/gems/smithy/spec/interfaces/rename_shapes_spec.rb new file mode 100644 index 000000000..4e7d145d9 --- /dev/null +++ b/gems/smithy/spec/interfaces/rename_shapes_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require_relative '../spec_helper' + +context 'Renamed Shapes' do + ['generated client gem', + 'generated schema gem', + 'generated client from source code', + 'generated schema from source code'].each do |context| + context context do + include_context context, 'RenameShapes' + + it 'renames type classes' do + expect(defined?(RenameShapes::Types::RenamedStructure)).to_not be nil + expect(defined?(RenameShapes::Types::Structure)).to be nil + end + + it 'renames schema classes' do + expect(defined?(RenameShapes::Schema::RenamedStructure)).to_not be nil + expect(defined?(RenameShapes::Schema::Structure)).to be nil + end + + it 'preserves the original shape id' do + expect(RenameShapes::Schema::RenamedStructure.id).to eq('smithy.ruby.tests#Structure') + end + + it 'renames type references in the schema' do + expect(RenameShapes::Schema::RenamedStructure.type).to eq(RenameShapes::Types::RenamedStructure) + end + end + end + + ['generated client gem', 'generated client from source code'].each do |context| + context context do + include_context context, 'RenameShapes' + + it 'assigns renamed shapes to operation inputs and outputs' do + client = RenameShapes::Client.new + operation = client.config.service.operation(:operation) + expect(operation.input.shape.type).to eq(RenameShapes::Types::RenamedOperationInput) + expect(operation.output.shape.type).to eq(RenameShapes::Types::RenamedOperationOutput) + end + end + end + + context 'generated client gem' do + include_context 'generated client gem', 'RenameShapes' + + it 'includes renamed shapes in the types documentation' do + types_file = File.join(@plan.destination_root, 'lib', 'rename_shapes', 'types.rb') + expected = <<~DOC + @return [Types::RenamedStructure] + DOC + expect(expected).to be_in_documentation(types_file, 'RenameShapes::Types::RenamedStructure') + end + + it 'includes renamed shapes in the operation documentation' do + client_file = File.join(@plan.destination_root, 'lib', 'rename_shapes', 'client.rb') + expected = <<~DOC + @param [Hash, Types::RenamedOperationInput] params + @return [Types::RenamedOperationOutput] + DOC + expect(expected).to be_in_documentation(client_file, 'RenameShapes::Client', 'operation') + end + end + + context 'generated schema gem' do + include_context 'generated schema gem', 'RenameShapes' + + it 'includes renamed shapes in the types documentation' do + types_file = File.join(@plan.destination_root, 'lib', 'rename_shapes-schema', 'types.rb') + expected = <<~DOC + @!attribute nested + @return [Types::RenamedStructure] + DOC + expect(expected).to be_in_documentation(types_file, 'RenameShapes::Types::RenamedStructure') + end + end +end diff --git a/gems/smithy/spec/interfaces/welds/synthetic_input_output_spec.rb b/gems/smithy/spec/interfaces/synthetic_input_output_spec.rb similarity index 82% rename from gems/smithy/spec/interfaces/welds/synthetic_input_output_spec.rb rename to gems/smithy/spec/interfaces/synthetic_input_output_spec.rb index 4e0d17ef5..b0c05e630 100644 --- a/gems/smithy/spec/interfaces/welds/synthetic_input_output_spec.rb +++ b/gems/smithy/spec/interfaces/synthetic_input_output_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative '../../spec_helper' +require_relative '../spec_helper' -describe 'Welds: Synthetic Input and Output' do +context 'Synthetic Input and Output' do ['generated client gem', 'generated schema gem', 'generated client from source code', @@ -60,4 +60,18 @@ end end end + + context 'generated client gem' do + include_context 'generated client gem', 'SyntheticInputOutput' + + it 'includes synthetic shapes in the operation documentation' do + client_file = File.join(@plan.destination_root, 'lib', 'synthetic_input_output', 'client.rb') + expected = <<~DOC + @param [Hash, Types::OperationInput] params + @option params [String] :member + @return [Types::OperationOutput] + DOC + expect(expected).to be_in_documentation(client_file, 'SyntheticInputOutput::Client', 'operation') + end + end end diff --git a/gems/smithy/spec/smithy/plan_spec.rb b/gems/smithy/spec/smithy/plan_spec.rb index ccdf168e4..dc26bc4e8 100644 --- a/gems/smithy/spec/smithy/plan_spec.rb +++ b/gems/smithy/spec/smithy/plan_spec.rb @@ -7,7 +7,7 @@ module Smithy let(:fixture) { File.expand_path('../fixtures/weather/model.json', __dir__.to_s) } let(:model) { JSON.load_file(fixture) } let(:type) { :client } - let(:options) { { gem_version: '0.1.0' } } + let(:options) { { gem_version: '0.1.0', quiet: true } } subject { described_class.new(model, type, options) } diff --git a/projections/shapes/lib/shapes/client.rb b/projections/shapes/lib/shapes/client.rb index a6c83ca52..86edbf587 100644 --- a/projections/shapes/lib/shapes/client.rb +++ b/projections/shapes/lib/shapes/client.rb @@ -167,7 +167,7 @@ def initialize(*options) super end - # @param [Hash] params + # @param [Hash, Types::OperationInput] params # @option params [String] :blob # @option params [Boolean] :boolean # @option params [String] :string diff --git a/projections/weather/lib/weather/client.rb b/projections/weather/lib/weather/client.rb index df2e6bdea..0fabe06aa 100644 --- a/projections/weather/lib/weather/client.rb +++ b/projections/weather/lib/weather/client.rb @@ -167,7 +167,7 @@ def initialize(*options) super end - # @param [Hash] params + # @param [Hash, Types::GetCityInput] params # @option params [String] :city_id # @return [Types::GetCityOutput] # @example Request syntax with placeholder values @@ -190,7 +190,7 @@ def get_city(params = {}, options = {}) input.send_request(options) end - # @param [Hash] params + # @param [Hash, Smithy::Schema::EmptyStructure] params # @return [Types::GetCurrentTimeOutput] # @example Request syntax with placeholder values # params = {} @@ -206,7 +206,7 @@ def get_current_time(params = {}, options = {}) input.send_request(options) end - # @param [Hash] params + # @param [Hash, Types::GetForecastInput] params # @option params [String] :city_id # @return [Types::GetForecastOutput] # @example Request syntax with placeholder values @@ -225,7 +225,7 @@ def get_forecast(params = {}, options = {}) input.send_request(options) end - # @param [Hash] params + # @param [Hash, Types::ListCitiesInput] params # @option params [String] :next_token # @option params [Integer] :page_size # @return [Types::ListCitiesOutput] From 758aafe1cf772420507010d1953fe7a73fc20fa9 Mon Sep 17 00:00:00 2001 From: Matt Muller Date: Sat, 17 May 2025 14:51:11 -0400 Subject: [PATCH 2/6] Do not include shapes module anymore --- .../lib/smithy/templates/client/schema.erb | 2 +- gems/smithy/lib/smithy/views/client/schema.rb | 4 +- projections/shapes/lib/shapes/schema.rb | 50 +++++++++---------- projections/weather/lib/weather/schema.rb | 48 +++++++++--------- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/gems/smithy/lib/smithy/templates/client/schema.erb b/gems/smithy/lib/smithy/templates/client/schema.erb index eb76089d2..addc3b818 100644 --- a/gems/smithy/lib/smithy/templates/client/schema.erb +++ b/gems/smithy/lib/smithy/templates/client/schema.erb @@ -5,7 +5,6 @@ module <%= module_name %> # This module contains a schema composed of shapes used by the client. module Schema - include Smithy::Schema::Shapes <% shapes.each do |shape| -%> <%= shape.name %> = <%= shape.initializer %> @@ -62,6 +61,7 @@ module <%= module_name %> <% end -%> end) <% end -%> + end end end diff --git a/gems/smithy/lib/smithy/views/client/schema.rb b/gems/smithy/lib/smithy/views/client/schema.rb index bb5eb336a..97e91822e 100644 --- a/gems/smithy/lib/smithy/views/client/schema.rb +++ b/gems/smithy/lib/smithy/views/client/schema.rb @@ -166,7 +166,7 @@ def name def initializer traits_str = ", traits: #{@traits}" unless @traits.empty? - "#{SHAPE_CLASS_MAP[@type]}.new(id: '#{@id}'#{traits_str})" + "Smithy::Schema::Shapes::#{SHAPE_CLASS_MAP[@type]}.new(id: '#{@id}'#{traits_str})" end end @@ -317,7 +317,7 @@ def initialize(service, member_name, shape_ref) attr_reader :name def target(id) - return PRELUDE_SHAPES_MAP[id] if PRELUDE_SHAPES_MAP.key?(id) + return "Smithy::Schema::Shapes::#{PRELUDE_SHAPES_MAP[id]}" if PRELUDE_SHAPES_MAP.key?(id) (@service.dig('rename', id) || Model::Shape.name(id)).camelize end diff --git a/projections/shapes/lib/shapes/schema.rb b/projections/shapes/lib/shapes/schema.rb index ab92b3e7d..010dafd10 100644 --- a/projections/shapes/lib/shapes/schema.rb +++ b/projections/shapes/lib/shapes/schema.rb @@ -5,32 +5,31 @@ module ShapeService # This module contains a schema composed of shapes used by the client. module Schema - include Smithy::Schema::Shapes - BigDecimal = BigDecimalShape.new(id: 'smithy.ruby.tests#BigDecimal', traits: {"smithy.ruby.tests#shape" => {}}) - BigInteger = IntegerShape.new(id: 'smithy.ruby.tests#BigInteger', traits: {"smithy.ruby.tests#shape" => {}}) - Blob = BlobShape.new(id: 'smithy.ruby.tests#Blob', traits: {"smithy.ruby.tests#shape" => {}}) - Boolean = BooleanShape.new(id: 'smithy.ruby.tests#Boolean', traits: {"smithy.ruby.tests#shape" => {}}) - Byte = IntegerShape.new(id: 'smithy.ruby.tests#Byte', traits: {"smithy.ruby.tests#shape" => {}}) - Document = DocumentShape.new(id: 'smithy.ruby.tests#Document', traits: {"smithy.ruby.tests#shape" => {}}) - Double = FloatShape.new(id: 'smithy.ruby.tests#Double', traits: {"smithy.ruby.tests#shape" => {}}) - Enum = EnumShape.new(id: 'smithy.ruby.tests#Enum', traits: {"smithy.ruby.tests#shape" => {}}) - Float = FloatShape.new(id: 'smithy.ruby.tests#Float', traits: {"smithy.ruby.tests#shape" => {}}) - IntEnum = IntEnumShape.new(id: 'smithy.ruby.tests#IntEnum', traits: {"smithy.ruby.tests#shape" => {}}) - Integer = IntegerShape.new(id: 'smithy.ruby.tests#Integer', traits: {"smithy.ruby.tests#shape" => {}}) - List = ListShape.new(id: 'smithy.ruby.tests#List', traits: {"smithy.ruby.tests#shape" => {}}) - Long = IntegerShape.new(id: 'smithy.ruby.tests#Long', traits: {"smithy.ruby.tests#shape" => {}}) - Map = MapShape.new(id: 'smithy.ruby.tests#Map', traits: {"smithy.ruby.tests#shape" => {}}) - OperationInput = StructureShape.new(id: 'smithy.ruby.tests#OperationInput', traits: {"smithy.api#input" => {}}) - OperationOutput = StructureShape.new(id: 'smithy.ruby.tests#OperationOutput', traits: {"smithy.api#output" => {}}) - Short = IntegerShape.new(id: 'smithy.ruby.tests#Short', traits: {"smithy.ruby.tests#shape" => {}}) - String = StringShape.new(id: 'smithy.ruby.tests#String', traits: {"smithy.ruby.tests#shape" => {}}) - Structure = StructureShape.new(id: 'smithy.ruby.tests#Structure', traits: {"smithy.ruby.tests#shape" => {}}) - Timestamp = TimestampShape.new(id: 'smithy.ruby.tests#Timestamp', traits: {"smithy.ruby.tests#shape" => {}}) - Union = UnionShape.new(id: 'smithy.ruby.tests#Union', traits: {"smithy.ruby.tests#shape" => {}}) + BigDecimal = Smithy::Schema::Shapes::BigDecimalShape.new(id: 'smithy.ruby.tests#BigDecimal', traits: {"smithy.ruby.tests#shape" => {}}) + BigInteger = Smithy::Schema::Shapes::IntegerShape.new(id: 'smithy.ruby.tests#BigInteger', traits: {"smithy.ruby.tests#shape" => {}}) + Blob = Smithy::Schema::Shapes::BlobShape.new(id: 'smithy.ruby.tests#Blob', traits: {"smithy.ruby.tests#shape" => {}}) + Boolean = Smithy::Schema::Shapes::BooleanShape.new(id: 'smithy.ruby.tests#Boolean', traits: {"smithy.ruby.tests#shape" => {}}) + Byte = Smithy::Schema::Shapes::IntegerShape.new(id: 'smithy.ruby.tests#Byte', traits: {"smithy.ruby.tests#shape" => {}}) + Document = Smithy::Schema::Shapes::DocumentShape.new(id: 'smithy.ruby.tests#Document', traits: {"smithy.ruby.tests#shape" => {}}) + Double = Smithy::Schema::Shapes::FloatShape.new(id: 'smithy.ruby.tests#Double', traits: {"smithy.ruby.tests#shape" => {}}) + Enum = Smithy::Schema::Shapes::EnumShape.new(id: 'smithy.ruby.tests#Enum', traits: {"smithy.ruby.tests#shape" => {}}) + Float = Smithy::Schema::Shapes::FloatShape.new(id: 'smithy.ruby.tests#Float', traits: {"smithy.ruby.tests#shape" => {}}) + IntEnum = Smithy::Schema::Shapes::IntEnumShape.new(id: 'smithy.ruby.tests#IntEnum', traits: {"smithy.ruby.tests#shape" => {}}) + Integer = Smithy::Schema::Shapes::IntegerShape.new(id: 'smithy.ruby.tests#Integer', traits: {"smithy.ruby.tests#shape" => {}}) + List = Smithy::Schema::Shapes::ListShape.new(id: 'smithy.ruby.tests#List', traits: {"smithy.ruby.tests#shape" => {}}) + Long = Smithy::Schema::Shapes::IntegerShape.new(id: 'smithy.ruby.tests#Long', traits: {"smithy.ruby.tests#shape" => {}}) + Map = Smithy::Schema::Shapes::MapShape.new(id: 'smithy.ruby.tests#Map', traits: {"smithy.ruby.tests#shape" => {}}) + OperationInput = Smithy::Schema::Shapes::StructureShape.new(id: 'smithy.ruby.tests#OperationInput', traits: {"smithy.api#input" => {}}) + OperationOutput = Smithy::Schema::Shapes::StructureShape.new(id: 'smithy.ruby.tests#OperationOutput', traits: {"smithy.api#output" => {}}) + Short = Smithy::Schema::Shapes::IntegerShape.new(id: 'smithy.ruby.tests#Short', traits: {"smithy.ruby.tests#shape" => {}}) + String = Smithy::Schema::Shapes::StringShape.new(id: 'smithy.ruby.tests#String', traits: {"smithy.ruby.tests#shape" => {}}) + Structure = Smithy::Schema::Shapes::StructureShape.new(id: 'smithy.ruby.tests#Structure', traits: {"smithy.ruby.tests#shape" => {}}) + Timestamp = Smithy::Schema::Shapes::TimestampShape.new(id: 'smithy.ruby.tests#Timestamp', traits: {"smithy.ruby.tests#shape" => {}}) + Union = Smithy::Schema::Shapes::UnionShape.new(id: 'smithy.ruby.tests#Union', traits: {"smithy.ruby.tests#shape" => {}}) - Enum.add_member(:foo, ShapeRef.new(shape: Prelude::Unit, member_name: 'FOO', traits: {"smithy.api#enumValue" => "bar"})) - IntEnum.add_member(:baz, ShapeRef.new(shape: Prelude::Unit, member_name: 'BAZ', traits: {"smithy.api#enumValue" => 1})) + Enum.add_member(:foo, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'FOO', traits: {"smithy.api#enumValue" => "bar"})) + IntEnum.add_member(:baz, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'BAZ', traits: {"smithy.api#enumValue" => 1})) List.member = ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) Map.key = ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) Map.value = ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) @@ -78,7 +77,7 @@ module Schema Structure.type = Types::Structure Union.add_member(:string, Types::Union::String, ShapeRef.new(shape: String, member_name: 'string', traits: {"smithy.ruby.tests#shape" => {}})) Union.add_member(:structure, Types::Union::Structure, ShapeRef.new(shape: Structure, member_name: 'structure', traits: {"smithy.ruby.tests#shape" => {}})) - Union.add_member(:unit, Types::Union::Unit, ShapeRef.new(shape: Prelude::Unit, member_name: 'unit', traits: {"smithy.ruby.tests#shape" => {}})) + Union.add_member(:unit, Types::Union::Unit, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'unit', traits: {"smithy.ruby.tests#shape" => {}})) Union.add_member(:unknown, Types::Union::Unknown, ShapeRef.new(shape: Prelude::Unit)) Union.type = Types::Union @@ -94,6 +93,7 @@ module Schema operation.output = ShapeRef.new(shape: OperationOutput) operation.traits = {"smithy.ruby.tests#shape" => {}} end) + end end end diff --git a/projections/weather/lib/weather/schema.rb b/projections/weather/lib/weather/schema.rb index 1e2fce736..09800884c 100644 --- a/projections/weather/lib/weather/schema.rb +++ b/projections/weather/lib/weather/schema.rb @@ -5,46 +5,45 @@ module Weather # This module contains a schema composed of shapes used by the client. module Schema - include Smithy::Schema::Shapes - CityCoordinates = StructureShape.new(id: 'example.weather#CityCoordinates') - CityId = StringShape.new(id: 'example.weather#CityId', traits: {"smithy.api#pattern" => "^[A-Za-z0-9 ]+$"}) - CitySummaries = ListShape.new(id: 'example.weather#CitySummaries') - CitySummary = StructureShape.new(id: 'example.weather#CitySummary', traits: {"smithy.api#references" => [{"resource" => "example.weather#City"}]}) - GetCityInput = StructureShape.new(id: 'example.weather#GetCityInput', traits: {"smithy.api#input" => {}}) - GetCityOutput = StructureShape.new(id: 'example.weather#GetCityOutput', traits: {"smithy.api#output" => {}}) - GetCurrentTimeOutput = StructureShape.new(id: 'example.weather#GetCurrentTimeOutput', traits: {"smithy.api#output" => {}}) - GetForecastInput = StructureShape.new(id: 'example.weather#GetForecastInput', traits: {"smithy.api#input" => {}}) - GetForecastOutput = StructureShape.new(id: 'example.weather#GetForecastOutput', traits: {"smithy.api#output" => {}}) - ListCitiesInput = StructureShape.new(id: 'example.weather#ListCitiesInput', traits: {"smithy.api#input" => {}}) - ListCitiesOutput = StructureShape.new(id: 'example.weather#ListCitiesOutput', traits: {"smithy.api#output" => {}}) - NoSuchResource = StructureShape.new(id: 'example.weather#NoSuchResource', traits: {"smithy.api#error" => "client"}) + CityCoordinates = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#CityCoordinates') + CityId = Smithy::Schema::Shapes::StringShape.new(id: 'example.weather#CityId', traits: {"smithy.api#pattern" => "^[A-Za-z0-9 ]+$"}) + CitySummaries = Smithy::Schema::Shapes::ListShape.new(id: 'example.weather#CitySummaries') + CitySummary = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#CitySummary', traits: {"smithy.api#references" => [{"resource" => "example.weather#City"}]}) + GetCityInput = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#GetCityInput', traits: {"smithy.api#input" => {}}) + GetCityOutput = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#GetCityOutput', traits: {"smithy.api#output" => {}}) + GetCurrentTimeOutput = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#GetCurrentTimeOutput', traits: {"smithy.api#output" => {}}) + GetForecastInput = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#GetForecastInput', traits: {"smithy.api#input" => {}}) + GetForecastOutput = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#GetForecastOutput', traits: {"smithy.api#output" => {}}) + ListCitiesInput = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#ListCitiesInput', traits: {"smithy.api#input" => {}}) + ListCitiesOutput = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#ListCitiesOutput', traits: {"smithy.api#output" => {}}) + NoSuchResource = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#NoSuchResource', traits: {"smithy.api#error" => "client"}) - CityCoordinates.add_member(:latitude, ShapeRef.new(shape: Prelude::Float, member_name: 'latitude', traits: {"smithy.api#required" => {}})) - CityCoordinates.add_member(:longitude, ShapeRef.new(shape: Prelude::Float, member_name: 'longitude', traits: {"smithy.api#required" => {}})) + CityCoordinates.add_member(:latitude, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'latitude', traits: {"smithy.api#required" => {}})) + CityCoordinates.add_member(:longitude, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'longitude', traits: {"smithy.api#required" => {}})) CityCoordinates.type = Types::CityCoordinates CitySummaries.member = ShapeRef.new(shape: CitySummary) CitySummary.add_member(:city_id, ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) - CitySummary.add_member(:name, ShapeRef.new(shape: Prelude::String, member_name: 'name', traits: {"smithy.api#required" => {}})) + CitySummary.add_member(:name, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'name', traits: {"smithy.api#required" => {}})) CitySummary.type = Types::CitySummary GetCityInput.add_member(:city_id, ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) GetCityInput.type = Types::GetCityInput - GetCityOutput.add_member(:name, ShapeRef.new(shape: Prelude::String, member_name: 'name', traits: {"smithy.api#notProperty" => {}, "smithy.api#required" => {}})) + GetCityOutput.add_member(:name, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'name', traits: {"smithy.api#notProperty" => {}, "smithy.api#required" => {}})) GetCityOutput.add_member(:coordinates, ShapeRef.new(shape: CityCoordinates, member_name: 'coordinates', traits: {"smithy.api#required" => {}})) GetCityOutput.type = Types::GetCityOutput - GetCurrentTimeOutput.add_member(:time, ShapeRef.new(shape: Prelude::Timestamp, member_name: 'time', traits: {"smithy.api#required" => {}})) + GetCurrentTimeOutput.add_member(:time, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Timestamp, member_name: 'time', traits: {"smithy.api#required" => {}})) GetCurrentTimeOutput.type = Types::GetCurrentTimeOutput GetForecastInput.add_member(:city_id, ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) GetForecastInput.type = Types::GetForecastInput - GetForecastOutput.add_member(:chance_of_rain, ShapeRef.new(shape: Prelude::Float, member_name: 'chanceOfRain')) + GetForecastOutput.add_member(:chance_of_rain, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'chanceOfRain')) GetForecastOutput.type = Types::GetForecastOutput - ListCitiesInput.add_member(:next_token, ShapeRef.new(shape: Prelude::String, member_name: 'nextToken')) - ListCitiesInput.add_member(:page_size, ShapeRef.new(shape: Prelude::Integer, member_name: 'pageSize')) + ListCitiesInput.add_member(:next_token, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'nextToken')) + ListCitiesInput.add_member(:page_size, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Integer, member_name: 'pageSize')) ListCitiesInput.type = Types::ListCitiesInput - ListCitiesOutput.add_member(:next_token, ShapeRef.new(shape: Prelude::String, member_name: 'nextToken')) + ListCitiesOutput.add_member(:next_token, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'nextToken')) ListCitiesOutput.add_member(:items, ShapeRef.new(shape: CitySummaries, member_name: 'items', traits: {"smithy.api#required" => {}})) ListCitiesOutput.type = Types::ListCitiesOutput - NoSuchResource.add_member(:resource_type, ShapeRef.new(shape: Prelude::String, member_name: 'resourceType', traits: {"smithy.api#required" => {}})) + NoSuchResource.add_member(:resource_type, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'resourceType', traits: {"smithy.api#required" => {}})) NoSuchResource.type = Types::NoSuchResource Weather = ServiceShape.new do |service| @@ -63,7 +62,7 @@ module Schema service.add_operation(:get_current_time, OperationShape.new do |operation| operation.id = "example.weather#GetCurrentTime" operation.name = "GetCurrentTime" - operation.input = ShapeRef.new(shape: Prelude::Unit) + operation.input = ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit) operation.output = ShapeRef.new(shape: GetCurrentTimeOutput) operation.traits = {"smithy.api#readonly" => {}} end) @@ -82,6 +81,7 @@ module Schema operation.traits = {"smithy.api#readonly" => {}} operation[:paginator] = Paginators::ListCities.new end) + end end end From a6752ebc2247e3d54c9b427ffeedd6da52b66c1e Mon Sep 17 00:00:00 2001 From: Matt Muller Date: Sat, 17 May 2025 14:54:30 -0400 Subject: [PATCH 3/6] Fix more references --- gems/smithy/lib/smithy/templates/client/schema.erb | 6 +++--- gems/smithy/lib/smithy/views/client/schema.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gems/smithy/lib/smithy/templates/client/schema.erb b/gems/smithy/lib/smithy/templates/client/schema.erb index addc3b818..ce6da0a63 100644 --- a/gems/smithy/lib/smithy/templates/client/schema.erb +++ b/gems/smithy/lib/smithy/templates/client/schema.erb @@ -34,12 +34,12 @@ module <%= module_name %> <% shape.members.each do |member| -%> <%= shape.name %>.add_member(:<%= member.name %>, <%= shape.union_type(member) %>, <%= member.initializer %>) <% end -%> - <%= shape.name %>.add_member(:unknown, Types::<%= shape.name %>::Unknown, ShapeRef.new(shape: Prelude::Unit)) + <%= shape.name %>.add_member(:unknown, Types::<%= shape.name %>::Unknown, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit)) <%= shape.name %>.type = <%= shape.type_class %> <% end -%> <% end -%> - <%= service_shape.name %> = ServiceShape.new do |service| + <%= service_shape.name %> = Smithy::Schema::Shapes::ServiceShape.new do |service| service.id = "<%= service_shape.id %>" service.name = "<%= service_shape.name %>" <% if service_shape.version -%> @@ -47,7 +47,7 @@ module <%= module_name %> <% end -%> service.traits = <%= service_shape.traits %> <% operation_shapes.each do |shape| -%> - service.add_operation(:<%= shape.name.underscore %>, OperationShape.new do |operation| + service.add_operation(:<%= shape.name.underscore %>, Smithy::Schema::Shapes::OperationShape.new do |operation| operation.id = "<%= shape.id %>" operation.name = "<%= shape.name %>" operation.input = <%= shape.input.initializer %> diff --git a/gems/smithy/lib/smithy/views/client/schema.rb b/gems/smithy/lib/smithy/views/client/schema.rb index 97e91822e..4d1b4a487 100644 --- a/gems/smithy/lib/smithy/views/client/schema.rb +++ b/gems/smithy/lib/smithy/views/client/schema.rb @@ -325,7 +325,7 @@ def target(id) def initializer traits_str = ", traits: #{@traits}" unless @traits.empty? member_name_str = ", member_name: '#{@member_name}'" if @member_name - "ShapeRef.new(shape: #{@target}#{member_name_str}#{traits_str})" + "Smithy::Schema::Shapes::ShapeRef.new(shape: #{@target}#{member_name_str}#{traits_str})" end def http_payload? From 18e0c340a36a1cb2f86f8f9750521bdaff7d551c Mon Sep 17 00:00:00 2001 From: Matt Muller Date: Sat, 17 May 2025 17:31:16 -0400 Subject: [PATCH 4/6] Handle the documentation traits --- gems/smithy/lib/smithy/model/yard.rb | 38 ++++++ gems/smithy/lib/smithy/views/client/client.rb | 48 +++++--- gems/smithy/lib/smithy/views/client/types.rb | 108 +++++++++++++++--- .../spec/fixtures/documentation/model.json | 85 ++++++++++++++ .../spec/fixtures/documentation/model.smithy | 39 +++++++ .../fixtures/documentation_trait/model.json | 51 --------- .../fixtures/documentation_trait/model.smithy | 26 ----- .../spec/interfaces/client/client_spec.rb | 50 +++++++- .../spec/interfaces/client/types_spec.rb | 56 ++++++++- projections/shapes/lib/shapes/schema.rb | 104 ++++++++--------- projections/weather/lib/weather/schema.rb | 60 +++++----- 11 files changed, 466 insertions(+), 199 deletions(-) create mode 100644 gems/smithy/spec/fixtures/documentation/model.json create mode 100644 gems/smithy/spec/fixtures/documentation/model.smithy delete mode 100644 gems/smithy/spec/fixtures/documentation_trait/model.json delete mode 100644 gems/smithy/spec/fixtures/documentation_trait/model.smithy diff --git a/gems/smithy/lib/smithy/model/yard.rb b/gems/smithy/lib/smithy/model/yard.rb index 902000e75..ec778dd23 100644 --- a/gems/smithy/lib/smithy/model/yard.rb +++ b/gems/smithy/lib/smithy/model/yard.rb @@ -5,6 +5,40 @@ module Model # @api private class YARD class << self + def deprecated_docstrings(message, since) + lines = ['@deprecated'] + lines << " #{escape(message)}" unless message.empty? + lines << " Since: #{escape(since)}" unless since.empty? + lines + end + + def external_documentation_docstrings(hash) + hash.map { |key, value| "@see #{escape(value)} #{escape(key)}" } + end + + def param_docstring(service, model, id, shape) + "@param [Hash, #{type(service, model, id, shape)}] params" + end + + def recommended_docstrings(reason) + lines = ['@note'] + lines << ' This shape is recommended' + lines << " Reason: #{escape(reason)}" unless reason.empty? + lines + end + + def return_docstring(service, model, id, shape) + "@return [#{type(service, model, id, shape)}]" + end + + def sensitive_docstring + '@note This shape contains sensitive data and should be treated as such.' + end + + def since_docstring(since) + "@since #{escape(since)}" + end + # rubocop:disable Metrics/CyclomaticComplexity def type(service, model, id, shape) case shape['type'] @@ -42,6 +76,10 @@ def list_type(service, model, shape) member_target = Model.shape(model, shape['member']['target']) "Array<#{type(service, model, shape['member']['target'], member_target)}>" end + + def escape(string) + string.split("\n").join(' ') + end end end end diff --git a/gems/smithy/lib/smithy/views/client/client.rb b/gems/smithy/lib/smithy/views/client/client.rb index 3ab005748..e93a5f4e1 100644 --- a/gems/smithy/lib/smithy/views/client/client.rb +++ b/gems/smithy/lib/smithy/views/client/client.rb @@ -101,15 +101,19 @@ def initialize(service, model, id, operation) @model = model @id = id @operation = operation + @traits = operation.fetch('traits', {}) end - def docstrings + def docstrings # rubocop:disable Metrics/AbcSize lines = [] - lines.concat(documentation) + lines.concat(documentation_docstrings) lines.concat(params_docstrings) - lines.concat(return_docstring) + lines.concat(return_docstrings) lines.concat(OperationExamples.new(@model, method_name, @operation).docstrings) lines.concat(RequestResponseExample.new(@model, method_name, @operation).docstrings) + lines.concat(deprecated_docstrings) + lines.concat(external_documentation_docstrings) + lines.concat(since_docstrings) lines end @@ -119,18 +123,36 @@ def method_name private - def documentation - @operation - .fetch('traits', {}) - .fetch('smithy.api#documentation', '') - .split("\n") + def deprecated_docstrings + return [] unless @traits.key?('smithy.api#deprecated') + + message = @traits['smithy.api#deprecated'].fetch('message', '') + since = @traits['smithy.api#deprecated'].fetch('since', '') + Model::YARD.deprecated_docstrings(message, since) + end + + def documentation_docstrings + @traits.fetch('smithy.api#documentation', '').split("\n") + end + + def external_documentation_docstrings + return [] unless @traits.key?('smithy.api#externalDocumentation') + + hash = @traits.fetch('smithy.api#externalDocumentation', {}) + Model::YARD.external_documentation_docstrings(hash) + end + + def since_docstrings + return [] unless @traits.key?('smithy.api#since') + + [Model::YARD.since_docstring(@traits['smithy.api#since'])] end def params_docstrings # rubocop:disable Metrics/AbcSize input = Model.shape(@model, @operation['input']['target']) - input_type = Model::YARD.type(@service, @model, @operation['input']['target'], input) - lines = ["@param [Hash, #{input_type}] params"] + lines = [] + lines << Model::YARD.param_docstring(@service, @model, @operation['input']['target'], input) input['members'].each do |member_name, member_shape| member = Model.shape(@model, member_shape['target']) member_type = Model::YARD.type(@service, @model, member_shape['target'], member) @@ -142,11 +164,9 @@ def params_docstrings # rubocop:disable Metrics/AbcSize lines end - def return_docstring - lines = [] + def return_docstrings output = Model.shape(@model, @operation['output']['target']) - lines << "@return [#{Model::YARD.type(@service, @model, @operation['output']['target'], output)}]" - lines + [Model::YARD.return_docstring(@service, @model, @operation['output']['target'], output)] end def param_docstring(member_shape) diff --git a/gems/smithy/lib/smithy/views/client/types.rb b/gems/smithy/lib/smithy/views/client/types.rb index 53a8bc71f..b738562a4 100644 --- a/gems/smithy/lib/smithy/views/client/types.rb +++ b/gems/smithy/lib/smithy/views/client/types.rb @@ -29,14 +29,15 @@ def initialize(service, model, id, shape) _, service = service.first @shape = shape @type = shape['type'] - @name = service.fetch('rename', {})[id] || Model::Shape.name(id).camelize + @name = (service.dig('rename', id) || Model::Shape.name(id)).camelize @members = shape['members'].map { |name, member| Member.new(service, model, name, member) } + @traits = shape.fetch('traits', {}) end attr_reader :type, :name, :members def input? - @shape.fetch('traits', {}).key?('smithy.api#input') + @traits.key?('smithy.api#input') end def defaults @@ -44,47 +45,120 @@ def defaults end def docstrings - @shape - .fetch('traits', {}) - .fetch('smithy.api#documentation', '') - .split("\n") + lines = [] + lines.concat(documentation_docstrings) + lines.concat(deprecated_docstrings) + lines.concat(external_documentation_docstrings) + lines.concat(sensitive_docstrings) + lines.concat(since_docstrings) + lines end def attribute_docstrings lines = [] members.each do |member| - lines.concat(member.attribute_docstrings) + lines.concat(member.docstrings) end lines end + + def deprecated_docstrings + return [] unless @traits.key?('smithy.api#deprecated') + + message = @traits['smithy.api#deprecated'].fetch('message', '') + since = @traits['smithy.api#deprecated'].fetch('since', '') + Model::YARD.deprecated_docstrings(message, since) + end + + def documentation_docstrings + @traits.fetch('smithy.api#documentation', '').split("\n") + end + + def external_documentation_docstrings + return [] unless @traits.key?('smithy.api#externalDocumentation') + + hash = @traits.fetch('smithy.api#externalDocumentation', {}) + Model::YARD.external_documentation_docstrings(hash) + end + + def sensitive_docstrings + return [] unless @traits.key?('smithy.api#sensitive') + + [Model::YARD.sensitive_docstring] + end + + def since_docstrings + return [] unless @traits.key?('smithy.api#since') + + [Model::YARD.since_docstring(@traits['smithy.api#since'])] + end end # @api private class Member def initialize(service, model, name, member) + @service = service + @model = model @name = name @member = member - @member_traits = member.fetch('traits', {}) + @traits = member.fetch('traits', {}) @target = Model.shape(model, member['target']) - @doc_type = Model::YARD.type(service, model, member['target'], @target) end attr_reader :name - def docstrings + def indented_docstrings(docstrings) + docstrings.map { |docstring| " #{docstring}" } + end + + def docstrings # rubocop:disable Metrics/AbcSize + lines = ["@!attribute #{@name.underscore}"] + lines.concat(indented_docstrings(documentation_docstrings)) + lines.concat(indented_docstrings(deprecated_docstrings)) + lines.concat(indented_docstrings(external_documentation_docstrings)) + lines.concat(indented_docstrings(recommended_docstrings)) + lines.concat(indented_docstrings(since_docstrings)) + lines.concat(indented_docstrings(return_docstrings)) + lines + end + + def deprecated_docstrings + return [] unless @traits.key?('smithy.api#deprecated') + + message = @traits['smithy.api#deprecated'].fetch('message', '') + since = @traits['smithy.api#deprecated'].fetch('since', '') + Model::YARD.deprecated_docstrings(message, since) + end + + def documentation_docstrings lines = @member.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") return lines unless lines.empty? @target.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") end - def attribute_docstrings - lines = ["@!attribute #{@name.underscore}"] - docstrings.each do |docstring| - lines << " #{docstring}" - end - lines << " @return [#{@doc_type}]" - lines + def external_documentation_docstrings + return [] unless @traits.key?('smithy.api#externalDocumentation') + + hash = @traits.fetch('smithy.api#externalDocumentation', {}) + Model::YARD.external_documentation_docstrings(hash) + end + + def recommended_docstrings + return [] unless @traits.key?('smithy.api#recommended') + + reason = @traits['smithy.api#recommended'].fetch('reason', '') + Model::YARD.recommended_docstrings(reason) + end + + def return_docstrings + [Model::YARD.return_docstring(@service, @model, @member['target'], @target)] + end + + def since_docstrings + return [] unless @traits.key?('smithy.api#since') + + [Model::YARD.since_docstring(@traits['smithy.api#since'])] end def default? diff --git a/gems/smithy/spec/fixtures/documentation/model.json b/gems/smithy/spec/fixtures/documentation/model.json new file mode 100644 index 000000000..3e683bc8b --- /dev/null +++ b/gems/smithy/spec/fixtures/documentation/model.json @@ -0,0 +1,85 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.ruby.tests#Baz": { + "type": "string", + "traits": { + "smithy.api#documentation": "Shape documentation" + } + }, + "smithy.ruby.tests#Documentation": { + "type": "service", + "operations": [ + { + "target": "smithy.ruby.tests#Operation" + } + ], + "traits": { + "smithy.api#title": "Documentation Test" + } + }, + "smithy.ruby.tests#Foo": { + "type": "structure", + "members": { + "baz": { + "target": "smithy.ruby.tests#Baz", + "traits": { + "smithy.api#deprecated": { + "message": "Deprecated structure member", + "since": "2.0" + }, + "smithy.api#documentation": "Member documentation", + "smithy.api#externalDocumentation": { + "Member link": "https://www.example.com/" + }, + "smithy.api#recommended": { + "reason": "This is recommended" + }, + "smithy.api#since": "2.0" + } + }, + "bar": { + "target": "smithy.ruby.tests#Baz", + "traits": { + "smithy.api#required": {} + } + }, + "qux": { + "target": "smithy.api#String" + } + }, + "traits": { + "smithy.api#deprecated": { + "message": "Deprecated structure", + "since": "1.0" + }, + "smithy.api#documentation": "Structure documentation", + "smithy.api#externalDocumentation": { + "Structure link": "https://www.example.com/" + }, + "smithy.api#sensitive": {}, + "smithy.api#since": "1.0" + } + }, + "smithy.ruby.tests#Operation": { + "type": "operation", + "input": { + "target": "smithy.ruby.tests#Foo" + }, + "output": { + "target": "smithy.ruby.tests#Foo" + }, + "traits": { + "smithy.api#deprecated": { + "message": "Deprecated operation", + "since": "1.0" + }, + "smithy.api#documentation": "Operation documentation", + "smithy.api#externalDocumentation": { + "Operation link": "https://www.example.com/" + }, + "smithy.api#since": "1.0" + } + } + } +} diff --git a/gems/smithy/spec/fixtures/documentation/model.smithy b/gems/smithy/spec/fixtures/documentation/model.smithy new file mode 100644 index 000000000..99502e874 --- /dev/null +++ b/gems/smithy/spec/fixtures/documentation/model.smithy @@ -0,0 +1,39 @@ +$version: "2" + +namespace smithy.ruby.tests + +@title("Documentation Test") +service Documentation { + operations: [Operation] +} + +@deprecated(message: "Deprecated operation", since: "1.0") +@documentation("Operation documentation") +@externalDocumentation("Operation link": "https://www.example.com/") +@since("1.0") +operation Operation { + input: Foo + output: Foo +} + +@deprecated(message: "Deprecated structure", since: "1.0") +@documentation("Structure documentation") +@externalDocumentation("Structure link": "https://www.example.com/") +@sensitive +@since("1.0") +structure Foo { + @deprecated(message: "Deprecated structure member", since: "2.0") + @documentation("Member documentation") + @externalDocumentation("Member link": "https://www.example.com/") + @recommended(reason: "This is recommended") + @since("2.0") + baz: Baz + + @required + bar: Baz + + qux: String +} + +@documentation("Shape documentation") +string Baz diff --git a/gems/smithy/spec/fixtures/documentation_trait/model.json b/gems/smithy/spec/fixtures/documentation_trait/model.json deleted file mode 100644 index 82f6893be..000000000 --- a/gems/smithy/spec/fixtures/documentation_trait/model.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "smithy": "2.0", - "shapes": { - "smithy.ruby.tests#Baz": { - "type": "string", - "traits": { - "smithy.api#documentation": "Shape documentation" - } - }, - "smithy.ruby.tests#Documentation": { - "type": "service", - "operations": [ - { - "target": "smithy.ruby.tests#Operation" - } - ] - }, - "smithy.ruby.tests#Foo": { - "type": "structure", - "members": { - "baz": { - "target": "smithy.ruby.tests#Baz", - "traits": { - "smithy.api#documentation": "Member documentation" - } - }, - "bar": { - "target": "smithy.ruby.tests#Baz" - }, - "qux": { - "target": "smithy.api#String" - } - }, - "traits": { - "smithy.api#documentation": "Structure documentation" - } - }, - "smithy.ruby.tests#Operation": { - "type": "operation", - "input": { - "target": "smithy.ruby.tests#Foo" - }, - "output": { - "target": "smithy.ruby.tests#Foo" - }, - "traits": { - "smithy.api#documentation": "Operation documentation" - } - } - } -} diff --git a/gems/smithy/spec/fixtures/documentation_trait/model.smithy b/gems/smithy/spec/fixtures/documentation_trait/model.smithy deleted file mode 100644 index d7a09faba..000000000 --- a/gems/smithy/spec/fixtures/documentation_trait/model.smithy +++ /dev/null @@ -1,26 +0,0 @@ -$version: "2" - -namespace smithy.ruby.tests - -service Documentation { - operations: [Operation] -} - -@documentation("Operation documentation") -operation Operation { - input: Foo - output: Foo -} - -@documentation("Structure documentation") -structure Foo { - @documentation("Member documentation") - baz: Baz - - bar: Baz - - qux: String -} - -@documentation("Shape documentation") -string Baz diff --git a/gems/smithy/spec/interfaces/client/client_spec.rb b/gems/smithy/spec/interfaces/client/client_spec.rb index 7fa880383..6f106f027 100644 --- a/gems/smithy/spec/interfaces/client/client_spec.rb +++ b/gems/smithy/spec/interfaces/client/client_spec.rb @@ -46,12 +46,32 @@ end end - context 'documentation trait' do - include_context 'generated client gem', 'DocumentationTrait' + context 'documentation' do + include_context 'generated client gem', 'Documentation' - it 'generates operation and param documentation' do + def assert(expected) + client_file = File.join(@plan.destination_root, 'lib', 'documentation', 'client.rb') + expect(expected).to be_in_documentation(client_file, 'Documentation::Client', 'operation') + end + + it 'generates deprecated documentation' do + expected = <<~DOC + @deprecated + Deprecated operation + Since: 1.0 + DOC + assert(expected) + end + + it 'generates operation documentation' do expected = <<~DOC Operation documentation + DOC + assert(expected) + end + + it 'generates param documentation' do + expected = <<~DOC @param [Hash, Types::OperationInput] params @option params [String] :baz Member documentation @@ -60,8 +80,28 @@ @option params [String] :qux @return [Types::OperationOutput] DOC - client_file = File.join(@plan.destination_root, 'lib', 'documentation_trait', 'client.rb') - expect(expected).to be_in_documentation(client_file, 'DocumentationTrait::Client', 'operation') + assert(expected) + end + + it 'generates return type documentation' do + expected = <<~DOC + @return [Types::OperationOutput] + DOC + assert(expected) + end + + it 'generates external documentation links' do + expected = <<~DOC + @see https://www.example.com/ Operation link + DOC + assert(expected) + end + + it 'generates since documentation' do + expected = <<~DOC + @since 1.0 + DOC + assert(expected) end end diff --git a/gems/smithy/spec/interfaces/client/types_spec.rb b/gems/smithy/spec/interfaces/client/types_spec.rb index 1e5d96100..3f5956aff 100644 --- a/gems/smithy/spec/interfaces/client/types_spec.rb +++ b/gems/smithy/spec/interfaces/client/types_spec.rb @@ -12,13 +12,62 @@ end context 'documentation trait' do - include_context 'generated client gem', 'DocumentationTrait' + include_context 'generated client gem', 'Documentation' - it 'generates type documentation' do + def assert(expected) + types_file = File.join(@plan.destination_root, 'lib', 'documentation', 'types.rb') + expect(expected).to be_in_documentation(types_file, 'Documentation::Types::OperationOutput') + end + + it 'generates deprecated documentation' do + expected = <<~DOC + @deprecated + Deprecated structure + Since: 1.0 + DOC + assert(expected) + end + + it 'generates structure documentation' do expected = <<~DOC Structure documentation + DOC + assert(expected) + end + + it 'generates external documentation links' do + expected = <<~DOC + @see https://www.example.com/ Structure link + DOC + assert(expected) + end + + it 'generates sensitive documentation' do + expected = <<~DOC + @note This shape contains sensitive data and should be treated as such. + DOC + assert(expected) + end + + it 'generates since documentation' do + expected = <<~DOC + @since 1.0 + DOC + assert(expected) + end + + it 'generates attribute documentation' do + expected = <<~DOC @!attribute baz Member documentation + @deprecated + Deprecated structure member + Since: 2.0 + @see https://www.example.com/ Member link + @note + This shape is recommended + Reason: This is recommended + @since 2.0 @return [String] @!attribute bar Shape documentation @@ -26,8 +75,7 @@ @!attribute qux @return [String] DOC - client_file = File.join(@plan.destination_root, 'lib', 'documentation_trait', 'types.rb') - expect(expected).to be_in_documentation(client_file, 'DocumentationTrait::Types::OperationOutput') + assert(expected) end end end diff --git a/projections/shapes/lib/shapes/schema.rb b/projections/shapes/lib/shapes/schema.rb index 010dafd10..8e31740ff 100644 --- a/projections/shapes/lib/shapes/schema.rb +++ b/projections/shapes/lib/shapes/schema.rb @@ -28,69 +28,69 @@ module Schema Timestamp = Smithy::Schema::Shapes::TimestampShape.new(id: 'smithy.ruby.tests#Timestamp', traits: {"smithy.ruby.tests#shape" => {}}) Union = Smithy::Schema::Shapes::UnionShape.new(id: 'smithy.ruby.tests#Union', traits: {"smithy.ruby.tests#shape" => {}}) - Enum.add_member(:foo, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'FOO', traits: {"smithy.api#enumValue" => "bar"})) - IntEnum.add_member(:baz, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'BAZ', traits: {"smithy.api#enumValue" => 1})) - List.member = ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) - Map.key = ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) - Map.value = ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) - OperationInput.add_member(:blob, ShapeRef.new(shape: Blob, member_name: 'blob')) - OperationInput.add_member(:boolean, ShapeRef.new(shape: Boolean, member_name: 'boolean')) - OperationInput.add_member(:string, ShapeRef.new(shape: String, member_name: 'string')) - OperationInput.add_member(:byte, ShapeRef.new(shape: Byte, member_name: 'byte')) - OperationInput.add_member(:short, ShapeRef.new(shape: Short, member_name: 'short')) - OperationInput.add_member(:integer, ShapeRef.new(shape: Integer, member_name: 'integer')) - OperationInput.add_member(:long, ShapeRef.new(shape: Long, member_name: 'long')) - OperationInput.add_member(:float, ShapeRef.new(shape: Float, member_name: 'float')) - OperationInput.add_member(:double, ShapeRef.new(shape: Double, member_name: 'double')) - OperationInput.add_member(:big_integer, ShapeRef.new(shape: BigInteger, member_name: 'bigInteger')) - OperationInput.add_member(:big_decimal, ShapeRef.new(shape: BigDecimal, member_name: 'bigDecimal')) - OperationInput.add_member(:timestamp, ShapeRef.new(shape: Timestamp, member_name: 'timestamp')) - OperationInput.add_member(:document, ShapeRef.new(shape: Document, member_name: 'document')) - OperationInput.add_member(:enum, ShapeRef.new(shape: Enum, member_name: 'enum')) - OperationInput.add_member(:int_enum, ShapeRef.new(shape: IntEnum, member_name: 'intEnum')) - OperationInput.add_member(:list, ShapeRef.new(shape: List, member_name: 'list')) - OperationInput.add_member(:map, ShapeRef.new(shape: Map, member_name: 'map')) - OperationInput.add_member(:structure, ShapeRef.new(shape: Structure, member_name: 'structure')) - OperationInput.add_member(:union, ShapeRef.new(shape: Union, member_name: 'union')) + Enum.add_member(:foo, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'FOO', traits: {"smithy.api#enumValue" => "bar"})) + IntEnum.add_member(:baz, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'BAZ', traits: {"smithy.api#enumValue" => 1})) + List.member = Smithy::Schema::Shapes::ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) + Map.key = Smithy::Schema::Shapes::ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) + Map.value = Smithy::Schema::Shapes::ShapeRef.new(shape: String, traits: {"smithy.ruby.tests#shape" => {}}) + OperationInput.add_member(:blob, Smithy::Schema::Shapes::ShapeRef.new(shape: Blob, member_name: 'blob')) + OperationInput.add_member(:boolean, Smithy::Schema::Shapes::ShapeRef.new(shape: Boolean, member_name: 'boolean')) + OperationInput.add_member(:string, Smithy::Schema::Shapes::ShapeRef.new(shape: String, member_name: 'string')) + OperationInput.add_member(:byte, Smithy::Schema::Shapes::ShapeRef.new(shape: Byte, member_name: 'byte')) + OperationInput.add_member(:short, Smithy::Schema::Shapes::ShapeRef.new(shape: Short, member_name: 'short')) + OperationInput.add_member(:integer, Smithy::Schema::Shapes::ShapeRef.new(shape: Integer, member_name: 'integer')) + OperationInput.add_member(:long, Smithy::Schema::Shapes::ShapeRef.new(shape: Long, member_name: 'long')) + OperationInput.add_member(:float, Smithy::Schema::Shapes::ShapeRef.new(shape: Float, member_name: 'float')) + OperationInput.add_member(:double, Smithy::Schema::Shapes::ShapeRef.new(shape: Double, member_name: 'double')) + OperationInput.add_member(:big_integer, Smithy::Schema::Shapes::ShapeRef.new(shape: BigInteger, member_name: 'bigInteger')) + OperationInput.add_member(:big_decimal, Smithy::Schema::Shapes::ShapeRef.new(shape: BigDecimal, member_name: 'bigDecimal')) + OperationInput.add_member(:timestamp, Smithy::Schema::Shapes::ShapeRef.new(shape: Timestamp, member_name: 'timestamp')) + OperationInput.add_member(:document, Smithy::Schema::Shapes::ShapeRef.new(shape: Document, member_name: 'document')) + OperationInput.add_member(:enum, Smithy::Schema::Shapes::ShapeRef.new(shape: Enum, member_name: 'enum')) + OperationInput.add_member(:int_enum, Smithy::Schema::Shapes::ShapeRef.new(shape: IntEnum, member_name: 'intEnum')) + OperationInput.add_member(:list, Smithy::Schema::Shapes::ShapeRef.new(shape: List, member_name: 'list')) + OperationInput.add_member(:map, Smithy::Schema::Shapes::ShapeRef.new(shape: Map, member_name: 'map')) + OperationInput.add_member(:structure, Smithy::Schema::Shapes::ShapeRef.new(shape: Structure, member_name: 'structure')) + OperationInput.add_member(:union, Smithy::Schema::Shapes::ShapeRef.new(shape: Union, member_name: 'union')) OperationInput.type = Types::OperationInput - OperationOutput.add_member(:blob, ShapeRef.new(shape: Blob, member_name: 'blob')) - OperationOutput.add_member(:boolean, ShapeRef.new(shape: Boolean, member_name: 'boolean')) - OperationOutput.add_member(:string, ShapeRef.new(shape: String, member_name: 'string')) - OperationOutput.add_member(:byte, ShapeRef.new(shape: Byte, member_name: 'byte')) - OperationOutput.add_member(:short, ShapeRef.new(shape: Short, member_name: 'short')) - OperationOutput.add_member(:integer, ShapeRef.new(shape: Integer, member_name: 'integer')) - OperationOutput.add_member(:long, ShapeRef.new(shape: Long, member_name: 'long')) - OperationOutput.add_member(:float, ShapeRef.new(shape: Float, member_name: 'float')) - OperationOutput.add_member(:double, ShapeRef.new(shape: Double, member_name: 'double')) - OperationOutput.add_member(:big_integer, ShapeRef.new(shape: BigInteger, member_name: 'bigInteger')) - OperationOutput.add_member(:big_decimal, ShapeRef.new(shape: BigDecimal, member_name: 'bigDecimal')) - OperationOutput.add_member(:timestamp, ShapeRef.new(shape: Timestamp, member_name: 'timestamp')) - OperationOutput.add_member(:document, ShapeRef.new(shape: Document, member_name: 'document')) - OperationOutput.add_member(:enum, ShapeRef.new(shape: Enum, member_name: 'enum')) - OperationOutput.add_member(:int_enum, ShapeRef.new(shape: IntEnum, member_name: 'intEnum')) - OperationOutput.add_member(:list, ShapeRef.new(shape: List, member_name: 'list')) - OperationOutput.add_member(:map, ShapeRef.new(shape: Map, member_name: 'map')) - OperationOutput.add_member(:structure, ShapeRef.new(shape: Structure, member_name: 'structure')) - OperationOutput.add_member(:union, ShapeRef.new(shape: Union, member_name: 'union')) + OperationOutput.add_member(:blob, Smithy::Schema::Shapes::ShapeRef.new(shape: Blob, member_name: 'blob')) + OperationOutput.add_member(:boolean, Smithy::Schema::Shapes::ShapeRef.new(shape: Boolean, member_name: 'boolean')) + OperationOutput.add_member(:string, Smithy::Schema::Shapes::ShapeRef.new(shape: String, member_name: 'string')) + OperationOutput.add_member(:byte, Smithy::Schema::Shapes::ShapeRef.new(shape: Byte, member_name: 'byte')) + OperationOutput.add_member(:short, Smithy::Schema::Shapes::ShapeRef.new(shape: Short, member_name: 'short')) + OperationOutput.add_member(:integer, Smithy::Schema::Shapes::ShapeRef.new(shape: Integer, member_name: 'integer')) + OperationOutput.add_member(:long, Smithy::Schema::Shapes::ShapeRef.new(shape: Long, member_name: 'long')) + OperationOutput.add_member(:float, Smithy::Schema::Shapes::ShapeRef.new(shape: Float, member_name: 'float')) + OperationOutput.add_member(:double, Smithy::Schema::Shapes::ShapeRef.new(shape: Double, member_name: 'double')) + OperationOutput.add_member(:big_integer, Smithy::Schema::Shapes::ShapeRef.new(shape: BigInteger, member_name: 'bigInteger')) + OperationOutput.add_member(:big_decimal, Smithy::Schema::Shapes::ShapeRef.new(shape: BigDecimal, member_name: 'bigDecimal')) + OperationOutput.add_member(:timestamp, Smithy::Schema::Shapes::ShapeRef.new(shape: Timestamp, member_name: 'timestamp')) + OperationOutput.add_member(:document, Smithy::Schema::Shapes::ShapeRef.new(shape: Document, member_name: 'document')) + OperationOutput.add_member(:enum, Smithy::Schema::Shapes::ShapeRef.new(shape: Enum, member_name: 'enum')) + OperationOutput.add_member(:int_enum, Smithy::Schema::Shapes::ShapeRef.new(shape: IntEnum, member_name: 'intEnum')) + OperationOutput.add_member(:list, Smithy::Schema::Shapes::ShapeRef.new(shape: List, member_name: 'list')) + OperationOutput.add_member(:map, Smithy::Schema::Shapes::ShapeRef.new(shape: Map, member_name: 'map')) + OperationOutput.add_member(:structure, Smithy::Schema::Shapes::ShapeRef.new(shape: Structure, member_name: 'structure')) + OperationOutput.add_member(:union, Smithy::Schema::Shapes::ShapeRef.new(shape: Union, member_name: 'union')) OperationOutput.type = Types::OperationOutput - Structure.add_member(:member, ShapeRef.new(shape: String, member_name: 'member', traits: {"smithy.ruby.tests#shape" => {}})) + Structure.add_member(:member, Smithy::Schema::Shapes::ShapeRef.new(shape: String, member_name: 'member', traits: {"smithy.ruby.tests#shape" => {}})) Structure.type = Types::Structure - Union.add_member(:string, Types::Union::String, ShapeRef.new(shape: String, member_name: 'string', traits: {"smithy.ruby.tests#shape" => {}})) - Union.add_member(:structure, Types::Union::Structure, ShapeRef.new(shape: Structure, member_name: 'structure', traits: {"smithy.ruby.tests#shape" => {}})) - Union.add_member(:unit, Types::Union::Unit, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'unit', traits: {"smithy.ruby.tests#shape" => {}})) - Union.add_member(:unknown, Types::Union::Unknown, ShapeRef.new(shape: Prelude::Unit)) + Union.add_member(:string, Types::Union::String, Smithy::Schema::Shapes::ShapeRef.new(shape: String, member_name: 'string', traits: {"smithy.ruby.tests#shape" => {}})) + Union.add_member(:structure, Types::Union::Structure, Smithy::Schema::Shapes::ShapeRef.new(shape: Structure, member_name: 'structure', traits: {"smithy.ruby.tests#shape" => {}})) + Union.add_member(:unit, Types::Union::Unit, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit, member_name: 'unit', traits: {"smithy.ruby.tests#shape" => {}})) + Union.add_member(:unknown, Types::Union::Unknown, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit)) Union.type = Types::Union - ShapeService = ServiceShape.new do |service| + ShapeService = Smithy::Schema::Shapes::ServiceShape.new do |service| service.id = "smithy.ruby.tests#ShapeService" service.name = "ShapeService" service.version = "2018-10-31" service.traits = {"smithy.ruby.tests#shape" => {}} - service.add_operation(:operation, OperationShape.new do |operation| + service.add_operation(:operation, Smithy::Schema::Shapes::OperationShape.new do |operation| operation.id = "smithy.ruby.tests#Operation" operation.name = "Operation" - operation.input = ShapeRef.new(shape: OperationInput) - operation.output = ShapeRef.new(shape: OperationOutput) + operation.input = Smithy::Schema::Shapes::ShapeRef.new(shape: OperationInput) + operation.output = Smithy::Schema::Shapes::ShapeRef.new(shape: OperationOutput) operation.traits = {"smithy.ruby.tests#shape" => {}} end) diff --git a/projections/weather/lib/weather/schema.rb b/projections/weather/lib/weather/schema.rb index 09800884c..ab80c0843 100644 --- a/projections/weather/lib/weather/schema.rb +++ b/projections/weather/lib/weather/schema.rb @@ -19,65 +19,65 @@ module Schema ListCitiesOutput = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#ListCitiesOutput', traits: {"smithy.api#output" => {}}) NoSuchResource = Smithy::Schema::Shapes::StructureShape.new(id: 'example.weather#NoSuchResource', traits: {"smithy.api#error" => "client"}) - CityCoordinates.add_member(:latitude, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'latitude', traits: {"smithy.api#required" => {}})) - CityCoordinates.add_member(:longitude, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'longitude', traits: {"smithy.api#required" => {}})) + CityCoordinates.add_member(:latitude, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'latitude', traits: {"smithy.api#required" => {}})) + CityCoordinates.add_member(:longitude, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'longitude', traits: {"smithy.api#required" => {}})) CityCoordinates.type = Types::CityCoordinates - CitySummaries.member = ShapeRef.new(shape: CitySummary) - CitySummary.add_member(:city_id, ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) - CitySummary.add_member(:name, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'name', traits: {"smithy.api#required" => {}})) + CitySummaries.member = Smithy::Schema::Shapes::ShapeRef.new(shape: CitySummary) + CitySummary.add_member(:city_id, Smithy::Schema::Shapes::ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) + CitySummary.add_member(:name, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'name', traits: {"smithy.api#required" => {}})) CitySummary.type = Types::CitySummary - GetCityInput.add_member(:city_id, ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) + GetCityInput.add_member(:city_id, Smithy::Schema::Shapes::ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) GetCityInput.type = Types::GetCityInput - GetCityOutput.add_member(:name, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'name', traits: {"smithy.api#notProperty" => {}, "smithy.api#required" => {}})) - GetCityOutput.add_member(:coordinates, ShapeRef.new(shape: CityCoordinates, member_name: 'coordinates', traits: {"smithy.api#required" => {}})) + GetCityOutput.add_member(:name, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'name', traits: {"smithy.api#notProperty" => {}, "smithy.api#required" => {}})) + GetCityOutput.add_member(:coordinates, Smithy::Schema::Shapes::ShapeRef.new(shape: CityCoordinates, member_name: 'coordinates', traits: {"smithy.api#required" => {}})) GetCityOutput.type = Types::GetCityOutput - GetCurrentTimeOutput.add_member(:time, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Timestamp, member_name: 'time', traits: {"smithy.api#required" => {}})) + GetCurrentTimeOutput.add_member(:time, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Timestamp, member_name: 'time', traits: {"smithy.api#required" => {}})) GetCurrentTimeOutput.type = Types::GetCurrentTimeOutput - GetForecastInput.add_member(:city_id, ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) + GetForecastInput.add_member(:city_id, Smithy::Schema::Shapes::ShapeRef.new(shape: CityId, member_name: 'cityId', traits: {"smithy.api#required" => {}})) GetForecastInput.type = Types::GetForecastInput - GetForecastOutput.add_member(:chance_of_rain, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'chanceOfRain')) + GetForecastOutput.add_member(:chance_of_rain, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Float, member_name: 'chanceOfRain')) GetForecastOutput.type = Types::GetForecastOutput - ListCitiesInput.add_member(:next_token, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'nextToken')) - ListCitiesInput.add_member(:page_size, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Integer, member_name: 'pageSize')) + ListCitiesInput.add_member(:next_token, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'nextToken')) + ListCitiesInput.add_member(:page_size, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Integer, member_name: 'pageSize')) ListCitiesInput.type = Types::ListCitiesInput - ListCitiesOutput.add_member(:next_token, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'nextToken')) - ListCitiesOutput.add_member(:items, ShapeRef.new(shape: CitySummaries, member_name: 'items', traits: {"smithy.api#required" => {}})) + ListCitiesOutput.add_member(:next_token, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'nextToken')) + ListCitiesOutput.add_member(:items, Smithy::Schema::Shapes::ShapeRef.new(shape: CitySummaries, member_name: 'items', traits: {"smithy.api#required" => {}})) ListCitiesOutput.type = Types::ListCitiesOutput - NoSuchResource.add_member(:resource_type, ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'resourceType', traits: {"smithy.api#required" => {}})) + NoSuchResource.add_member(:resource_type, Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::String, member_name: 'resourceType', traits: {"smithy.api#required" => {}})) NoSuchResource.type = Types::NoSuchResource - Weather = ServiceShape.new do |service| + Weather = Smithy::Schema::Shapes::ServiceShape.new do |service| service.id = "example.weather#Weather" service.name = "Weather" service.version = "2006-03-01" service.traits = {} - service.add_operation(:get_city, OperationShape.new do |operation| + service.add_operation(:get_city, Smithy::Schema::Shapes::OperationShape.new do |operation| operation.id = "example.weather#GetCity" operation.name = "GetCity" - operation.input = ShapeRef.new(shape: GetCityInput) - operation.output = ShapeRef.new(shape: GetCityOutput) - operation.errors << ShapeRef.new(shape: NoSuchResource) + operation.input = Smithy::Schema::Shapes::ShapeRef.new(shape: GetCityInput) + operation.output = Smithy::Schema::Shapes::ShapeRef.new(shape: GetCityOutput) + operation.errors << Smithy::Schema::Shapes::ShapeRef.new(shape: NoSuchResource) operation.traits = {"smithy.api#readonly" => {}} end) - service.add_operation(:get_current_time, OperationShape.new do |operation| + service.add_operation(:get_current_time, Smithy::Schema::Shapes::OperationShape.new do |operation| operation.id = "example.weather#GetCurrentTime" operation.name = "GetCurrentTime" - operation.input = ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit) - operation.output = ShapeRef.new(shape: GetCurrentTimeOutput) + operation.input = Smithy::Schema::Shapes::ShapeRef.new(shape: Smithy::Schema::Shapes::Prelude::Unit) + operation.output = Smithy::Schema::Shapes::ShapeRef.new(shape: GetCurrentTimeOutput) operation.traits = {"smithy.api#readonly" => {}} end) - service.add_operation(:get_forecast, OperationShape.new do |operation| + service.add_operation(:get_forecast, Smithy::Schema::Shapes::OperationShape.new do |operation| operation.id = "example.weather#GetForecast" operation.name = "GetForecast" - operation.input = ShapeRef.new(shape: GetForecastInput) - operation.output = ShapeRef.new(shape: GetForecastOutput) + operation.input = Smithy::Schema::Shapes::ShapeRef.new(shape: GetForecastInput) + operation.output = Smithy::Schema::Shapes::ShapeRef.new(shape: GetForecastOutput) operation.traits = {"smithy.api#readonly" => {}} end) - service.add_operation(:list_cities, OperationShape.new do |operation| + service.add_operation(:list_cities, Smithy::Schema::Shapes::OperationShape.new do |operation| operation.id = "example.weather#ListCities" operation.name = "ListCities" - operation.input = ShapeRef.new(shape: ListCitiesInput) - operation.output = ShapeRef.new(shape: ListCitiesOutput) + operation.input = Smithy::Schema::Shapes::ShapeRef.new(shape: ListCitiesInput) + operation.output = Smithy::Schema::Shapes::ShapeRef.new(shape: ListCitiesOutput) operation.traits = {"smithy.api#readonly" => {}} operation[:paginator] = Paginators::ListCities.new end) From e7064d370ea948a6c6b01280b329b8fc8f0fd517 Mon Sep 17 00:00:00 2001 From: Matt Muller Date: Sat, 17 May 2025 17:52:22 -0400 Subject: [PATCH 5/6] Clean up and make yard type api private --- gems/smithy/lib/smithy/model/yard.rb | 28 +++++++++------- gems/smithy/lib/smithy/views/client/client.rb | 33 +++++++++++-------- .../spec/fixtures/documentation/model.json | 6 +++- .../spec/fixtures/documentation/model.smithy | 4 ++- .../spec/interfaces/client/client_spec.rb | 2 +- .../spec/interfaces/client/types_spec.rb | 2 +- 6 files changed, 46 insertions(+), 29 deletions(-) diff --git a/gems/smithy/lib/smithy/model/yard.rb b/gems/smithy/lib/smithy/model/yard.rb index ec778dd23..5462420c5 100644 --- a/gems/smithy/lib/smithy/model/yard.rb +++ b/gems/smithy/lib/smithy/model/yard.rb @@ -16,6 +16,14 @@ def external_documentation_docstrings(hash) hash.map { |key, value| "@see #{escape(value)} #{escape(key)}" } end + def option_docstrings(service, model, id, shape, option, docstrings) # rubocop:disable Metrics/ParameterLists + lines = ["@option params [#{type(service, model, id, shape)}] :#{option}"] + docstrings.each do |docstring| + lines << " #{docstring}" + end + lines + end + def param_docstring(service, model, id, shape) "@param [Hash, #{type(service, model, id, shape)}] params" end @@ -39,8 +47,9 @@ def since_docstring(since) "@since #{escape(since)}" end - # rubocop:disable Metrics/CyclomaticComplexity - def type(service, model, id, shape) + private + + def type(service, model, id, shape) # rubocop:disable Metrics/CyclomaticComplexity case shape['type'] when 'blob', 'string', 'enum' then 'String' when 'boolean' then 'Boolean' @@ -48,23 +57,20 @@ def type(service, model, id, shape) when 'float', 'double' then 'Float' when 'timestamp' then 'Time' when 'document' then 'JSON' - when 'list' then list_type(service, model, shape) - when 'map' then map_type(service, model, shape) - when 'structure', 'union' then structure_type(service, id) + when 'list' then list(service, model, shape) + when 'map' then map(service, model, shape) + when 'structure', 'union' then structure(service, id) else 'Object' end end - # rubocop:enable Metrics/CyclomaticComplexity - - private - def structure_type(service, id) + def structure(service, id) return 'Smithy::Schema::EmptyStructure' if id == 'smithy.api#Unit' "Types::#{(service.dig('rename', id) || Model::Shape.name(id)).camelize}" end - def map_type(service, model, shape) + def map(service, model, shape) key_target = Model.shape(model, shape['key']['target']) value_target = Model.shape(model, shape['value']['target']) key_type = type(service, model, shape['key']['target'], key_target) @@ -72,7 +78,7 @@ def map_type(service, model, shape) "Hash<#{key_type}, #{value_type}>" end - def list_type(service, model, shape) + def list(service, model, shape) member_target = Model.shape(model, shape['member']['target']) "Array<#{type(service, model, shape['member']['target'], member_target)}>" end diff --git a/gems/smithy/lib/smithy/views/client/client.rb b/gems/smithy/lib/smithy/views/client/client.rb index e93a5f4e1..e44f2e693 100644 --- a/gems/smithy/lib/smithy/views/client/client.rb +++ b/gems/smithy/lib/smithy/views/client/client.rb @@ -148,33 +148,38 @@ def since_docstrings [Model::YARD.since_docstring(@traits['smithy.api#since'])] end - def params_docstrings # rubocop:disable Metrics/AbcSize - input = Model.shape(@model, @operation['input']['target']) + def params_docstrings + input_target = @operation['input']['target'] + input = Model.shape(@model, input_target) lines = [] - lines << Model::YARD.param_docstring(@service, @model, @operation['input']['target'], input) + lines << Model::YARD.param_docstring(@service, @model, input_target, input) input['members'].each do |member_name, member_shape| - member = Model.shape(@model, member_shape['target']) - member_type = Model::YARD.type(@service, @model, member_shape['target'], member) - lines << "@option params [#{member_type}] :#{member_name.underscore}" - param_docstring(member_shape).each do |docstring| - lines << " #{docstring}" - end + target = Model.shape(@model, member_shape['target']) + docstrings = Model::YARD.option_docstrings( + @service, + @model, + member_shape['target'], + target, + member_name.underscore, + param_docstrings(member_shape, target) + ) + lines.concat(docstrings) end lines end def return_docstrings - output = Model.shape(@model, @operation['output']['target']) - [Model::YARD.return_docstring(@service, @model, @operation['output']['target'], output)] + output_target = @operation['output']['target'] + output = Model.shape(@model, output_target) + [Model::YARD.return_docstring(@service, @model, output_target, output)] end - def param_docstring(member_shape) + def param_docstrings(member_shape, target) documentation = member_shape.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") return documentation unless documentation.empty? - member = Model.shape(@model, member_shape['target']) - member.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") + target.fetch('traits', {}).fetch('smithy.api#documentation', '').split("\n") end end end diff --git a/gems/smithy/spec/fixtures/documentation/model.json b/gems/smithy/spec/fixtures/documentation/model.json index 3e683bc8b..9cc97aca4 100644 --- a/gems/smithy/spec/fixtures/documentation/model.json +++ b/gems/smithy/spec/fixtures/documentation/model.json @@ -45,7 +45,7 @@ } }, "qux": { - "target": "smithy.api#String" + "target": "smithy.ruby.tests#Structure" } }, "traits": { @@ -80,6 +80,10 @@ }, "smithy.api#since": "1.0" } + }, + "smithy.ruby.tests#Structure": { + "type": "structure", + "members": {} } } } diff --git a/gems/smithy/spec/fixtures/documentation/model.smithy b/gems/smithy/spec/fixtures/documentation/model.smithy index 99502e874..d9f063b4d 100644 --- a/gems/smithy/spec/fixtures/documentation/model.smithy +++ b/gems/smithy/spec/fixtures/documentation/model.smithy @@ -32,8 +32,10 @@ structure Foo { @required bar: Baz - qux: String + qux: Structure } @documentation("Shape documentation") string Baz + +structure Structure {} diff --git a/gems/smithy/spec/interfaces/client/client_spec.rb b/gems/smithy/spec/interfaces/client/client_spec.rb index 6f106f027..11980ccbd 100644 --- a/gems/smithy/spec/interfaces/client/client_spec.rb +++ b/gems/smithy/spec/interfaces/client/client_spec.rb @@ -77,7 +77,7 @@ def assert(expected) Member documentation @option params [String] :bar Shape documentation - @option params [String] :qux + @option params [Types::Structure] :qux @return [Types::OperationOutput] DOC assert(expected) diff --git a/gems/smithy/spec/interfaces/client/types_spec.rb b/gems/smithy/spec/interfaces/client/types_spec.rb index 3f5956aff..9731d0fb9 100644 --- a/gems/smithy/spec/interfaces/client/types_spec.rb +++ b/gems/smithy/spec/interfaces/client/types_spec.rb @@ -73,7 +73,7 @@ def assert(expected) Shape documentation @return [String] @!attribute qux - @return [String] + @return [Types::Structure] DOC assert(expected) end From a096f553a73e9f2b15716ef07f7134514501362b Mon Sep 17 00:00:00 2001 From: Matt Muller Date: Sat, 17 May 2025 18:27:26 -0400 Subject: [PATCH 6/6] Support service shape documentation traits --- gems/smithy/lib/smithy/model/yard.rb | 4 ++ .../lib/smithy/templates/client/module.erb | 13 ++-- gems/smithy/lib/smithy/views/client/module.rb | 71 +++++++++++++++---- .../spec/fixtures/documentation/model.json | 9 +++ .../spec/fixtures/documentation/model.smithy | 4 ++ .../spec/interfaces/client/module_spec.rb | 54 ++++++++++++-- .../spec/interfaces/schema/module_spec.rb | 8 +-- 7 files changed, 134 insertions(+), 29 deletions(-) diff --git a/gems/smithy/lib/smithy/model/yard.rb b/gems/smithy/lib/smithy/model/yard.rb index 5462420c5..09b3898e0 100644 --- a/gems/smithy/lib/smithy/model/yard.rb +++ b/gems/smithy/lib/smithy/model/yard.rb @@ -47,6 +47,10 @@ def since_docstring(since) "@since #{escape(since)}" end + def title_docstring(title) + "@title #{escape(title)}" + end + private def type(service, model, id, shape) # rubocop:disable Metrics/CyclomaticComplexity diff --git a/gems/smithy/lib/smithy/templates/client/module.erb b/gems/smithy/lib/smithy/templates/client/module.erb index e131a3355..790f446bf 100644 --- a/gems/smithy/lib/smithy/templates/client/module.erb +++ b/gems/smithy/lib/smithy/templates/client/module.erb @@ -6,13 +6,16 @@ require '<%= require %>' <% end %> -<% module_names.each_with_index do |module_name, i| -%> -<%= ' ' * i %>module <%= module_name %> +<% ["Object"].concat(module_names).each_cons(2) do |parent, child| -%> +<%= parent %>.const_set('<%= child %>', Module.new) unless <%= parent %>.const_defined?('<%= child %>') <% end -%> -<%= ' ' * (module_names.size) %>VERSION = '<%= gem_version %>' -<% (0...module_names.size).reverse_each do |i| -%> -<%= ' ' * i %>end + +<% docstrings.each do |docstring| -%> +# <%= docstring %> <% end -%> +module <%= module_name %> + VERSION = '<%= gem_version %>' +end <% relative_requires.each do |require| -%> require_relative '<%= gem_name %>/<%= require %>' diff --git a/gems/smithy/lib/smithy/views/client/module.rb b/gems/smithy/lib/smithy/views/client/module.rb index 247c849f0..7b2cb2cb8 100644 --- a/gems/smithy/lib/smithy/views/client/module.rb +++ b/gems/smithy/lib/smithy/views/client/module.rb @@ -7,18 +7,12 @@ module Client class Module < View def initialize(plan) @plan = plan + _, service = plan.service.first + @traits = service.fetch('traits', {}) @model = plan.model super() end - def gem_name - @plan.gem_name - end - - def gem_version - @plan.gem_version - end - def requires requires = @plan.welds.map(&:add_dependencies).reduce({}, :merge) requires = requires.except(@plan.welds.map(&:remove_dependencies).reduce([], :+)) @@ -32,16 +26,32 @@ def requires requires end - def documentation - _id, service = @model.shapes.find { |_key, shape| shape.is_a?(Model::ServiceShape) } - _id, trait = service.traits.find { |_id, trait| trait.id == 'smithy.api#documentation' } - "# #{trait.data}" - end - def module_names @plan.module_name.split('::') end + def module_name + @plan.module_name + end + + def docstrings + lines = [] + lines.concat(title_docstrings) + lines.concat(documentation_docstrings) + lines.concat(deprecated_docstrings) + lines.concat(external_documentation_docstrings) + lines.concat(since_docstrings) + lines + end + + def gem_version + @plan.gem_version + end + + def gem_name + @plan.gem_name + end + def relative_requires return [] unless @plan.destination_root # types must come before schemas @@ -51,6 +61,39 @@ def relative_requires %w[types paginators schema auth_parameters auth_resolver client customizations errors endpoint_parameters endpoint_provider] end + + private + + def documentation_docstrings + @traits.fetch('smithy.api#documentation', '').split("\n") + end + + def deprecated_docstrings + return [] unless @traits.key?('smithy.api#deprecated') + + message = @traits['smithy.api#deprecated'].fetch('message', '') + since = @traits['smithy.api#deprecated'].fetch('since', '') + Model::YARD.deprecated_docstrings(message, since) + end + + def external_documentation_docstrings + return [] unless @traits.key?('smithy.api#externalDocumentation') + + hash = @traits.fetch('smithy.api#externalDocumentation', {}) + Model::YARD.external_documentation_docstrings(hash) + end + + def since_docstrings + return [] unless @traits.key?('smithy.api#since') + + [Model::YARD.since_docstring(@traits['smithy.api#since'])] + end + + def title_docstrings + return [] unless @traits.key?('smithy.api#title') + + [Model::YARD.title_docstring(@traits['smithy.api#title'])] + end end end end diff --git a/gems/smithy/spec/fixtures/documentation/model.json b/gems/smithy/spec/fixtures/documentation/model.json index 9cc97aca4..2667b2f47 100644 --- a/gems/smithy/spec/fixtures/documentation/model.json +++ b/gems/smithy/spec/fixtures/documentation/model.json @@ -15,6 +15,15 @@ } ], "traits": { + "smithy.api#deprecated": { + "message": "Deprecated service", + "since": "1.0" + }, + "smithy.api#documentation": "Service documentation", + "smithy.api#externalDocumentation": { + "Service link": "https://www.example.com/" + }, + "smithy.api#since": "1.0", "smithy.api#title": "Documentation Test" } }, diff --git a/gems/smithy/spec/fixtures/documentation/model.smithy b/gems/smithy/spec/fixtures/documentation/model.smithy index d9f063b4d..079195ebf 100644 --- a/gems/smithy/spec/fixtures/documentation/model.smithy +++ b/gems/smithy/spec/fixtures/documentation/model.smithy @@ -2,6 +2,10 @@ $version: "2" namespace smithy.ruby.tests +@deprecated(message: "Deprecated service", since: "1.0") +@documentation("Service documentation") +@externalDocumentation("Service link": "https://www.example.com/") +@since("1.0") @title("Documentation Test") service Documentation { operations: [Operation] diff --git a/gems/smithy/spec/interfaces/client/module_spec.rb b/gems/smithy/spec/interfaces/client/module_spec.rb index f0a0aac1c..67377929f 100644 --- a/gems/smithy/spec/interfaces/client/module_spec.rb +++ b/gems/smithy/spec/interfaces/client/module_spec.rb @@ -3,11 +3,55 @@ require_relative '../../spec_helper' describe 'Client: Module' do - context 'single module' do - ['generated client gem', 'generated client from source code'].each do |context| - context context do - include_examples 'gem module', context - end + ['generated client gem', 'generated client from source code'].each do |context| + context context do + include_examples 'gem module', context + end + end + + context 'documentation trait' do + include_context 'generated client gem', 'Documentation' + + def assert(expected) + module_file = File.join(@plan.destination_root, 'lib', 'documentation.rb') + expect(expected).to be_in_documentation(module_file, 'Documentation') + end + + it 'generates title documentation' do + expected = <<~DOC + @title Documentation Test + DOC + assert(expected) + end + + it 'generates module documentation' do + expected = <<~DOC + Service documentation + DOC + assert(expected) + end + + it 'generates deprecated documentation' do + expected = <<~DOC + @deprecated + Deprecated service + Since: 1.0 + DOC + assert(expected) + end + + it 'generates external documentation links' do + expected = <<~DOC + @see https://www.example.com/ Service link + DOC + assert(expected) + end + + it 'generates since documentation' do + expected = <<~DOC + @since 1.0 + DOC + assert(expected) end end end diff --git a/gems/smithy/spec/interfaces/schema/module_spec.rb b/gems/smithy/spec/interfaces/schema/module_spec.rb index 76ef06944..e1f80c3a8 100644 --- a/gems/smithy/spec/interfaces/schema/module_spec.rb +++ b/gems/smithy/spec/interfaces/schema/module_spec.rb @@ -3,11 +3,9 @@ require_relative '../../spec_helper' describe 'Schema: Module' do - context 'single module' do - ['generated schema gem', 'generated schema from source code'].each do |context| - context context do - include_examples 'gem module', context - end + ['generated schema gem', 'generated schema from source code'].each do |context| + context context do + include_examples 'gem module', context end end end