smithy 2.0.0.pre0 → 2.0.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/bin/smithy-ruby +1 -4
- data/lib/smithy/command.rb +68 -0
- data/lib/smithy/generators/base.rb +19 -0
- data/lib/smithy/generators/client.rb +106 -0
- data/lib/smithy/generators/schema.rb +61 -0
- data/lib/smithy/generators.rb +29 -0
- data/lib/smithy/model/flattener.rb +114 -0
- data/lib/smithy/model/operation_parser.rb +42 -0
- data/lib/smithy/model/rbs.rb +57 -0
- data/lib/smithy/model/service_index.rb +51 -0
- data/lib/smithy/model/service_parser.rb +74 -0
- data/lib/smithy/model/shape.rb +49 -0
- data/lib/smithy/model/structure_parser.rb +43 -0
- data/lib/smithy/model/yard.rb +100 -0
- data/lib/smithy/model.rb +54 -0
- data/lib/smithy/plan.rb +79 -3
- data/lib/smithy/templates/client/auth_parameters.erb +29 -0
- data/lib/smithy/templates/client/auth_parameters_rbs.erb +14 -0
- data/lib/smithy/templates/client/auth_plugin.erb +115 -0
- data/lib/smithy/templates/client/auth_resolver.erb +16 -0
- data/lib/smithy/templates/client/auth_resolver_rbs.erb +5 -0
- data/lib/smithy/templates/client/client.erb +142 -0
- data/lib/smithy/templates/client/client_rbs.erb +29 -0
- data/lib/smithy/templates/client/customizations.erb +3 -0
- data/lib/smithy/templates/client/endpoint_parameters.erb +65 -0
- data/lib/smithy/templates/client/endpoint_parameters_rbs.erb +13 -0
- data/lib/smithy/templates/client/endpoint_plugin.erb +58 -0
- data/lib/smithy/templates/client/endpoint_provider.erb +15 -0
- data/lib/smithy/templates/client/endpoint_provider_rbs.erb +5 -0
- data/lib/smithy/templates/client/endpoint_provider_spec.erb +70 -0
- data/lib/smithy/templates/client/errors.erb +69 -0
- data/lib/smithy/templates/client/errors_rbs.erb +17 -0
- data/lib/smithy/templates/client/gemspec.erb +17 -0
- data/lib/smithy/templates/client/module.erb +22 -0
- data/lib/smithy/templates/client/module_rbs.erb +7 -0
- data/lib/smithy/templates/client/paginators.erb +33 -0
- data/lib/smithy/templates/client/protocol_spec.erb +144 -0
- data/lib/smithy/templates/client/rubocop_yml.erb +33 -0
- data/lib/smithy/templates/client/schema.erb +76 -0
- data/lib/smithy/templates/client/schema_rbs.erb +13 -0
- data/lib/smithy/templates/client/spec_helper.erb +10 -0
- data/lib/smithy/templates/client/types.erb +64 -0
- data/lib/smithy/templates/client/types_rbs.erb +47 -0
- data/lib/smithy/templates/client/waiters.erb +42 -0
- data/lib/smithy/util/hash_formatter.rb +124 -0
- data/lib/smithy/util/underscore.rb +18 -0
- data/lib/smithy/util.rb +9 -0
- data/lib/smithy/views/client/auth_parameter.rb +29 -0
- data/lib/smithy/views/client/auth_parameters.rb +23 -0
- data/lib/smithy/views/client/auth_parameters_rbs.rb +23 -0
- data/lib/smithy/views/client/auth_plugin.rb +35 -0
- data/lib/smithy/views/client/auth_resolver.rb +125 -0
- data/lib/smithy/views/client/auth_resolver_rbs.rb +19 -0
- data/lib/smithy/views/client/client.rb +208 -0
- data/lib/smithy/views/client/client_rbs.rb +231 -0
- data/lib/smithy/views/client/customizations.rb +10 -0
- data/lib/smithy/views/client/endpoint_parameter.rb +156 -0
- data/lib/smithy/views/client/endpoint_parameters.rb +43 -0
- data/lib/smithy/views/client/endpoint_parameters_rbs.rb +28 -0
- data/lib/smithy/views/client/endpoint_plugin.rb +27 -0
- data/lib/smithy/views/client/endpoint_provider.rb +241 -0
- data/lib/smithy/views/client/endpoint_provider_rbs.rb +19 -0
- data/lib/smithy/views/client/endpoint_provider_spec.rb +137 -0
- data/lib/smithy/views/client/errors.rb +88 -0
- data/lib/smithy/views/client/errors_rbs.rb +12 -0
- data/lib/smithy/views/client/gemspec.rb +36 -0
- data/lib/smithy/views/client/module.rb +107 -0
- data/lib/smithy/views/client/module_rbs.rb +20 -0
- data/lib/smithy/views/client/operation_examples.rb +157 -0
- data/lib/smithy/views/client/paginators.rb +108 -0
- data/lib/smithy/views/client/plugin.rb +29 -0
- data/lib/smithy/views/client/plugin_list.rb +57 -0
- data/lib/smithy/views/client/protocol_spec.rb +254 -0
- data/lib/smithy/views/client/request_response_example.rb +179 -0
- data/lib/smithy/views/client/rubocop_yml.rb +19 -0
- data/lib/smithy/views/client/schema.rb +356 -0
- data/lib/smithy/views/client/schema_rbs.rb +84 -0
- data/lib/smithy/views/client/shape_to_hash.rb +99 -0
- data/lib/smithy/views/client/spec_helper.rb +19 -0
- data/lib/smithy/views/client/types.rb +293 -0
- data/lib/smithy/views/client/types_rbs.rb +67 -0
- data/lib/smithy/views/client/waiters.rb +82 -0
- data/lib/smithy/views/client.rb +47 -0
- data/lib/smithy/views/view.rb +30 -0
- data/lib/smithy/views.rb +9 -0
- data/lib/smithy/weld.rb +109 -0
- data/lib/smithy/welds/auth/anonymous_auth.rb +31 -0
- data/lib/smithy/welds/auth/http_api_key_auth.rb +34 -0
- data/lib/smithy/welds/auth/http_basic_auth.rb +34 -0
- data/lib/smithy/welds/auth/http_bearer_auth.rb +34 -0
- data/lib/smithy/welds/auth/http_digest_auth.rb +34 -0
- data/lib/smithy/welds/plugins.rb +54 -0
- data/lib/smithy/welds/rpc_v2_cbor.rb +20 -0
- data/lib/smithy/welds/rubocop.rb +33 -0
- data/lib/smithy/welds/transforms/default_endpoint_rules.json +35 -0
- data/lib/smithy/welds/transforms/default_endpoint_tests.json +24 -0
- data/lib/smithy/welds/transforms/endpoints.rb +68 -0
- data/lib/smithy/welds/transforms/synthetic_input_output.rb +60 -0
- data/lib/smithy/welds.rb +29 -0
- data/lib/smithy.rb +33 -2
- metadata +144 -9
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Views
|
5
|
+
module Client
|
6
|
+
# @api private
|
7
|
+
class OperationExamples
|
8
|
+
# @param [Hash] model Model
|
9
|
+
# @param [String] operation_name Operation name
|
10
|
+
# @param [Hash<String, Object>] operation Operation shape
|
11
|
+
def initialize(model, operation_name, operation)
|
12
|
+
@model = model
|
13
|
+
@operation_name = operation_name
|
14
|
+
@operation = operation
|
15
|
+
@examples = operation.fetch('traits', {}).fetch('smithy.api#examples', [])
|
16
|
+
end
|
17
|
+
|
18
|
+
def docstrings
|
19
|
+
examples = ''
|
20
|
+
@examples.each do |example|
|
21
|
+
examples += example(example)
|
22
|
+
rescue ArgumentError => e
|
23
|
+
puts "Error processing example for #{@operation_name}: #{e}"
|
24
|
+
next
|
25
|
+
end
|
26
|
+
examples.split("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def example(example)
|
32
|
+
if example['error']
|
33
|
+
error_example(example)
|
34
|
+
else
|
35
|
+
output_example(example)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def output_example(example)
|
40
|
+
<<~EXAMPLE
|
41
|
+
@example #{example['title']}
|
42
|
+
#{documentation(example)}
|
43
|
+
params = #{params(example['input'], @operation['input']['target'])}
|
44
|
+
options = {}
|
45
|
+
response = client.#{@operation_name}(params, options)
|
46
|
+
response.to_h #=>
|
47
|
+
#{params(example['output'], @operation['output']['target'])}
|
48
|
+
EXAMPLE
|
49
|
+
end
|
50
|
+
|
51
|
+
def error_example(example)
|
52
|
+
error = example['error']
|
53
|
+
<<~EXAMPLE
|
54
|
+
@example #{example['title']}
|
55
|
+
#{documentation(example)}
|
56
|
+
params = #{params(example['input'], @operation['input']['target'])}
|
57
|
+
options = {}
|
58
|
+
begin
|
59
|
+
response = client.#{@operation_name}(params, options)
|
60
|
+
rescue Smithy::Client::ServiceError => e
|
61
|
+
puts e.class #=> #{Model::Shape.name(error['shapeId'])}
|
62
|
+
puts e.data.to_h #=>
|
63
|
+
#{error_data(error['content'], error['shapeId'])}
|
64
|
+
end
|
65
|
+
EXAMPLE
|
66
|
+
end
|
67
|
+
|
68
|
+
def params(document, target, indent = ' ')
|
69
|
+
return '{}' if document.nil? || document.empty?
|
70
|
+
|
71
|
+
shape = Model.shape(@model, target)
|
72
|
+
structure(document, shape, indent).join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
def error_data(document, shape_id, indent = ' ')
|
76
|
+
shape = Model.shape(@model, shape_id)
|
77
|
+
structure(document, shape, indent).join("\n")
|
78
|
+
end
|
79
|
+
|
80
|
+
def documentation(example)
|
81
|
+
documentation = example.fetch('documentation', "Example operation for #{@operation_name}")
|
82
|
+
documentation.split("\n").map { |line| "# #{line}" }.join("\n")
|
83
|
+
end
|
84
|
+
|
85
|
+
def value(json, shape, indent)
|
86
|
+
case shape['type']
|
87
|
+
when 'structure' then structure(json, shape, indent)
|
88
|
+
when 'map' then map(json, shape, indent)
|
89
|
+
when 'list' then list(json, shape, indent)
|
90
|
+
when 'timestamp' then "Time.parse(#{json.inspect})"
|
91
|
+
when 'string', 'blob' then json.inspect
|
92
|
+
else json
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def structure(json, shape, indent)
|
97
|
+
lines = []
|
98
|
+
lines << '{'
|
99
|
+
json.each_pair do |key, value|
|
100
|
+
member_shape = Model.shape(@model, shape['members'][key]['target'])
|
101
|
+
entry = value(value, member_shape, "#{indent} ")
|
102
|
+
lines << "#{indent} #{key.underscore}: #{format(entry)},"
|
103
|
+
end
|
104
|
+
lines.last.chomp!(',') if lines.last.end_with?(',')
|
105
|
+
lines << "#{indent}}"
|
106
|
+
lines
|
107
|
+
end
|
108
|
+
|
109
|
+
def map(json, shape, indent)
|
110
|
+
lines = []
|
111
|
+
lines << '{'
|
112
|
+
json.each do |key, value|
|
113
|
+
member_shape = Model.shape(@model, shape['value']['target'])
|
114
|
+
entry = value(value, member_shape, "#{indent} ")
|
115
|
+
lines << "#{indent} \"#{key}\" => #{format(entry)},"
|
116
|
+
end
|
117
|
+
lines.last.chomp!(',') if lines.last.end_with?(',')
|
118
|
+
lines << "#{indent}}"
|
119
|
+
lines
|
120
|
+
end
|
121
|
+
|
122
|
+
def list(json, shape, indent)
|
123
|
+
lines = []
|
124
|
+
lines << '['
|
125
|
+
json.each do |element|
|
126
|
+
member_shape = Model.shape(@model, shape['member']['target'])
|
127
|
+
entry = value(element, member_shape, "#{indent} ")
|
128
|
+
lines << "#{indent} #{format(entry)},"
|
129
|
+
end
|
130
|
+
lines.last.chomp!(',') if lines.last.end_with?(',')
|
131
|
+
lines << "#{indent}]"
|
132
|
+
lines
|
133
|
+
end
|
134
|
+
|
135
|
+
def format(shape_val)
|
136
|
+
formatted = []
|
137
|
+
if shape_val.is_a?(Array)
|
138
|
+
hashes = shape_val.map do |v|
|
139
|
+
(v.is_a?(Hash) ? format_hash(v, ' ') : v)
|
140
|
+
end
|
141
|
+
hashes.join(',')
|
142
|
+
formatted << hashes
|
143
|
+
elsif shape_val.is_a?(Hash)
|
144
|
+
formatted << format_hash(shape_val, ' ')
|
145
|
+
else
|
146
|
+
formatted << shape_val
|
147
|
+
end
|
148
|
+
formatted.join("\n")
|
149
|
+
end
|
150
|
+
|
151
|
+
def format_hash(value, indent)
|
152
|
+
Util::HashFormatter.new(indent: indent).format(value)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Views
|
5
|
+
module Client
|
6
|
+
# @api private
|
7
|
+
class Paginators < View
|
8
|
+
def initialize(plan)
|
9
|
+
@plan = plan
|
10
|
+
@model = plan.model
|
11
|
+
_, service = plan.service.first
|
12
|
+
@service_trait = paginated_trait(service)
|
13
|
+
super()
|
14
|
+
end
|
15
|
+
|
16
|
+
def module_name
|
17
|
+
@plan.module_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def paginators
|
21
|
+
Model::ServiceIndex
|
22
|
+
.new(@model)
|
23
|
+
.operations_for(@plan.service)
|
24
|
+
.map do |id, operation|
|
25
|
+
operation_trait = paginated_trait(operation)
|
26
|
+
next if operation_trait.empty?
|
27
|
+
|
28
|
+
resolved_trait = @service_trait.merge(operation_trait)
|
29
|
+
Paginator.new(Model::Shape.name(id), resolved_trait)
|
30
|
+
end
|
31
|
+
.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def paginated_trait(shape)
|
37
|
+
shape.fetch('traits', {}).fetch('smithy.api#paginated', {})
|
38
|
+
end
|
39
|
+
|
40
|
+
# @api private
|
41
|
+
class Paginator
|
42
|
+
def initialize(name, trait)
|
43
|
+
@name = name
|
44
|
+
@input_token = trait['inputToken']
|
45
|
+
@output_token = trait['outputToken']
|
46
|
+
@items = trait['items']
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :name
|
50
|
+
|
51
|
+
def next_tokens_code
|
52
|
+
next_token_getter = output_getter(@output_token)
|
53
|
+
code = ["next_token = #{next_token_getter}"]
|
54
|
+
code << 'return {} if next_token.nil? || next_token.empty?'
|
55
|
+
code << ''
|
56
|
+
code << 'tokens = Hash.new { |h, k| h[k] = {} }'
|
57
|
+
next_token_setter = input_getter(@input_token, 'tokens')
|
58
|
+
code << "#{next_token_setter} = next_token"
|
59
|
+
code << 'tokens'
|
60
|
+
code
|
61
|
+
end
|
62
|
+
|
63
|
+
def prev_tokens_code
|
64
|
+
prev_token_getter = input_getter(@input_token)
|
65
|
+
code = ["prev_token = #{prev_token_getter}"]
|
66
|
+
code << 'return {} if prev_token.nil? || prev_token.empty?'
|
67
|
+
code << ''
|
68
|
+
code << 'tokens = Hash.new { |h, k| h[k] = {} }'
|
69
|
+
prev_token_setter = input_getter(@input_token, 'tokens')
|
70
|
+
code << "#{prev_token_setter} = prev_token"
|
71
|
+
code << 'tokens'
|
72
|
+
code
|
73
|
+
end
|
74
|
+
|
75
|
+
def items_code
|
76
|
+
return ["raise NotImplementedError, 'item iteration is not implemented for this operation'"] unless items?
|
77
|
+
|
78
|
+
[output_getter(@items)]
|
79
|
+
end
|
80
|
+
|
81
|
+
def items?
|
82
|
+
!!@items
|
83
|
+
end
|
84
|
+
|
85
|
+
# Builds a getter using the output shape and a path.
|
86
|
+
# This is used to get the value of the output token or items.
|
87
|
+
def output_getter(path)
|
88
|
+
getter = String.new('data')
|
89
|
+
path.split('.').each do |member|
|
90
|
+
getter << ".#{member.underscore}"
|
91
|
+
end
|
92
|
+
getter
|
93
|
+
end
|
94
|
+
|
95
|
+
# Builds a getter using the input shape and a path.
|
96
|
+
# This is used to set the value of the input token or fetch the previous token.
|
97
|
+
def input_getter(path, context = 'params')
|
98
|
+
getter = String.new(context)
|
99
|
+
path.split('.').each do |member|
|
100
|
+
getter << "[:#{member.underscore}]"
|
101
|
+
end
|
102
|
+
getter
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Views
|
5
|
+
module Client
|
6
|
+
# @api private
|
7
|
+
class Plugin
|
8
|
+
def initialize(options = {})
|
9
|
+
@class_name = options[:class_name]
|
10
|
+
@require_path = options[:require_path]
|
11
|
+
@require_relative = options.fetch(:require_relative, false)
|
12
|
+
@source = options[:source]
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :class_name, :require_path, :source
|
16
|
+
|
17
|
+
def options
|
18
|
+
klass = @class_name
|
19
|
+
klass = Object.const_get(@class_name) if @class_name.is_a?(String)
|
20
|
+
klass.options
|
21
|
+
end
|
22
|
+
|
23
|
+
def require_relative?
|
24
|
+
@require_relative
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Views
|
5
|
+
module Client
|
6
|
+
# @api private
|
7
|
+
class PluginList
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(plan, code_generated_plugins)
|
11
|
+
@plan = plan
|
12
|
+
@plugins = plugins(plan, code_generated_plugins)
|
13
|
+
end
|
14
|
+
|
15
|
+
def each(&)
|
16
|
+
@plugins.each(&)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def plugins(plan, code_generated_plugins)
|
22
|
+
plugins = []
|
23
|
+
code_generated_plugins(plugins, code_generated_plugins)
|
24
|
+
weld_plugins(plugins, plan.welds)
|
25
|
+
plugins
|
26
|
+
end
|
27
|
+
|
28
|
+
def code_generated_plugins(plugins, code_generated_plugins)
|
29
|
+
define_module_names
|
30
|
+
code_generated_plugins.each do |_, plugin| # rubocop:disable Style/HashEachMethods
|
31
|
+
Object.module_eval(plugin.source)
|
32
|
+
plugins << plugin
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Code generated plugins may have nested namespaces, so we need to ensure
|
37
|
+
# that they are defined before we try to evaluate the source.
|
38
|
+
def define_module_names
|
39
|
+
parent = Object
|
40
|
+
@plan.module_name.split('::') do |mod|
|
41
|
+
child = mod
|
42
|
+
parent.const_set(child, ::Module.new) unless parent.const_defined?(child)
|
43
|
+
parent = parent.const_get(child)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def weld_plugins(plugins, welds)
|
48
|
+
weld_plugins = welds.map(&:add_plugins).reduce({}, :merge)
|
49
|
+
weld_plugins = weld_plugins.except(*welds.map(&:remove_plugins).reduce([], :+))
|
50
|
+
weld_plugins.each do |class_name, options|
|
51
|
+
plugins << Plugin.new(class_name: class_name, **options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Views
|
5
|
+
module Client
|
6
|
+
# @api private
|
7
|
+
class ProtocolSpec < View
|
8
|
+
PROTOCOL_TEST_TRAITS = %w[smithy.test#httpRequestTests smithy.test#httpResponseTests].freeze
|
9
|
+
|
10
|
+
def initialize(plan)
|
11
|
+
@plan = plan
|
12
|
+
@model = plan.model
|
13
|
+
@all_operation_tests =
|
14
|
+
Model::ServiceIndex
|
15
|
+
.new(@model)
|
16
|
+
.operations_for(@plan.service)
|
17
|
+
.map { |id, o| OperationTests.new(@model, id, o) }
|
18
|
+
.reject(&:empty?)
|
19
|
+
super()
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :all_operation_tests
|
23
|
+
|
24
|
+
def module_name
|
25
|
+
@plan.module_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def additional_requires
|
29
|
+
Set.new(@all_operation_tests.map(&:additional_requires).flatten)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
class OperationTests
|
34
|
+
def initialize(model, id, operation)
|
35
|
+
@model = model
|
36
|
+
@id = id
|
37
|
+
@operation = operation
|
38
|
+
@request_tests = build_request_tests
|
39
|
+
@response_tests = build_response_tests
|
40
|
+
@error_tests = build_error_tests
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :request_tests, :response_tests, :error_tests
|
44
|
+
|
45
|
+
def name
|
46
|
+
Model::Shape.name(@id).underscore
|
47
|
+
end
|
48
|
+
|
49
|
+
def additional_requires
|
50
|
+
@request_tests.map(&:additional_requires) + @response_tests.map(&:additional_requires)
|
51
|
+
end
|
52
|
+
|
53
|
+
def empty?
|
54
|
+
request_tests.empty? && response_tests.empty? && error_tests.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def build_response_tests
|
60
|
+
@operation
|
61
|
+
.fetch('traits', {})
|
62
|
+
.fetch('smithy.test#httpResponseTests', [])
|
63
|
+
.select { |t| t.fetch('appliesTo', 'client') == 'client' }
|
64
|
+
.map { |t| ResponseTest.new(@model, @operation, t) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_request_tests
|
68
|
+
@operation
|
69
|
+
.fetch('traits', {})
|
70
|
+
.fetch('smithy.test#httpRequestTests', [])
|
71
|
+
.select { |t| t.fetch('appliesTo', 'client') == 'client' }
|
72
|
+
.map { |t| RequestTest.new(@model, @operation, t) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_error_tests
|
76
|
+
@operation
|
77
|
+
.fetch('errors', [])
|
78
|
+
.map { |e| tests_for_error(e) }
|
79
|
+
.flatten
|
80
|
+
end
|
81
|
+
|
82
|
+
def tests_for_error(error)
|
83
|
+
error_shape = Model.shape(@model, error['target'])
|
84
|
+
# NOTE: error tests do not have a appliesTo that should be checked
|
85
|
+
error_shape
|
86
|
+
.fetch('traits', {})
|
87
|
+
.fetch('smithy.test#httpResponseTests', [])
|
88
|
+
.map { |t| ErrorTest.new(@model, @operation, error['target'], t) }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
class TestCase
|
94
|
+
def initialize(model, operation, test_case)
|
95
|
+
@model = model
|
96
|
+
@operation = operation
|
97
|
+
@test_case = test_case
|
98
|
+
@input_shape = Model.shape(@model, @operation['input']['target'])
|
99
|
+
@output_shape = Model.shape(@model, @operation['output']['target'])
|
100
|
+
end
|
101
|
+
|
102
|
+
attr_reader :test_case
|
103
|
+
|
104
|
+
def [](key)
|
105
|
+
test_case[key]
|
106
|
+
end
|
107
|
+
|
108
|
+
def comments
|
109
|
+
test_case.fetch('documentation', '').split("\n")
|
110
|
+
end
|
111
|
+
|
112
|
+
def id
|
113
|
+
test_case['id']
|
114
|
+
end
|
115
|
+
|
116
|
+
def additional_requires
|
117
|
+
requires = []
|
118
|
+
if test_case['bodyMediaType']
|
119
|
+
requires +=
|
120
|
+
case test_case['bodyMediaType']
|
121
|
+
when 'application/cbor'
|
122
|
+
%w[base64]
|
123
|
+
when 'application/json'
|
124
|
+
%w[json]
|
125
|
+
else
|
126
|
+
[]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
requires
|
130
|
+
end
|
131
|
+
|
132
|
+
def skip?
|
133
|
+
@operation
|
134
|
+
.fetch('traits', {})
|
135
|
+
.fetch('smithy.ruby#skipTests', [])
|
136
|
+
.any? { |skip| skip['id'] == id }
|
137
|
+
end
|
138
|
+
|
139
|
+
def skip_reason
|
140
|
+
@operation
|
141
|
+
.fetch('traits', {})
|
142
|
+
.fetch('smithy.ruby#skipTests', [])
|
143
|
+
.find { |skip| skip['id'] == id }
|
144
|
+
&.fetch('reason', 'skipped')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# @api private
|
149
|
+
class RequestTest < TestCase
|
150
|
+
def params
|
151
|
+
ShapeToHash.transform_value(@model, test_case.fetch('params', {}), @input_shape)
|
152
|
+
end
|
153
|
+
|
154
|
+
def endpoint
|
155
|
+
"https://#{test_case.fetch('host', '127.0.0.1')}"
|
156
|
+
end
|
157
|
+
|
158
|
+
def body_expect
|
159
|
+
return nil unless test_case['body']
|
160
|
+
|
161
|
+
case test_case['bodyMediaType']
|
162
|
+
when 'application/cbor'
|
163
|
+
'expect(Smithy::CBOR.decode(request.body.read)).' \
|
164
|
+
"to match_data(Smithy::CBOR.decode(::Base64.decode64('#{test_case['body']}')))"
|
165
|
+
when 'application/json'
|
166
|
+
"expect(JSON.parse(request.body.read)).to eq(JSON.parse('#{test_case['body']}'))"
|
167
|
+
else
|
168
|
+
"expect(request.body.read).to eq('#{test_case['body']}')"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def query_expect?
|
173
|
+
test_case['queryParams'] || test_case['forbidQueryParams'] || test_case['requireQueryParams']
|
174
|
+
end
|
175
|
+
|
176
|
+
def idempotency_token_trait?
|
177
|
+
@input_shape.fetch('members', {})
|
178
|
+
.any? { |_name, shape| shape.fetch('traits', {}).key?('smithy.api#idempotencyToken') }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# @api private
|
183
|
+
class ResponseTest < TestCase
|
184
|
+
def params
|
185
|
+
# Finds all required members to pass operation validation
|
186
|
+
ShapeToHash.transform_value(@model, structure(@input_shape, {}), @input_shape)
|
187
|
+
end
|
188
|
+
|
189
|
+
def shape(shape, value)
|
190
|
+
case shape['type']
|
191
|
+
when 'structure' then structure(shape, value)
|
192
|
+
else value
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def structure(shape, values)
|
197
|
+
shape['members'].each_with_object({}) do |(member_name, member_shape), data|
|
198
|
+
next unless required?(member_shape.fetch('traits', {}))
|
199
|
+
|
200
|
+
target = Model.shape(@model, member_shape['target'])
|
201
|
+
data[member_name] = shape(target, values)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def required?(traits)
|
206
|
+
traits.key?('smithy.api#required') && !traits.key?('smithy.api#clientOptional')
|
207
|
+
end
|
208
|
+
|
209
|
+
def stub_body
|
210
|
+
case test_case['bodyMediaType']
|
211
|
+
when 'application/cbor'
|
212
|
+
"::Base64.decode64('#{test_case['body']}')"
|
213
|
+
else
|
214
|
+
"'#{test_case['body']}'"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def data_expect
|
219
|
+
output = ShapeToHash.transform_value(@model, test_case.fetch('params', {}), @output_shape)
|
220
|
+
"expect(response.data.to_h).to match_data(#{output})"
|
221
|
+
end
|
222
|
+
|
223
|
+
def streaming_member
|
224
|
+
@output_shape
|
225
|
+
.fetch('members', {})
|
226
|
+
.map { |name, member| [name, Model.shape(@model, member['target'])] }
|
227
|
+
.find { |_name, shape| shape.fetch('traits', {}).key?('smithy.api#streaming') }
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# @api private
|
232
|
+
class ErrorTest < ResponseTest
|
233
|
+
def initialize(model, operation, error_id, test_case)
|
234
|
+
super(model, operation, test_case)
|
235
|
+
@error_id = error_id
|
236
|
+
@error_shape = Model.shape(@model, error_id)
|
237
|
+
end
|
238
|
+
|
239
|
+
def error_name
|
240
|
+
Model::Shape.name(@error_id)
|
241
|
+
end
|
242
|
+
|
243
|
+
def params
|
244
|
+
ShapeToHash.transform_value(@model, test_case.fetch('params', {}), @error_shape)
|
245
|
+
end
|
246
|
+
|
247
|
+
def data_expect
|
248
|
+
"expect(e.data.to_h).to match_data(#{params})"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|