aws-sdk-code-generator 0.2.4.pre → 0.4.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-sdk-code-generator/client_constructor.rb +4 -1
  3. data/lib/aws-sdk-code-generator/client_operation_documentation.rb +10 -4
  4. data/lib/aws-sdk-code-generator/code_builder.rb +78 -9
  5. data/lib/aws-sdk-code-generator/codegenerated_plugin.rb +34 -0
  6. data/lib/aws-sdk-code-generator/gem_builder.rb +4 -5
  7. data/lib/aws-sdk-code-generator/plugin_list.rb +29 -9
  8. data/lib/aws-sdk-code-generator/service.rb +22 -0
  9. data/lib/aws-sdk-code-generator/views/async_client_class.rb +6 -2
  10. data/lib/aws-sdk-code-generator/views/client_api_module.rb +18 -2
  11. data/lib/aws-sdk-code-generator/views/client_class.rb +7 -2
  12. data/lib/aws-sdk-code-generator/views/endpoint_parameters_class.rb +80 -0
  13. data/lib/aws-sdk-code-generator/views/endpoint_provider_class.rb +253 -0
  14. data/lib/aws-sdk-code-generator/views/endpoints_module.rb +177 -0
  15. data/lib/aws-sdk-code-generator/views/endpoints_plugin.rb +85 -0
  16. data/lib/aws-sdk-code-generator/views/gemspec.rb +4 -0
  17. data/lib/aws-sdk-code-generator/views/service_module.rb +15 -1
  18. data/lib/aws-sdk-code-generator/views/spec/endpoint_provider_spec_class.rb +198 -0
  19. data/lib/aws-sdk-code-generator/views/spec/spec_helper.rb +9 -4
  20. data/lib/aws-sdk-code-generator/views/types_module.rb +8 -7
  21. data/lib/aws-sdk-code-generator.rb +6 -0
  22. data/templates/async_client_class.mustache +2 -2
  23. data/templates/client_api_module.mustache +7 -0
  24. data/templates/client_class.mustache +4 -1
  25. data/templates/endpoint_parameters_class.mustache +50 -0
  26. data/templates/endpoint_provider_class.mustache +12 -0
  27. data/templates/endpoints_module.mustache +33 -0
  28. data/templates/endpoints_plugin.mustache +76 -0
  29. data/templates/gemspec.mustache +1 -1
  30. data/templates/spec/endpoint_provider_spec_class.mustache +76 -0
  31. data/templates/spec/spec_helper.mustache +5 -0
  32. metadata +14 -3
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSdkCodeGenerator
4
+ module Views
5
+ class EndpointProviderClass < View
6
+
7
+ # @option options [required, Service] :service
8
+ # @option options [required, Hash] :endpoint_rules
9
+ def initialize(options)
10
+ @service = options.fetch(:service)
11
+ @endpoint_rules = @service.endpoint_rules
12
+
13
+ version = @endpoint_rules['version']
14
+ return if version&.match(/^\d+\.\d+$/) # && version == '1.0'
15
+
16
+ raise 'Endpoint Rules version must be 1.0'
17
+ end
18
+
19
+ # @return [String|nil]
20
+ def generated_src_warning
21
+ return if @service.protocol == 'api-gateway'
22
+ GENERATED_SRC_WARNING
23
+ end
24
+
25
+ def module_name
26
+ @service.module_name
27
+ end
28
+
29
+ def endpoint_rules_code
30
+ res = StringIO.new
31
+ # map parameters first
32
+ @endpoint_rules["parameters"].each do |k,_v|
33
+ res << indent("#{underscore(k)} = parameters.#{underscore(k)}\n", 3)
34
+ end
35
+
36
+ # map rules
37
+ @endpoint_rules["rules"].each do |rule|
38
+ case rule["type"]
39
+ when "endpoint"
40
+ res << endpoint_rule(rule, 3)
41
+ when "error"
42
+ res << error_rule(rule, 3)
43
+ when "tree"
44
+ res << tree_rule(rule, 3)
45
+ else
46
+ raise "Unknown rule type: #{rule['type']}"
47
+ end
48
+ end
49
+
50
+ # if no rules match, raise an error
51
+ res << indent("raise ArgumentError, 'No endpoint could be resolved'\n", 3)
52
+
53
+ res.string
54
+ end
55
+
56
+ private
57
+
58
+ def endpoint_rule(rule, levels=3)
59
+ res = StringIO.new
60
+ if rule['conditions'] && !rule['conditions'].empty?
61
+ res << conditions(rule['conditions'], levels)
62
+ res << endpoint(rule['endpoint'], levels+1)
63
+ res << indent("end\n", levels)
64
+ else
65
+ res << endpoint(rule['endpoint'], levels)
66
+ end
67
+ res.string
68
+ end
69
+
70
+ def endpoint(endpoint, levels)
71
+ res = StringIO.new
72
+ res << "return Aws::Endpoints::Endpoint.new(url: #{str(endpoint['url'])}"
73
+ if endpoint['headers']
74
+ res << ", headers: #{templated_hash_to_s(endpoint['headers'])}"
75
+ end
76
+ if endpoint['properties']
77
+ res << ", properties: #{templated_hash_to_s(endpoint['properties'])}"
78
+ end
79
+ res << ")\n"
80
+ indent(res.string, levels)
81
+ end
82
+
83
+ def templated_hash_to_s(hash)
84
+ template_hash_values(hash).to_s.gsub('\#{', '#{') # unescape references
85
+ end
86
+
87
+ def template_hash_values(hash)
88
+ hash.inject({}) { |h, (k, v)| h[k] = template_hash_value(v); h }
89
+ end
90
+
91
+ def template_hash_value(value)
92
+ case value
93
+ when Hash
94
+ template_hash_values(value)
95
+ when Array
96
+ value.map { |v| template_hash_value(v) }
97
+ when String
98
+ template_str(value, false)
99
+ else
100
+ value
101
+ end
102
+ end
103
+
104
+ def error_rule(rule, levels=3)
105
+ res = StringIO.new
106
+ if rule['conditions'] && !rule['conditions'].empty?
107
+ res << conditions(rule['conditions'], levels)
108
+ res << error(rule['error'], levels+1)
109
+ res << indent("end\n", levels)
110
+ else
111
+ res << error(rule['error'], levels)
112
+ end
113
+ res.string
114
+ end
115
+
116
+ def error(error, levels)
117
+ indent("raise ArgumentError, #{str(error)}\n", levels)
118
+ end
119
+
120
+ def tree_rule(rule, levels=3)
121
+ res = StringIO.new
122
+ if rule['conditions'] && !rule['conditions'].empty?
123
+ res << conditions(rule['conditions'], levels)
124
+ res << tree_rules(rule['rules'], levels+1)
125
+ res << indent("end\n", levels)
126
+ else
127
+ res << tree_rules(rule['rules'], levels)
128
+ end
129
+ res.string
130
+ end
131
+
132
+ def tree_rules(rules, levels)
133
+ res = StringIO.new
134
+ rules.each do |rule|
135
+ case rule["type"]
136
+ when "endpoint"
137
+ res << endpoint_rule(rule, levels)
138
+ when "error"
139
+ res << error_rule(rule, levels)
140
+ when "tree"
141
+ res << tree_rule(rule, levels)
142
+ else
143
+ raise "Unknown rule type: #{rule['type']}"
144
+ end
145
+ end
146
+ res.string
147
+ end
148
+
149
+ def conditions(conditions, level)
150
+ res = StringIO.new
151
+ cnd_str = conditions.map { |c| condition(c) }.join(" && ")
152
+ res << indent("if #{cnd_str}\n", level)
153
+ res.string
154
+ end
155
+
156
+ def condition(condition)
157
+ if condition['assign']
158
+ "(#{underscore(condition['assign'])} = #{fn(condition)})"
159
+ else
160
+ fn(condition)
161
+ end
162
+ end
163
+
164
+ def str(s)
165
+ if s.is_a?(Hash)
166
+ if s['ref']
167
+ underscore(s['ref'])
168
+ elsif s['fn']
169
+ fn(s)
170
+ else
171
+ raise "Unknown string type: #{s}"
172
+ end
173
+ else
174
+ template_str(s)
175
+ end
176
+ end
177
+
178
+ def template_str(string, wrap=true)
179
+ string.scan(/\{.+?\}/).each do |capture|
180
+ value = capture[1..-2] # strips curly brackets
181
+ string = string.gsub(capture, '#{' + template_replace(value) + '}')
182
+ end
183
+ string = string.gsub('"', '\"')
184
+ wrap ? "\"#{string}\"" : string
185
+ end
186
+
187
+ def template_replace(value)
188
+ indexes = value.split("#")
189
+ res = underscore(indexes.shift)
190
+ res += indexes.map do |index|
191
+ "['#{index}']"
192
+ end.join("")
193
+ res
194
+ end
195
+
196
+ def fn(fn)
197
+ args = fn['argv'].map {|arg| fn_arg(arg)}.join(", ")
198
+ "#{fn_name(fn['fn'])}(#{args})"
199
+ end
200
+
201
+ def fn_arg(arg)
202
+ if arg.is_a?(Hash)
203
+ if arg['ref']
204
+ underscore(arg['ref'])
205
+ elsif arg['fn']
206
+ fn(arg)
207
+ else
208
+ raise "Unexpected argument type: #{arg}"
209
+ end
210
+ elsif arg.is_a?(String)
211
+ template_str(arg)
212
+ else
213
+ arg
214
+ end
215
+ end
216
+
217
+ def fn_name(fn)
218
+ case fn
219
+ when 'isSet'
220
+ "Aws::Endpoints::Matchers.set?"
221
+ when 'not'
222
+ "Aws::Endpoints::Matchers.not"
223
+ when 'getAttr'
224
+ "Aws::Endpoints::Matchers.attr"
225
+ when 'substring'
226
+ "Aws::Endpoints::Matchers.substring"
227
+ when 'stringEquals'
228
+ "Aws::Endpoints::Matchers.string_equals?"
229
+ when 'booleanEquals'
230
+ "Aws::Endpoints::Matchers.boolean_equals?"
231
+ when 'uriEncode'
232
+ "Aws::Endpoints::Matchers.uri_encode"
233
+ when 'parseURL'
234
+ "Aws::Endpoints::Matchers.parse_url"
235
+ when 'isValidHostLabel'
236
+ "Aws::Endpoints::Matchers.valid_host_label?"
237
+ when 'aws.partition'
238
+ "Aws::Endpoints::Matchers.aws_partition"
239
+ when 'aws.parseArn'
240
+ "Aws::Endpoints::Matchers.aws_parse_arn"
241
+ when 'aws.isVirtualHostableS3Bucket'
242
+ "Aws::Endpoints::Matchers.aws_virtual_hostable_s3_bucket?"
243
+ else
244
+ raise "Function not found: #{@fn}"
245
+ end
246
+ end
247
+
248
+ def indent(s, levels=3)
249
+ (" " * levels) + s
250
+ end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSdkCodeGenerator
4
+ module Views
5
+ class EndpointsModule < View
6
+ # @option options [required, Service] :service
7
+ def initialize(options)
8
+ @service = options.fetch(:service)
9
+
10
+ @parameters = @service.endpoint_rules.fetch('parameters', {})
11
+
12
+ @endpoint_classes = @service.api['operations'].each.with_object([]) do
13
+ |(name, op), array|
14
+ array << EndpointClass.new(
15
+ name: name,
16
+ parameters: endpoint_parameters_for_operation(op)
17
+ )
18
+ end
19
+ end
20
+
21
+ # @return [Array<EndpointClass>]
22
+ attr_reader :endpoint_classes
23
+
24
+ # @return [String|nil]
25
+ def generated_src_warning
26
+ return if @service.protocol == 'api-gateway'
27
+ GENERATED_SRC_WARNING
28
+ end
29
+
30
+ def module_name
31
+ @service.module_name
32
+ end
33
+
34
+ class EndpointClass
35
+ def initialize(options)
36
+ @name = options[:name]
37
+ @parameters = options[:parameters]
38
+ end
39
+
40
+ # @return [String]
41
+ attr_reader :name
42
+
43
+ # @return [Array<EndpointParameter>]
44
+ attr_reader :parameters
45
+
46
+ def has_endpoint_built_in?
47
+ parameters.any? { |p| p.param_data['builtIn'] == 'SDK::Endpoint' }
48
+ end
49
+ end
50
+
51
+ class EndpointParameter
52
+ def initialize(options)
53
+ @key = options[:key]
54
+ @value = options[:value]
55
+ @source = options[:source]
56
+ @param_data = options[:param_data]
57
+ end
58
+
59
+ # @return [String]
60
+ attr_accessor :key
61
+
62
+ # @return [String]
63
+ attr_accessor :value
64
+
65
+ # @return [String]
66
+ attr_accessor :source
67
+
68
+ # @return [Hash]
69
+ attr_accessor :param_data
70
+
71
+ def static_string?
72
+ @source == 'staticContextParam' && value.is_a?(String)
73
+ end
74
+ end
75
+
76
+
77
+ private
78
+
79
+ def endpoint_parameters_for_operation(operation)
80
+ @parameters.each.with_object([]) do |(param_name, param_data), endpoint_parameters|
81
+ value, source = endpoint_parameter_value(
82
+ operation, param_name, param_data
83
+ )
84
+
85
+ endpoint_parameters << EndpointParameter.new(
86
+ key: Underscore.underscore(param_name),
87
+ value: value,
88
+ source: source,
89
+ param_data: param_data
90
+ )
91
+ end
92
+ end
93
+
94
+ # Most to least
95
+ # staticContextParams
96
+ # contextParam
97
+ # clientContextParams
98
+ # Built-In Bindings
99
+ # Built-in binding default values
100
+ def endpoint_parameter_value(operation, param_name, param_data)
101
+ value, source = [
102
+ static_context_param(operation, param_name), 'staticContextParam'
103
+ ]
104
+ value, source = [
105
+ context_param_value(operation, param_name), 'contextParam'
106
+ ] unless value
107
+ value, source = [
108
+ client_context_param_value(param_name, param_data),
109
+ 'clientContextParam'
110
+ ] unless value
111
+ value, source = [
112
+ built_in_client_context_param_value(param_data), 'builtIn'
113
+ ] unless value
114
+
115
+ [value || 'nil', source]
116
+ end
117
+
118
+ def client_context_param_value(param_name, param_data)
119
+ if @service.api['clientContextParams']&.key?(param_name) &&
120
+ !param_data['builtIn']
121
+ "context.config.#{Underscore.underscore(param_name)}"
122
+ end
123
+ end
124
+
125
+ def built_in_client_context_param_value(param_data)
126
+ case param_data['builtIn']
127
+ when 'AWS::Region'
128
+ 'context.config.region'
129
+ when 'AWS::UseFIPS'
130
+ 'context.config.use_fips_endpoint'
131
+ when 'AWS::UseDualStack'
132
+ if @service.name == 'S3' || @service.name == 'S3Control'
133
+ 'context[:use_dualstack_endpoint]'
134
+ else
135
+ 'context.config.use_dualstack_endpoint'
136
+ end
137
+ when 'AWS::STS::UseGlobalEndpoint'
138
+ "context.config.sts_regional_endpoints == 'legacy'"
139
+ when 'AWS::S3::UseGlobalEndpoint'
140
+ "context.config.s3_us_east_1_regional_endpoint == 'legacy'"
141
+ when 'AWS::S3::Accelerate'
142
+ if @service.name == 'S3' || @service.name == 'S3Control'
143
+ 'context[:use_accelerate_endpoint]'
144
+ else
145
+ 'context.config.use_accelerate_endpoint'
146
+ end
147
+ when 'AWS::S3::ForcePathStyle'
148
+ 'context.config.force_path_style'
149
+ when 'AWS::S3::UseArnRegion', 'AWS::S3Control::UseArnRegion'
150
+ 'context.config.s3_use_arn_region'
151
+ when 'AWS::S3::DisableMultiRegionAccessPoints'
152
+ 'context.config.s3_disable_multiregion_access_points'
153
+ when 'SDK::Endpoint'
154
+ 'endpoint'
155
+ end
156
+ end
157
+
158
+ def context_param_value(operation, param_name)
159
+ return nil unless operation['input']
160
+
161
+ input_shape = operation['input']['shape']
162
+ members = @service.api['shapes'][input_shape].fetch('members', {})
163
+ members.detect do |(member_name, member)|
164
+ context_param = member.fetch('contextParam', {})
165
+ if context_param.fetch('name', nil) == param_name
166
+ break "context.params[:#{Underscore.underscore(member_name)}]"
167
+ end
168
+ end
169
+ end
170
+
171
+ def static_context_param(operation, param_name)
172
+ operation.fetch('staticContextParams', {})
173
+ .fetch(param_name, {}).fetch('value', nil)
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSdkCodeGenerator
4
+ module Views
5
+ class EndpointsPlugin < View
6
+ # @option options [required, Service] :service
7
+ def initialize(options)
8
+ @service = options.fetch(:service)
9
+ if (client_options = @service.api['clientContextParams'])
10
+ endpoint_parameters = @service.endpoint_rules.fetch('parameters', {})
11
+
12
+ @endpoint_options = client_options.each.with_object([]) do |(name, _data), array|
13
+ param_data = endpoint_parameters[name]
14
+
15
+ next if param_data['builtIn']
16
+
17
+ array << EndpointOption.new(
18
+ name: Underscore.underscore(name),
19
+ docstring: param_data['documentation'],
20
+ doc_type: param_data['type'],
21
+ default: param_data['default']
22
+ )
23
+ end
24
+ end
25
+ @endpoint_classes = @service.api['operations'].each.with_object([]) do
26
+ |(op, _api), array|
27
+ array << EndpointClass.new(
28
+ operation_name: Underscore.underscore(op),
29
+ class_name: op
30
+ )
31
+ end
32
+ end
33
+
34
+ # @return [Array<EndpointClass>]
35
+ attr_reader :endpoint_classes
36
+
37
+ # @return [Array<EndpointOption>, nil]
38
+ attr_reader :endpoint_options
39
+
40
+ # @return [String|nil]
41
+ def generated_src_warning
42
+ return if @service.protocol == 'api-gateway'
43
+ GENERATED_SRC_WARNING
44
+ end
45
+
46
+ def module_name
47
+ @service.module_name
48
+ end
49
+
50
+ class EndpointClass
51
+ def initialize(options)
52
+ @operation_name = options[:operation_name]
53
+ @class_name = options[:class_name]
54
+ end
55
+
56
+ # @return [String]
57
+ attr_reader :operation_name
58
+
59
+ # @return [String]
60
+ attr_reader :class_name
61
+ end
62
+
63
+ class EndpointOption
64
+ def initialize(options)
65
+ @name = options[:name]
66
+ @doc_type = options[:doc_type]
67
+ @default = options[:default].nil? ? 'nil' : options[:default]
68
+ @docstring = options[:docstring]
69
+ end
70
+
71
+ # @return [String]
72
+ attr_reader :name
73
+
74
+ # @return [String]
75
+ attr_reader :doc_type
76
+
77
+ # @return [Boolean,String]
78
+ attr_reader :default
79
+
80
+ # @return [String]
81
+ attr_reader :docstring
82
+ end
83
+ end
84
+ end
85
+ end
@@ -42,6 +42,10 @@ module AwsSdkCodeGenerator
42
42
  @custom ? false : true
43
43
  end
44
44
 
45
+ def files
46
+ ['LICENSE.txt', 'CHANGELOG.md', 'VERSION', 'lib/**/*.rb']
47
+ end
48
+
45
49
  # @return [String]
46
50
  def description
47
51
  if @custom
@@ -11,6 +11,7 @@ module AwsSdkCodeGenerator
11
11
  def initialize(options)
12
12
  @service = options.fetch(:service)
13
13
  @prefix = options.fetch(:prefix)
14
+ @codegenerated_plugins = options.fetch(:codegenerated_plugins)
14
15
  end
15
16
 
16
17
  # @return [String|nil]
@@ -56,7 +57,7 @@ module AwsSdkCodeGenerator
56
57
  # This is required to support backwards compatibility for SSO which was
57
58
  # moved from the aws-sdk-sso gem into aws-sdk-core.
58
59
  def require_core_guard?
59
- name == 'SSO' || name == 'STS'
60
+ @service.included_in_core?
60
61
  end
61
62
 
62
63
  # @return [Array<String>]
@@ -64,10 +65,23 @@ module AwsSdkCodeGenerator
64
65
  paths = Set.new
65
66
  paths << "#{@prefix}/types"
66
67
  paths << "#{@prefix}/client_api"
68
+
69
+ # these must be required before the client
70
+ if @codegenerated_plugins
71
+ paths += @codegenerated_plugins.map { | p| p.path }
72
+ end
73
+
67
74
  paths << "#{@prefix}/client"
68
75
  paths << "#{@prefix}/errors"
69
76
  paths << "#{@prefix}/waiters" if @service.waiters
70
77
  paths << "#{@prefix}/resource"
78
+
79
+ unless @service.legacy_endpoints?
80
+ paths << "#{@prefix}/endpoint_parameters"
81
+ paths << "#{@prefix}/endpoint_provider"
82
+ paths << "#{@prefix}/endpoints"
83
+ end
84
+
71
85
  if @service.resources && @service.resources['resources']
72
86
  @service.resources['resources'].keys.each do |resource_name|
73
87
  path = "#{@prefix}/#{underscore(resource_name)}"