Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate specs for protocol tests #270

Merged
merged 107 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from 72 commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
a57acd6
Add protocol base class and draft rpcv2 protocol implementation
jterapin Feb 3, 2025
4d3a7f4
Add example implementation of codegenerated plugin
jterapin Feb 3, 2025
036d2d8
Update smithy model to use rpcv2 trait for testing
jterapin Feb 3, 2025
fb17908
Add projections
jterapin Feb 3, 2025
c7c672c
Merge decaf into branch
jterapin Feb 4, 2025
787f763
Update RPCV2 protocol
jterapin Feb 5, 2025
37c2508
Add protocols weld
jterapin Feb 6, 2025
578478e
Update protocol weld
jterapin Feb 6, 2025
c168be0
Update protocol weld to include a TODO
jterapin Feb 6, 2025
2d098e4
Add more TODO guidance
jterapin Feb 6, 2025
885ae8b
Update protocol plugin
jterapin Feb 6, 2025
20abd06
Update protocol plugin and other components
jterapin Feb 6, 2025
aa367c8
Update handler syntax
jterapin Feb 6, 2025
589feaa
Appease specs
jterapin Feb 6, 2025
8e4c0f3
Update projections
jterapin Feb 6, 2025
03e7699
Remove base class
jterapin Feb 6, 2025
318b0cd
Remove unnecessary method
jterapin Feb 6, 2025
6fe901e
Setup basic testing protocol_test model, builds
alextwoods Feb 7, 2025
1462f8f
Add templates/views and check for protocol test traits
alextwoods Feb 7, 2025
c29eb20
Merge branch 'decaf' into add_cbor_protocol
jterapin Feb 7, 2025
d5413e6
Update protocol modules
jterapin Feb 7, 2025
68713d1
Update documentation
jterapin Feb 7, 2025
5418ba0
Update endpoint specs to handle protocol plugin
jterapin Feb 7, 2025
1f475c2
Move handler classes to smithy-client
jterapin Feb 10, 2025
7659994
Refactor ShapeToHash utility - not working because of mixins.
alextwoods Feb 10, 2025
42114fa
Updates based on feedbacks
jterapin Feb 10, 2025
2656ba7
Sync fixtures
jterapin Feb 10, 2025
86c777d
Update handlers
jterapin Feb 10, 2025
2014c1c
Add rbs file
jterapin Feb 10, 2025
1a26319
Revert "Sync fixtures"
jterapin Feb 10, 2025
1ed8b04
Add protocol plugin specs
jterapin Feb 10, 2025
f7922a9
Update fixture model to recongize config file
jterapin Feb 10, 2025
a4ef87c
update projections
jterapin Feb 10, 2025
f4cb90d
Revert "Update fixture model to recongize config file"
jterapin Feb 10, 2025
fd361e9
Fix config flag for fixtures
jterapin Feb 10, 2025
157bae5
HACK - add in preprocess to handle mixins. Actual implementation is p…
alextwoods Feb 11, 2025
975be63
Add expects
alextwoods Feb 11, 2025
bc5614e
Merge branch 'add_cbor_protocol' into protocol_specs
alextwoods Feb 11, 2025
138ae69
Add more expects, including body matching
alextwoods Feb 11, 2025
3e47008
Update preprocess code (from PR)
alextwoods Feb 11, 2025
71c4e5c
refactor client_options out to allow for host override
alextwoods Feb 11, 2025
659a1ea
Fix documentation
jterapin Feb 11, 2025
f3360c3
Merge decaf into branch
jterapin Feb 11, 2025
7952f0e
Merge branch 'decaf' into add_cbor_protocol
jterapin Feb 11, 2025
b4354a9
Update plugin docs
jterapin Feb 11, 2025
44f1898
Fix config documentation
jterapin Feb 11, 2025
4b1ed95
Fix specs based on merged changes
jterapin Feb 11, 2025
726aaf1
Full implementation of request tests
alextwoods Feb 11, 2025
f25c1d2
Merge branch 'add_cbor_protocol' into protocol_specs
alextwoods Feb 11, 2025
21ee535
Remove temp spec
alextwoods Feb 11, 2025
15cf0aa
Handle endpoints
jterapin Feb 11, 2025
7231d08
Merge branch 'add_cbor_protocol' into protocol_specs
alextwoods Feb 11, 2025
e791ccb
Refactor request/response to use a TestCase base
alextwoods Feb 11, 2025
4ab9718
Fix some formatting and start response tests
alextwoods Feb 11, 2025
9a9afb6
Resolve failures relating to content-type and accept headers
jterapin Feb 11, 2025
3f35c47
Add shapes change coverage
jterapin Feb 11, 2025
69208d9
Merge branch 'decaf' into add_cbor_protocol
jterapin Feb 11, 2025
0edce81
Fix codec deserialization
jterapin Feb 11, 2025
e6e2d1f
Response tests mostly working
alextwoods Feb 11, 2025
6391e83
Merge branch 'add_cbor_protocol' into protocol_specs
alextwoods Feb 11, 2025
046227d
Add possible rbs fix
jterapin Feb 11, 2025
0687fd4
Add header fixes
jterapin Feb 11, 2025
7a9a8f3
Update rbs
jterapin Feb 11, 2025
24c7387
Improve cbor_match for float inf/nan
alextwoods Feb 11, 2025
ef9f6cb
Use --flatten in AST, remove flatten code from model.
alextwoods Feb 12, 2025
302e7f8
Generate error tests
alextwoods Feb 12, 2025
c344501
Merge branch 'add_cbor_protocol' into protocol_specs
alextwoods Feb 12, 2025
0d9830f
handle streaming response body + rubocop cleansup
alextwoods Feb 13, 2025
d0c8398
Add skip mechanism
alextwoods Feb 13, 2025
1f954ed
Add rake tasks for syncing and running protocol tests
alextwoods Feb 13, 2025
c269532
Remove puts
alextwoods Feb 13, 2025
d37e4b8
Fix codegen of float inf/nan values
alextwoods Feb 13, 2025
e05f941
Merge branch 'decaf' into add_cbor_protocol
jterapin Feb 14, 2025
a8996b3
Cleanups from PR
alextwoods Feb 14, 2025
65f2275
More cleanups
alextwoods Feb 14, 2025
3bc1683
Merge branch 'decaf' into add_cbor_protocol
jterapin Feb 17, 2025
8e201a6
Merge decaf into branch
jterapin Feb 17, 2025
0ae5b87
Adding member names to shape
jterapin Feb 17, 2025
33c4ced
Merge decaf into branch
jterapin Feb 17, 2025
36fe4a5
Update based on refactor
jterapin Feb 17, 2025
c175478
Merge decaf into branch
jterapin Feb 17, 2025
936d4ec
Temp fix
jterapin Feb 17, 2025
08073ed
Merge protocol plugin branch
jterapin Feb 17, 2025
c2ee782
Handle timestamp-related protocol tests and skip error test cases
jterapin Feb 18, 2025
d3875d1
Add to reference service shape name
jterapin Feb 18, 2025
eb3255a
Fix failures related to protocol tests
jterapin Feb 18, 2025
c5adb00
Fix top model changes
jterapin Feb 18, 2025
a65325d
Fix rbs
jterapin Feb 18, 2025
3fd03d2
Merge branch 'add_cbor_protocol' into protocol_specs
jterapin Feb 18, 2025
cebe5c4
Appease rubocop
jterapin Feb 18, 2025
70dd666
Update syntax
jterapin Feb 20, 2025
52a8d15
Update helpful commands
jterapin Feb 20, 2025
fcb34b1
Update schema and its shapes
jterapin Feb 20, 2025
bd0d912
Update cbor
jterapin Feb 20, 2025
f7358e5
Merge branch 'decaf' into add_cbor_protocol
jterapin Feb 21, 2025
402f07a
Clean up README
jterapin Feb 21, 2025
86995e0
Update Protocol Plugin to hold generic handlers
jterapin Feb 21, 2025
8a29c33
Update based on feedbacks
jterapin Feb 21, 2025
668d9b0
Add DummyWeldProtocol
jterapin Feb 21, 2025
cafe0a9
Fix failure
jterapin Feb 21, 2025
37ca72d
Update existing spec const
jterapin Feb 21, 2025
601f857
Merge branch 'decaf' into protocol_specs
jterapin Feb 21, 2025
ab2c239
Merge branch 'add_cbor_protocol' into protocol_specs
jterapin Feb 21, 2025
1d7a03d
Merge branch 'decaf' into protocol_specs
mullermp Feb 22, 2025
4ff8a11
Cleanup after merge
mullermp Feb 22, 2025
47996d6
Rubocop
mullermp Feb 22, 2025
98dbb9b
Move value matcher out of smithy-client
mullermp Feb 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,62 @@ namespace :smithy do
plans.each { |plan| SpecHelper.cleanup_gem(plan) }
end

task 'spec' => %w[spec:unit spec:endpoints]
task 'spec:protocols', [:rbs_test] do |_t, args|
require_relative 'gems/smithy/spec/spec_helper'

spec_paths = []
include_paths = []
plans = []
rbs_targets = %w[Smithy Smithy::* Smithy::Client]
sig_paths = ['gems/smithy-client/sig']
Dir.glob('gems/smithy/spec/fixtures/protocol_tests/*/model.json') do |model_path|
test_name = model_path.split('/')[-2]
test_module = test_name.gsub('-', '').camelize
plan = SpecHelper.generate_gem(test_module, :client, fixture: "protocol_tests/#{test_name}")
plans << plan
tmpdir = plan.destination_root
spec_paths << "#{tmpdir}/spec"
include_paths << "#{tmpdir}/lib"
include_paths << "#{tmpdir}/spec"
sig_paths << "#{tmpdir}/sig"
rbs_targets += [test_module, "#{test_module}::*"]
end
specs = spec_paths.join(' ')
includes = include_paths.map { |p| "-I #{p}" }.join(' ')

env =
if args[:rbs_test]
{
'RUBYOPT' => '-r bundler/setup -r rbs/test/setup',
'RBS_TEST_RAISE' => 'true',
'RBS_TEST_LOGLEVEL' => 'error',
'RBS_TEST_OPT' => sig_paths.map { |p| "-I #{p}" }.join(' '),
'RBS_TEST_TARGET' => "\"#{rbs_targets.join(',')}\"",
'RBS_TEST_DOUBLE_SUITE' => 'rspec'
}
else
{}
end

sh(env, "bundle exec rspec #{specs} #{includes}")
ensure
plans.each { |plan| SpecHelper.cleanup_gem(plan) }
end

task 'spec' => %w[spec:unit spec:endpoints spec:protocols]

desc 'Convert all fixture smithy models to JSON AST representation.'
task 'sync-fixtures' do
Dir.glob('gems/smithy/spec/fixtures/**/model.smithy') do |model_path|
out_path = model_path.sub('.smithy', '.json')
sh("smithy ast --aut #{model_path} > #{out_path}")
end

Dir.glob('gems/smithy/spec/fixtures/protocol_tests/**/smithy-build.json') do |smithy_build_path|
Dir.chdir(File.dirname(smithy_build_path)) do
sh('smithy ast --flatten > model.json')
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this supposed to take a file for an argument somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No - the key is that its using the local smithy-build.json which has a transform that selects the cbor protocol test service.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right but I think it should explicitly declare the model file and possibly the config file too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no model file - the model comes from the dependency in the smithy-build.json. but updated to reference the config file.

end
end
end

desc 'Validate that all fixtures JSON models are up to date.'
Expand Down Expand Up @@ -95,8 +143,13 @@ namespace :smithy do
task('smithy:spec:endpoints').invoke('rbs_test')
end

desc 'Run RBS spy tests for all generated protocol test specs.'
task 'rbs:protocol_tests' do
task('smithy:spec:protocols').invoke('rbs_test')
end

desc 'Run RBS spy tests for unit tests and generated endpoint provider specs.'
task 'rbs' => %w[rbs:unit rbs:endpoints]
task 'rbs' => %w[rbs:unit rbs:endpoints rbs:protocol_tests]
end

namespace 'smithy-client' do
Expand Down
6 changes: 5 additions & 1 deletion gems/smithy-client/lib/smithy-client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
require_relative 'smithy-client/handler_context'
require_relative 'smithy-client/handler_list'
require_relative 'smithy-client/handler_list_entry'
require_relative 'smithy-client/handlers'
require_relative 'smithy-client/managed_file'
require_relative 'smithy-client/networking_error'
require_relative 'smithy-client/plugin'
Expand Down Expand Up @@ -40,7 +41,10 @@
# codecs

require_relative 'smithy-client/cbor'
require_relative 'smithy-client/codecs/cbor'
require_relative 'smithy-client/codecs'

# protocols
require_relative 'smithy-client/protocols'

module Smithy
# Base module for a generated Smithy gem.
Expand Down
66 changes: 66 additions & 0 deletions gems/smithy-client/lib/smithy-client/cbor/value_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

require 'rspec/expectations'

def expected_nan?(expected)
expected == 'NaN' || expected.nan?
end

def expected_infinite?(expected)
expected == 'Infinity' || expected == '-Infinity' || expected.infinite?
end

# Provides an rspec matcher for CBOR encoded values
# rubocop:disable Metrics/BlockLength
RSpec::Matchers.define :match_cbor do |expected|
match do |actual|
# identical values don't need more comparison
return true if actual == expected

expect(actual.class).to eq(expected.class)

def match_hash(actual, expected)
expected.each do |key, value|
expect(actual).to include(key)
match_value(actual[key], value)
end

actual.each_key do |key|
expect(expected).to include(key)
end
end

def match_array(actual, expected)
actual.each_with_index do |value, index|
match_value(value, expected[index])
end
end

def match_float(actual, expected)
return if actual.nan? && expected_nan?(expected)
return if actual.infinite? && expected_infinite?(expected)

expect(actual).to be_within(0.0001).of(expected)
end

def match_value(actual, expected)
case actual
when Hash
match_hash(actual, expected)
when Array
match_array(actual, expected)
when Float
match_float(actual, expected)
when Time
expect(actual.utc.iso8601).to eq(expected.utc.iso8601)
else
expect(actual).to eq(expected)
end
end

match_value(actual, expected)
end

diffable
end
# rubocop:enable Metrics/BlockLength
35 changes: 35 additions & 0 deletions gems/smithy-client/lib/smithy-client/handlers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Smithy
module Client
# A collection of generic handlers that are used by Smithy Clients.
# @api private
module Handlers
# Generic build handler to build request based on protocol
class Build < Handler
def call(context)
context.config.protocol.build(context)
@handler.call(context)
end
end

# Generic parse handler to parse response based on protocol
class Parse < Handler
def call(context)
resp = @handler.call(context)
context.config.protocol.parse(context)
resp
end
end

# Generic error handler to parse error based on protocol
class Error < Handler
def call(context)
@handler.call(context).on(300..599) do |response|
context.config.protocol.error(context, response)
end
end
end
end
end
end
28 changes: 28 additions & 0 deletions gems/smithy-client/lib/smithy-client/protocols.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

require_relative 'protocols/rpc_v2'

module Smithy
module Client
# In Smithy, a protocol is a named set of rules that defines the
# syntax and semantics of how a client and server communicate. To
# use a {Smithy::Client}, a protocol is required to be set on
# {Configuration}.
#
# We currently support the following protocols:
#
# - {RPCv2}, a RPC-based protocol over HTTP that sends requests
# and responses with CBOR payloads.
#
# You could also create a custom protocol to pass into the client
# configuration. The given protocol must provide the following
# functionalities:
#
# - `build` - builds the request
# - `parse` - parse the response
# - `error` - extracts the error from response
#
# See {RPCv2} as an example implementation.
module Protocols; end
end
end
79 changes: 79 additions & 0 deletions gems/smithy-client/lib/smithy-client/protocols/rpc_v2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

module Smithy
module Client
module Protocols
# A RPC-based protocol over HTTP that sends requests
# and responses with CBOR payloads.
#
# TODO: Refactor methods to handle eventstreams
class RPCv2
SHAPE_ID = 'smithy.protocols#rpcv2Cbor'

def initialize(options = {})
@query_compatible = options[:query_compatible]
end

# @api private
def build(context)
codec = Client::Codecs::CBOR.new(setting(context))
context.request.body = codec.serialize(context.params, context.operation.input)
context.request.http_method = 'POST'
apply_headers(context)
build_url(context)
end

# @api private
def parse(context)
output_shape = context.operation.output
codec = Client::Codecs::CBOR.new(setting(context))
codec.deserialize(context.response.body.read, output_shape, output_shape.type.new)
end

# @api private
# TODO: To implement after error handling
def error(_context, _response); end

private

def apply_headers(context)
context.request.headers['X-Amzn-Query-Mode'] = 'true' if query_compatible?(context)
context.request.headers['Smithy-Protocol'] = 'rpc-v2-cbor'
apple_content_type(context)
apply_accept_header(context)
# TODO: Implement Content-Length Plugin/Handler - maybe?
context.request.headers['Content-Length'] = context.request.body.size
end

def apply_accept_header(context)
# TODO: Needs an update when streaming is handled
context.request.headers['Accept'] = 'application/cbor'
end

def apple_content_type(context)
return if context.operation.input == Shapes::Prelude::Unit

# TODO: Needs an update when streaming is handled
context.request.headers['Content-Type'] = 'application/cbor'
end

def build_url(context)
base = context.request.endpoint
base.path +=
"/service/#{context.config.schema.service.name}/operation/#{context.operation.name}"
end

def setting(context)
{}.tap do |h|
h[:query_compatible] = true if query_compatible?(context)
end
end

def query_compatible?(context)
@query_compatible ||
context.config.schema.service.traits.one? { |k, _v| k == 'aws.protocols#awsQuery' }
end
end
end
end
end
8 changes: 8 additions & 0 deletions gems/smithy-client/lib/smithy-client/shapes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,31 @@ def initialize(options = {})
class ServiceShape < Shape
def initialize(options = {})
super
@name = options[:name]
@version = options[:version]
end

# @return [String, nil]
attr_accessor :version

# @return [String, nil]
attr_accessor :name
end

# Represents an Operation shape.
class OperationShape < Shape
def initialize(options = {})
super
@name = options[:name]
@input = options[:input]
@output = options[:output]
@errors = options[:errors] || []
yield self if block_given?
end

# @return [String, nil]
attr_accessor :name

# @return [StructureShape, nil]
attr_accessor :input

Expand Down
6 changes: 6 additions & 0 deletions gems/smithy-client/sig/interfaces.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@ module Smithy
end

type endpoint_url = String | URI::HTTP | URI::HTTPS

interface _Protocol
def build: (HandlerContext context) -> void
def parse: (HandlerContext context) -> void
def error: (HandlerContext context, HTTP::Response response) -> void
end
end
end
11 changes: 11 additions & 0 deletions gems/smithy-client/sig/smithy-client/protocols/rpc_v2.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Smithy
module Client
module Protocols
# A RPC-based protocol over HTTP that sends requests
# and responses with CBOR payloads.
class RPCv2
def initialize: (?::Hash[untyped, untyped] options) -> void
end
end
end
end
3 changes: 3 additions & 0 deletions gems/smithy-client/sig/smithy-client/shapes.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ module Smithy
end

class ServiceShape < Shape

attr_accessor name: String?
attr_accessor version: String?
end

class OperationShape < Shape
attr_accessor name: String?
attr_accessor input: StructureShape?
attr_accessor output: StructureShape?
attr_accessor errors: Array[StructureShape]
Expand Down
Loading