From 26941cf79b5b4106ceea8b131f75a86a977861ce Mon Sep 17 00:00:00 2001 From: Alex Woods Date: Tue, 25 Jun 2024 11:56:13 -0700 Subject: [PATCH] Add support for stringArray parameters + operationContextParams (#203) --- .../rails_json/spec/protocol_spec.rb | 224 +++++------------- .../rpcv2_cbor/spec/protocol_spec.rb | 120 +++------- .../white_label/spec/endpoint_spec.rb | 8 +- .../endpoints-string-array.smithy | 188 +++++++++++++++ codegen/smithy-ruby-codegen/CHANGELOG.md | 1 + .../ruby/codegen/ApplicationTransport.java | 2 + .../codegen/generators/EndpointGenerator.java | 217 ++++++++++++++++- .../codegen/rulesengine/BuiltInBinding.java | 20 +- .../ruby/codegen/util/ParamsToHash.java | 3 + hearth/CHANGELOG.md | 1 + tasks/test.rake | 2 +- 11 files changed, 507 insertions(+), 279 deletions(-) create mode 100644 codegen/smithy-ruby-codegen-test/model/test-services/endpoints-string-array.smithy diff --git a/codegen/projections/rails_json/spec/protocol_spec.rb b/codegen/projections/rails_json/spec/protocol_spec.rb index 192d73771..70d182c64 100644 --- a/codegen/projections/rails_json/spec/protocol_spec.rb +++ b/codegen/projections/rails_json/spec/protocol_spec.rb @@ -1057,9 +1057,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.empty_input_and_empty_output({ - - }, **opts) + client.empty_input_and_empty_output({}, **opts) end end @@ -1078,9 +1076,7 @@ module RailsJson client.stub_responses(:empty_input_and_empty_output, response) allow(Builders::EmptyInputAndEmptyOutput).to receive(:build) output = client.empty_input_and_empty_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end # This test ensures that clients can gracefully handle @@ -1093,9 +1089,7 @@ module RailsJson client.stub_responses(:empty_input_and_empty_output, response) allow(Builders::EmptyInputAndEmptyOutput).to receive(:build) output = client.empty_input_and_empty_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -1111,13 +1105,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::EmptyInputAndEmptyOutput).to receive(:build) - client.stub_responses(:empty_input_and_empty_output, data: { - - }) + client.stub_responses(:empty_input_and_empty_output, data: {}) output = client.empty_input_and_empty_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end # This test ensures that clients can gracefully handle @@ -1128,13 +1118,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::EmptyInputAndEmptyOutput).to receive(:build) - client.stub_responses(:empty_input_and_empty_output, data: { - - }) + client.stub_responses(:empty_input_and_empty_output, data: {}) output = client.empty_input_and_empty_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -1158,9 +1144,7 @@ module RailsJson interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} opts[:endpoint] = 'http://example.com' - client.endpoint_operation({ - - }, **opts) + client.endpoint_operation({}, **opts) end end @@ -1431,25 +1415,19 @@ module RailsJson begin output = client.greeting_with_errors({}, auth_resolver: Hearth::AnonymousAuthResolver.new) rescue Errors::ComplexError => e - expect(e.data.to_h).to eq({ - - }) + expect(e.data.to_h).to eq({}) end end # it 'stubs RailsJsonEmptyComplexErrorWithNoMessage' do - client.stub_responses(:greeting_with_errors, error: { class: Errors::ComplexError, data: { - - } }) + client.stub_responses(:greeting_with_errors, error: { class: Errors::ComplexError, data: {} }) allow(Builders::GreetingWithErrors).to receive(:build) begin output = client.greeting_with_errors({}, auth_resolver: Hearth::AnonymousAuthResolver.new) rescue Errors::ComplexError => e expect(e.http_status).to eq(403) - expect(e.data.to_h).to eq({ - - }) + expect(e.data.to_h).to eq({}) end end end @@ -1471,9 +1449,7 @@ module RailsJson interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} opts[:endpoint] = 'http://example.com/custom' - client.host_with_path_operation({ - - }, **opts) + client.host_with_path_operation({}, **opts) end end @@ -1869,9 +1845,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.http_payload_with_union({ - - }, **opts) + client.http_payload_with_union({}, **opts) end end @@ -1907,9 +1881,7 @@ module RailsJson client.stub_responses(:http_payload_with_union, response) allow(Builders::HttpPayloadWithUnion).to receive(:build) output = client.http_payload_with_union({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -1943,13 +1915,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::HttpPayloadWithUnion).to receive(:build) - client.stub_responses(:http_payload_with_union, data: { - - }) + client.stub_responses(:http_payload_with_union, data: {}) output = client.http_payload_with_union({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -2452,9 +2420,7 @@ module RailsJson client.stub_responses(:ignore_query_params_in_response, response) allow(Builders::IgnoreQueryParamsInResponse).to receive(:build) output = client.ignore_query_params_in_response({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end # This test is similar to RailsJsonIgnoreQueryParamsInResponse, @@ -2468,9 +2434,7 @@ module RailsJson client.stub_responses(:ignore_query_params_in_response, response) allow(Builders::IgnoreQueryParamsInResponse).to receive(:build) output = client.ignore_query_params_in_response({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -2487,13 +2451,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::IgnoreQueryParamsInResponse).to receive(:build) - client.stub_responses(:ignore_query_params_in_response, data: { - - }) + client.stub_responses(:ignore_query_params_in_response, data: {}) output = client.ignore_query_params_in_response({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end # This test is similar to RailsJsonIgnoreQueryParamsInResponse, @@ -2505,13 +2465,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::IgnoreQueryParamsInResponse).to receive(:build) - client.stub_responses(:ignore_query_params_in_response, data: { - - }) + client.stub_responses(:ignore_query_params_in_response, data: {}) output = client.ignore_query_params_in_response({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -5409,9 +5365,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.no_input_and_no_output({ - - }, **opts) + client.no_input_and_no_output({}, **opts) end end @@ -5429,9 +5383,7 @@ module RailsJson client.stub_responses(:no_input_and_no_output, response) allow(Builders::NoInputAndNoOutput).to receive(:build) output = client.no_input_and_no_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -5447,13 +5399,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::NoInputAndNoOutput).to receive(:build) - client.stub_responses(:no_input_and_no_output, data: { - - }) + client.stub_responses(:no_input_and_no_output, data: {}) output = client.no_input_and_no_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -5476,9 +5424,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.no_input_and_output({ - - }, **opts) + client.no_input_and_output({}, **opts) end end @@ -5496,9 +5442,7 @@ module RailsJson client.stub_responses(:no_input_and_output, response) allow(Builders::NoInputAndOutput).to receive(:build) output = client.no_input_and_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end # This test is similar to RailsJsonNoInputAndOutputWithJson, but @@ -5512,9 +5456,7 @@ module RailsJson client.stub_responses(:no_input_and_output, response) allow(Builders::NoInputAndOutput).to receive(:build) output = client.no_input_and_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -5529,13 +5471,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::NoInputAndOutput).to receive(:build) - client.stub_responses(:no_input_and_output, data: { - - }) + client.stub_responses(:no_input_and_output, data: {}) output = client.no_input_and_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end # This test is similar to RailsJsonNoInputAndOutputWithJson, but @@ -5547,13 +5485,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::NoInputAndOutput).to receive(:build) - client.stub_responses(:no_input_and_output, data: { - - }) + client.stub_responses(:no_input_and_output, data: {}) output = client.no_input_and_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -5732,9 +5666,7 @@ module RailsJson interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} client.operation_with_defaults({ - defaults: { - - } + defaults: {} }, **opts) end @@ -5750,9 +5682,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.operation_with_defaults({ - - }, **opts) + client.operation_with_defaults({}, **opts) end # Client uses explicitly provided member values over defaults @@ -5869,9 +5799,7 @@ module RailsJson interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} client.operation_with_defaults({ - client_optional_defaults: { - - } + client_optional_defaults: {} }, **opts) end @@ -6225,13 +6153,9 @@ module RailsJson language: "en" }, dialog_list: [ + {}, { - - }, - { - farewell: { - - } + farewell: {} }, { language: "it", @@ -6242,14 +6166,10 @@ module RailsJson } ], dialog_map: { - 'emptyDialog' => { - - }, + 'emptyDialog' => {}, 'partialEmptyDialog' => { language: "en", - farewell: { - - } + farewell: {} }, 'nonEmptyDialog' => { greeting: "konnichiwa", @@ -6475,9 +6395,7 @@ module RailsJson opts = {interceptors: [interceptor]} client.post_player_action({ action: { - quit: { - - } + quit: {} } }, **opts) end @@ -6502,9 +6420,7 @@ module RailsJson output = client.post_player_action({}, auth_resolver: Hearth::AnonymousAuthResolver.new) expect(output.data.to_h).to eq({ action: { - quit: { - - } + quit: {} } }) end @@ -6522,17 +6438,13 @@ module RailsJson allow(Builders::PostPlayerAction).to receive(:build) client.stub_responses(:post_player_action, data: { action: { - quit: { - - } + quit: {} } }) output = client.post_player_action({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) expect(output.data.to_h).to eq({ action: { - quit: { - - } + quit: {} } }) end @@ -6808,9 +6720,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.query_idempotency_token_auto_fill({ - - }, **opts) + client.query_idempotency_token_auto_fill({}, **opts) end # Uses the given idempotency token as-is @@ -7203,9 +7113,7 @@ module RailsJson client.stub_responses(:simple_scalar_properties, response) allow(Builders::SimpleScalarProperties).to receive(:build) output = client.simple_scalar_properties({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end # Supports handling NaN float values. @@ -7310,13 +7218,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::SimpleScalarProperties).to receive(:build) - client.stub_responses(:simple_scalar_properties, data: { - - }) + client.stub_responses(:simple_scalar_properties, data: {}) output = client.simple_scalar_properties({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end # Supports handling NaN float values. @@ -8233,9 +8137,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.test_body_structure({ - - }, **opts) + client.test_body_structure({}, **opts) end end @@ -8257,9 +8159,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.test_no_payload({ - - }, **opts) + client.test_no_payload({}, **opts) end # Serializes a GET request with header member but no modeled body @@ -8298,9 +8198,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.test_payload_blob({ - - }, **opts) + client.test_payload_blob({}, **opts) end # Serializes a payload targeting a blob @@ -8341,9 +8239,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.test_payload_structure({ - - }, **opts) + client.test_payload_structure({}, **opts) end # Serializes a payload targeting a structure @@ -8494,9 +8390,7 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.unit_input_and_output({ - - }, **opts) + client.unit_input_and_output({}, **opts) end end @@ -8514,9 +8408,7 @@ module RailsJson client.stub_responses(:unit_input_and_output, response) allow(Builders::UnitInputAndOutput).to receive(:build) output = client.unit_input_and_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end @@ -8532,13 +8424,9 @@ module RailsJson end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::UnitInputAndOutput).to receive(:build) - client.stub_responses(:unit_input_and_output, data: { - - }) + client.stub_responses(:unit_input_and_output, data: {}) output = client.unit_input_and_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to eq({ - - }) + expect(output.data.to_h).to eq({}) end end diff --git a/codegen/projections/rpcv2_cbor/spec/protocol_spec.rb b/codegen/projections/rpcv2_cbor/spec/protocol_spec.rb index 84c6b486e..52dcbccb4 100644 --- a/codegen/projections/rpcv2_cbor/spec/protocol_spec.rb +++ b/codegen/projections/rpcv2_cbor/spec/protocol_spec.rb @@ -40,9 +40,7 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.empty_input_output({ - - }, **opts) + client.empty_input_output({}, **opts) end end @@ -60,9 +58,7 @@ module Rpcv2Cbor client.stub_responses(:empty_input_output, response) allow(Builders::EmptyInputOutput).to receive(:build) output = client.empty_input_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end # When output structure is empty the client should accept an empty body @@ -76,9 +72,7 @@ module Rpcv2Cbor client.stub_responses(:empty_input_output, response) allow(Builders::EmptyInputOutput).to receive(:build) output = client.empty_input_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end end @@ -92,13 +86,9 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::EmptyInputOutput).to receive(:build) - client.stub_responses(:empty_input_output, data: { - - }) + client.stub_responses(:empty_input_output, data: {}) output = client.empty_input_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end # When output structure is empty the client should accept an empty body @@ -108,13 +98,9 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::EmptyInputOutput).to receive(:build) - client.stub_responses(:empty_input_output, data: { - - }) + client.stub_responses(:empty_input_output, data: {}) output = client.empty_input_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end end @@ -436,25 +422,19 @@ module Rpcv2Cbor begin output = client.greeting_with_errors({}, auth_resolver: Hearth::AnonymousAuthResolver.new) rescue Errors::ComplexError => e - expect(e.data.to_h).to eq({ - - }) + expect(e.data.to_h).to eq({}) end end # it 'stubs RpcV2CborEmptyComplexError' do - client.stub_responses(:greeting_with_errors, error: { class: Errors::ComplexError, data: { - - } }) + client.stub_responses(:greeting_with_errors, error: { class: Errors::ComplexError, data: {} }) allow(Builders::GreetingWithErrors).to receive(:build) begin output = client.greeting_with_errors({}, auth_resolver: Hearth::AnonymousAuthResolver.new) rescue Errors::ComplexError => e expect(e.http_status).to eq(400) - expect(e.data.to_h).to eq({ - - }) + expect(e.data.to_h).to eq({}) end end end @@ -477,9 +457,7 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.no_input_output({ - - }, **opts) + client.no_input_output({}, **opts) end end @@ -496,9 +474,7 @@ module Rpcv2Cbor client.stub_responses(:no_input_output, response) allow(Builders::NoInputOutput).to receive(:build) output = client.no_input_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end # Clients should accept a CBOR empty struct if there is no output. @@ -512,9 +488,7 @@ module Rpcv2Cbor client.stub_responses(:no_input_output, response) allow(Builders::NoInputOutput).to receive(:build) output = client.no_input_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end # Clients should accept an empty body if there is no output and @@ -529,9 +503,7 @@ module Rpcv2Cbor client.stub_responses(:no_input_output, response) allow(Builders::NoInputOutput).to receive(:build) output = client.no_input_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end end @@ -545,13 +517,9 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::NoInputOutput).to receive(:build) - client.stub_responses(:no_input_output, data: { - - }) + client.stub_responses(:no_input_output, data: {}) output = client.no_input_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end # Clients should accept a CBOR empty struct if there is no output. @@ -561,13 +529,9 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::NoInputOutput).to receive(:build) - client.stub_responses(:no_input_output, data: { - - }) + client.stub_responses(:no_input_output, data: {}) output = client.no_input_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end # Clients should accept an empty body if there is no output and @@ -578,13 +542,9 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::NoInputOutput).to receive(:build) - client.stub_responses(:no_input_output, data: { - - }) + client.stub_responses(:no_input_output, data: {}) output = client.no_input_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end end @@ -608,9 +568,7 @@ module Rpcv2Cbor interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} client.operation_with_defaults({ - defaults: { - - } + defaults: {} }, **opts) end @@ -626,9 +584,7 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.operation_with_defaults({ - - }, **opts) + client.operation_with_defaults({}, **opts) end # Client uses explicitly provided member values over defaults @@ -707,9 +663,7 @@ module Rpcv2Cbor interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} client.operation_with_defaults({ - client_optional_defaults: { - - } + client_optional_defaults: {} }, **opts) end @@ -961,9 +915,7 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) opts = {interceptors: [interceptor]} - client.optional_input_output({ - - }, **opts) + client.optional_input_output({}, **opts) end end @@ -981,9 +933,7 @@ module Rpcv2Cbor client.stub_responses(:optional_input_output, response) allow(Builders::OptionalInputOutput).to receive(:build) output = client.optional_input_output({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end end @@ -997,13 +947,9 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::OptionalInputOutput).to receive(:build) - client.stub_responses(:optional_input_output, data: { - - }) + client.stub_responses(:optional_input_output, data: {}) output = client.optional_input_output({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end end @@ -2506,9 +2452,7 @@ module Rpcv2Cbor client.stub_responses(:simple_scalar_properties, response) allow(Builders::SimpleScalarProperties).to receive(:build) output = client.simple_scalar_properties({}, auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end # Supports handling NaN float values. @@ -2684,13 +2628,9 @@ module Rpcv2Cbor end interceptor = Hearth::Interceptor.new(read_after_transmit: proc) allow(Builders::SimpleScalarProperties).to receive(:build) - client.stub_responses(:simple_scalar_properties, data: { - - }) + client.stub_responses(:simple_scalar_properties, data: {}) output = client.simple_scalar_properties({}, interceptors: [interceptor], auth_resolver: Hearth::AnonymousAuthResolver.new) - expect(output.data.to_h).to match_cbor({ - - }) + expect(output.data.to_h).to match_cbor({}) end # Supports handling NaN float values. diff --git a/codegen/projections/white_label/spec/endpoint_spec.rb b/codegen/projections/white_label/spec/endpoint_spec.rb index fd2753629..584341383 100644 --- a/codegen/projections/white_label/spec/endpoint_spec.rb +++ b/codegen/projections/white_label/spec/endpoint_spec.rb @@ -156,7 +156,9 @@ module Endpoint end end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) - client.resource_endpoint({resource_url: 'https://resource.com/path'}, interceptors: [interceptor]) + client.resource_endpoint({ + resource_url: "https://resource.com/path" + }, interceptors: [interceptor]) end end @@ -214,7 +216,9 @@ module Endpoint end end interceptor = Hearth::Interceptor.new(read_before_transmit: proc) - client.endpoint_with_host_label_operation({label_member: 'label'}, interceptors: [interceptor]) + client.endpoint_with_host_label_operation({ + label_member: "label" + }, interceptors: [interceptor]) end end end diff --git a/codegen/smithy-ruby-codegen-test/model/test-services/endpoints-string-array.smithy b/codegen/smithy-ruby-codegen-test/model/test-services/endpoints-string-array.smithy new file mode 100644 index 000000000..268b069f6 --- /dev/null +++ b/codegen/smithy-ruby-codegen-test/model/test-services/endpoints-string-array.smithy @@ -0,0 +1,188 @@ +$version: "2.0" + +namespace example + +use smithy.rules#endpointRuleSet +use smithy.rules#endpointTests +use smithy.rules#staticContextParams +use smithy.rules#operationContextParams + +@endpointRuleSet({ + version: "1.0", + parameters: { + stringArrayParam: { + type: "stringArray", + required: true, + default: ["defaultValue1", "defaultValue2"], + documentation: "docs" + } + }, + rules: [ + { + "documentation": "Template first array value into URI if set", + "conditions": [ + { + "fn": "getAttr", + "argv": [ + { + "ref": "stringArrayParam" + }, + "[0]" + ], + "assign": "arrayValue" + } + ], + "endpoint": { + "url": "https://example.com/{arrayValue}" + }, + "type": "endpoint" + }, + { + "conditions": [], + "documentation": "error fallthrough", + "error": "no array values set", + "type": "error" + } + ] +}) +@endpointTests({ + "version": "1.0", + "testCases": [ + { + "documentation": "Default array values used" + "params": {} + "expect": { + "endpoint": { + "url": "https://example.com/defaultValue1" + } + }, + "operationInputs": [ + { + "operationName": "NoBindingsOperation", + } + ] + }, + { + "documentation": "Empty array", + "params": { + "stringArrayParam": [] + } + "expect": { + "error": "no array values set" + }, + "operationInputs": [ + { + "operationName": "EmptyStaticContextOperation", + } + ] + }, + { + "documentation": "Static value", + "params": { + "stringArrayParam": ["staticValue1"] + } + "expect": { + "endpoint": { + "url": "https://example.com/staticValue1" + } + }, + "operationInputs": [ + { + "operationName": "StaticContextOperation", + } + ] + }, + { + "documentation": "bound value from input", + "params": { + "stringArrayParam": ["key1"] + } + "expect": { + "endpoint": { + "url": "https://example.com/key1" + } + }, + "operationInputs": [ + { + "operationName": "ListOfObjectsOperation", + "operationParams": { + "nested": { + "listOfObjects": [{"key": "key1"}] + } + }, + }, + { + "operationName": "MapOperation", + "operationParams": { + "map": { + "key1": "value1" + } + } + } + ] + } + ] +}) +service EndpointStringArrayService { + version: "2022-01-01", + operations: [ + NoBindingsOperation, + EmptyStaticContextOperation, + StaticContextOperation, + ListOfObjectsOperation, + MapOperation + ] +} + +operation NoBindingsOperation { + input:= {} +} + +@staticContextParams( + "stringArrayParam": {value: []} +) +operation EmptyStaticContextOperation { + input := {} +} + +@staticContextParams( + "stringArrayParam": {value: ["staticValue1"]} +) +operation StaticContextOperation { + input := {} +} + +@operationContextParams( + "stringArrayParam": {path: "nested.listOfObjects[*].key"} +) +operation ListOfObjectsOperation { + input:= { + nested: Nested + } +} + +@operationContextParams( + "stringArrayParam": {path: "keys(map)"} +) +operation MapOperation { + input:= { + map: Map + } +} + +structure Nested { + listOfObjects: ListOfObjects +} + +list ListOfObjects { + member: ObjectMember +} + +structure ObjectMember { + key: String, +} + +map Map { + key: String, + value: String +} \ No newline at end of file diff --git a/codegen/smithy-ruby-codegen/CHANGELOG.md b/codegen/smithy-ruby-codegen/CHANGELOG.md index 8e0b1aa9f..42793792f 100644 --- a/codegen/smithy-ruby-codegen/CHANGELOG.md +++ b/codegen/smithy-ruby-codegen/CHANGELOG.md @@ -1,6 +1,7 @@ Unreleased Changes ------------------ +* Feature - Add support for stringArray Endpoint parameters + operationContextParams binding. * Issue - Update reserved member names for new Struct methods. * Feature - Add support for Smithy RPC v2 CBOR protocol. * Issue - Use `strict_encode64` instead of `encode64`. diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/ApplicationTransport.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/ApplicationTransport.java index d81cea1f4..bb15fd870 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/ApplicationTransport.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/ApplicationTransport.java @@ -32,6 +32,7 @@ import software.amazon.smithy.ruby.codegen.middleware.factories.ContentMD5MiddlewareFactory; import software.amazon.smithy.ruby.codegen.middleware.factories.ParseMiddlewareFactory; import software.amazon.smithy.ruby.codegen.middleware.factories.RequestCompressionMiddlewareFactory; +import software.amazon.smithy.ruby.codegen.rulesengine.BuiltInBinding; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -101,6 +102,7 @@ public static ApplicationTransport createDefaultHttpApplicationTransport() { ClientFragment client = ClientFragment.builder() .addConfig(httpClient) + .addConfig(BuiltInBinding.SDK_ENDPOINT_CONFIG) .render((self, ctx) -> httpClient.renderGetConfigValue()) .build(); diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/EndpointGenerator.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/EndpointGenerator.java index 825271554..5f696f418 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/EndpointGenerator.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/EndpointGenerator.java @@ -29,6 +29,25 @@ import java.util.stream.Stream; import software.amazon.smithy.build.SmithyBuildException; import software.amazon.smithy.codegen.core.directed.ContextualDirective; +import software.amazon.smithy.jmespath.JmespathExpression; +import software.amazon.smithy.jmespath.ast.AndExpression; +import software.amazon.smithy.jmespath.ast.ComparatorExpression; +import software.amazon.smithy.jmespath.ast.CurrentExpression; +import software.amazon.smithy.jmespath.ast.ExpressionTypeExpression; +import software.amazon.smithy.jmespath.ast.FieldExpression; +import software.amazon.smithy.jmespath.ast.FilterProjectionExpression; +import software.amazon.smithy.jmespath.ast.FlattenExpression; +import software.amazon.smithy.jmespath.ast.FunctionExpression; +import software.amazon.smithy.jmespath.ast.IndexExpression; +import software.amazon.smithy.jmespath.ast.LiteralExpression; +import software.amazon.smithy.jmespath.ast.MultiSelectHashExpression; +import software.amazon.smithy.jmespath.ast.MultiSelectListExpression; +import software.amazon.smithy.jmespath.ast.NotExpression; +import software.amazon.smithy.jmespath.ast.ObjectProjectionExpression; +import software.amazon.smithy.jmespath.ast.OrExpression; +import software.amazon.smithy.jmespath.ast.ProjectionExpression; +import software.amazon.smithy.jmespath.ast.SliceExpression; +import software.amazon.smithy.jmespath.ast.Subexpression; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.BooleanNode; import software.amazon.smithy.model.node.Node; @@ -37,8 +56,10 @@ import software.amazon.smithy.model.node.NumberNode; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.node.StringNode; +import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.ruby.codegen.GenerationContext; @@ -48,6 +69,7 @@ import software.amazon.smithy.ruby.codegen.RubySettings; import software.amazon.smithy.ruby.codegen.rulesengine.AuthSchemeBinding; import software.amazon.smithy.ruby.codegen.rulesengine.FunctionBinding; +import software.amazon.smithy.ruby.codegen.util.ParamsToHash; import software.amazon.smithy.rulesengine.language.Endpoint; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.syntax.Identifier; @@ -74,8 +96,11 @@ import software.amazon.smithy.rulesengine.traits.EndpointTestOperationInput; import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait; import software.amazon.smithy.rulesengine.traits.ExpectedEndpoint; +import software.amazon.smithy.rulesengine.traits.OperationContextParamDefinition; +import software.amazon.smithy.rulesengine.traits.OperationContextParamsTrait; import software.amazon.smithy.rulesengine.traits.StaticContextParamDefinition; import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait; +import software.amazon.smithy.utils.Pair; import software.amazon.smithy.utils.SmithyInternalApi; import software.amazon.smithy.utils.StringUtils; @@ -213,6 +238,11 @@ private void renderEndpointParamsClass(RubyCodeWriter writer) { } else if (parameter.getType() == ParameterType.BOOLEAN) { defaultParams.put(rubyParamName, parameter.getDefault().get().expectBooleanValue().getValue() ? "true" : "false"); + } else if (parameter.getType() == ParameterType.STRING_ARRAY) { + defaultParams.put(rubyParamName, + "[" + parameter.getDefault().get().expectArrayValue().getValues().stream() + .map(v -> "'" + v.expectStringValue().getValue() + "'") + .collect(Collectors.joining(", ")) + "]"); } else { throw new IllegalArgumentException("Unexpected parameter type: " + parameter.getType()); } @@ -254,6 +284,8 @@ private void renderRbsEndpointParamsClass(RubyCodeWriter writer) { paramsToTypes.put(rubyParamName, "::String"); } else if (parameter.getType() == ParameterType.BOOLEAN) { paramsToTypes.put(rubyParamName, "bool"); + } else if (parameter.getType() == ParameterType.STRING_ARRAY) { + paramsToTypes.put(rubyParamName, "::Array[::String]"); } else { throw new IllegalArgumentException("Unexpected parameter type: " + parameter.getType()); } @@ -279,11 +311,17 @@ private void renderParamBuilders(RubyCodeWriter writer) { for (OperationShape operation : operations) { Map staticContextParams = new HashMap<>(); + Map operationContextParams = new HashMap<>(); operation.getTrait(StaticContextParamsTrait.class).ifPresent((staticContext) -> { staticContext.getParameters().forEach((name, p) -> { staticContextParams.put(name, p); }); }); + operation.getTrait(OperationContextParamsTrait.class).ifPresent((operationContext) -> { + operationContext.getParameters().forEach((name, p) -> { + operationContextParams.put(name, p); + }); + }); Map contextParams = new HashMap<>(); context.model().expectShape(operation.getInputShape(), StructureShape.class) @@ -311,12 +349,23 @@ private void renderParamBuilders(RubyCodeWriter writer) { value = "'" + def.getValue().expectStringNode() + "'"; } else if (def.getValue().isBooleanNode()) { value = def.getValue().expectBooleanNode().getValue() ? "true" : "false"; + } else if (def.getValue().isArrayNode()) { + value = "[" + def.getValue().expectArrayNode().getElements().stream() + .map(v -> "'" + v.expectStringNode().getValue() + "'") + .collect(Collectors.joining(", ")) + "]"; } else { throw new SmithyBuildException("Unexpected StaticContextParam type: " + def.getValue().getType() + " for parameter: " + paramName); } writer.write("params.$L = $L", RubyFormatter.toSnakeCase(paramName), value); + } else if (operationContextParams.containsKey(paramName)) { + OperationContextParamDefinition def = operationContextParams.get(paramName); + String value = JmespathExpression.parse(def.getPath()) + .accept(new RubyEndpointsJmesPathVisitor( + context, model.expectShape(operation.getInputShape()))).left; + writer.write("params.$L = input$L", + RubyFormatter.toSnakeCase(paramName), value); } else if (clientContextParams.containsKey(paramName)) { writer.write("params.$1L = config[:$1L] unless config[:$1L].nil?", clientContextParams.get(paramName)); @@ -432,8 +481,15 @@ private void renderEndpointTestCases(RubyCodeWriter writer, List "'" + v.expectStringNode().getValue() + "'") + .collect(Collectors.joining(", ")) + "]"; + } else { + throw new SmithyBuildException("Unexpected endpoint test parameter type: " + + e.getValue().getType() + " for parameter: " + e.getKey().getValue()); } return RubyFormatter.toSnakeCase(e.getKey().getValue()) + ": " + value; }).collect(Collectors.joining(", ")); @@ -478,7 +534,7 @@ private void renderEndpointTestCases(RubyCodeWriter writer, List id.getName().contains(operationInput.getOperationName())) + .filter(id -> id.getName().equals(operationInput.getOperationName())) .findFirst().get(); OperationShape operation = model.expectShape(operationId, OperationShape.class); String operationName = RubyFormatter.toSnakeCase( @@ -505,12 +561,8 @@ private void renderOperationInputTest(RubyCodeWriter writer, EndpointTestCase te .write("") .write("client = Client.new(config)"); - - String operationInputs = operationInput.getOperationParams().getMembers() - .entrySet().stream().map(e -> { - return RubyFormatter.toSnakeCase(e.getKey().getValue()) + ": " - + e.getValue().accept(new RubyNodeVisitor()); - }).collect(Collectors.joining(", ")); + String operationInputs = model.expectShape(operation.getInputShape()) + .accept(new ParamsToHash(model, operationInput.getOperationParams(), symbolProvider)); if (testCase.getExpect().getError().isPresent()) { writer @@ -532,7 +584,7 @@ private void renderOperationInputTest(RubyCodeWriter writer, EndpointTestCase te .closeBlock("end") .closeBlock("end") .write("interceptor = Hearth::Interceptor.new(read_before_transmit: proc)") - .write("client.$L({$L}, interceptors: [interceptor])", + .write("client.$L($L, interceptors: [interceptor])", operationName, operationInputs); // TODO: Verify Auth @@ -741,6 +793,153 @@ public String stringNode(StringNode node) { } } + private static final class RubyEndpointsJmesPathVisitor implements + software.amazon.smithy.jmespath.ExpressionVisitor> { + + final GenerationContext context; + final Shape input; + + RubyEndpointsJmesPathVisitor(GenerationContext context, Shape input) { + this.context = context; + this.input = input; + } + + @Override + public Pair visitFunction(FunctionExpression expression) { + if (expression.getName().equals("keys")) { + return Pair.of( + expression.getArguments().get(0).accept(this).left + ".to_h.keys", + null); + } else { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + } + + @Override + public Pair visitField(FieldExpression expression) { + MemberShape member = input.getMember(expression.getName()).orElseThrow(); + return Pair.of( + "." + context.symbolProvider().toMemberName(member), + context.model().expectShape(member.getTarget())); + } + + @Override + public Pair visitObjectProjection(ObjectProjectionExpression expression) { + Pair left = expression.getLeft().accept(this); + if (left.right.isMapShape()) { + Pair right = + expression.getRight().accept( + new RubyEndpointsJmesPathVisitor( + context, + context.model().expectShape( + left.right.asMapShape().get().getValue().getTarget()))); + return Pair.of( + left.left + ".values.map { |o| o" + right.left + " }.compact", + right.right + ); + } else { + throw new SmithyBuildException("ObjectProjection can be applied only to Map shapes."); + } + } + + @Override + public Pair visitSubexpression(Subexpression expression) { + Pair left = expression.getLeft().accept(this); + Pair right = + expression.getRight().accept(new RubyEndpointsJmesPathVisitor(context, left.right)); + return Pair.of( + left.left + right.left, + right.right + ); + } + + + @Override + public Pair visitProjection(ProjectionExpression expression) { + Pair left = expression.getLeft().accept(this); + if (left.right.isListShape()) { + Pair right = + expression.getRight().accept( + new RubyEndpointsJmesPathVisitor( + context, + context.model().expectShape( + left.right.asListShape().get().getMember().getTarget()))); + return Pair.of( + left.left + ".map { |o| o" + right.left + " }.compact", + right.right + ); + } else { + throw new SmithyBuildException("Projection can only be applied to List Shapes."); + } + } + + @Override + public Pair visitExpressionType(ExpressionTypeExpression expression) { + return expression.getExpression().accept(this); + } + + @Override + public Pair visitComparator(ComparatorExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitCurrentNode(CurrentExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitFlatten(FlattenExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitIndex(IndexExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitLiteral(LiteralExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitMultiSelectList(MultiSelectListExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitMultiSelectHash(MultiSelectHashExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitAnd(AndExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitOr(OrExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitNot(NotExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitFilterProjection(FilterProjectionExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + @Override + public Pair visitSlice(SliceExpression expression) { + throw new SmithyBuildException("Unsupported JMESPath expression"); + } + + } + private class RubyRuleVisitor implements RuleValueVisitor { final RubyCodeWriter writer; diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/rulesengine/BuiltInBinding.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/rulesengine/BuiltInBinding.java index 5f2d75864..a456872eb 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/rulesengine/BuiltInBinding.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/rulesengine/BuiltInBinding.java @@ -38,6 +38,16 @@ * Provides a binding between Smithy rules engine built-ins and Ruby code. */ public final class BuiltInBinding { + public static final ClientConfig SDK_ENDPOINT_CONFIG = ClientConfig.builder() + .name("endpoint") + .documentationRbsAndValidationType("String") + .documentation("Endpoint of the service") + .defaultDynamicValue("cfg[:stub_responses] ? 'http://localhost' : nil") + .build(); + public static final BuiltInBinding ENDPOINT_BUILT_IN_BINDING = BuiltInBinding.builder() + .builtIn(BuiltIns.SDK_ENDPOINT) + .fromConfig(SDK_ENDPOINT_CONFIG) + .build(); private final Parameter builtIn; private final Set clientConfig; private final RenderBuild renderBuild; @@ -59,15 +69,7 @@ public static Builder builder() { public static Set defaultBuiltInBindings() { return Set.of( - BuiltInBinding.builder() - .builtIn(BuiltIns.SDK_ENDPOINT) - .fromConfig(ClientConfig.builder() - .name("endpoint") - .documentationRbsAndValidationType("String") - .documentation("Endpoint of the service") - .defaultDynamicValue("cfg[:stub_responses] ? 'http://localhost' : nil") - .build()) - .build() + ENDPOINT_BUILT_IN_BINDING ); } diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/util/ParamsToHash.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/util/ParamsToHash.java index a0a8a9036..22b188e87 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/util/ParamsToHash.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/util/ParamsToHash.java @@ -247,6 +247,9 @@ public String structureShape(StructureShape shape) { } ObjectNode objectNode = node.expectObjectNode(); Map members = objectNode.getMembers(); + if (members.isEmpty()) { + return "{}"; + } Map shapeMembers = shape.getAllMembers(); diff --git a/hearth/CHANGELOG.md b/hearth/CHANGELOG.md index 710e43fb9..db43ec5f2 100644 --- a/hearth/CHANGELOG.md +++ b/hearth/CHANGELOG.md @@ -1,5 +1,6 @@ Unreleased Changes ------------------ + * Feature - Add `Hearth::Cbor.encode` and `Hearth::Cbor.decode`. * Issue - Fix query param `to_s` for empty arrays. * Feature - [Breaking Change] Add `config` to `Hearth::Context` and refactor `logger` and `interceptors` inside of it. diff --git a/tasks/test.rake b/tasks/test.rake index 7c529ac00..696919453 100644 --- a/tasks/test.rake +++ b/tasks/test.rake @@ -26,7 +26,7 @@ namespace :test do build_dir = 'codegen/smithy-ruby-codegen-test/build/smithyprojections/smithy-ruby-codegen-test' test_sdk_dirs = Dir.glob("#{build_dir}/*/ruby-codegen/*") - .select { |d| !d.include?('white_label') && Dir.exist?("#{d}/spec") } + .select { |d| !d.include?('white_label') && !d.include?('rpcv2cbor') && Dir.exist?("#{d}/spec") } specs = test_sdk_dirs.map { |d| "#{d}/spec" }.join(' ') includes = test_sdk_dirs.map { |d|