aws-sdk-code-generator 0.1.0.pre

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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/lib/aws-sdk-code-generator.rb +91 -0
  3. data/lib/aws-sdk-code-generator/apply_docs.rb +37 -0
  4. data/lib/aws-sdk-code-generator/code_builder.rb +201 -0
  5. data/lib/aws-sdk-code-generator/dsl/access_control_statement.rb +23 -0
  6. data/lib/aws-sdk-code-generator/dsl/attribute_accessor.rb +43 -0
  7. data/lib/aws-sdk-code-generator/dsl/attribute_reader.rb +11 -0
  8. data/lib/aws-sdk-code-generator/dsl/attribute_writer.rb +11 -0
  9. data/lib/aws-sdk-code-generator/dsl/autoload_statement.rb +15 -0
  10. data/lib/aws-sdk-code-generator/dsl/block_param.rb +11 -0
  11. data/lib/aws-sdk-code-generator/dsl/class.rb +27 -0
  12. data/lib/aws-sdk-code-generator/dsl/code_literal.rb +66 -0
  13. data/lib/aws-sdk-code-generator/dsl/code_object.rb +33 -0
  14. data/lib/aws-sdk-code-generator/dsl/docstring.rb +36 -0
  15. data/lib/aws-sdk-code-generator/dsl/eigenclass.rb +15 -0
  16. data/lib/aws-sdk-code-generator/dsl/extend_statement.rb +12 -0
  17. data/lib/aws-sdk-code-generator/dsl/formatter.rb +25 -0
  18. data/lib/aws-sdk-code-generator/dsl/include_statement.rb +17 -0
  19. data/lib/aws-sdk-code-generator/dsl/main.rb +105 -0
  20. data/lib/aws-sdk-code-generator/dsl/method.rb +108 -0
  21. data/lib/aws-sdk-code-generator/dsl/module.rb +167 -0
  22. data/lib/aws-sdk-code-generator/dsl/option_tag.rb +36 -0
  23. data/lib/aws-sdk-code-generator/dsl/param.rb +43 -0
  24. data/lib/aws-sdk-code-generator/dsl/param_list.rb +38 -0
  25. data/lib/aws-sdk-code-generator/dsl/return_tag.rb +19 -0
  26. data/lib/aws-sdk-code-generator/dsl/tag_default.rb +20 -0
  27. data/lib/aws-sdk-code-generator/dsl/tag_docstring.rb +27 -0
  28. data/lib/aws-sdk-code-generator/dsl/tag_type.rb +18 -0
  29. data/lib/aws-sdk-code-generator/errors.rb +30 -0
  30. data/lib/aws-sdk-code-generator/gem_builder.rb +71 -0
  31. data/lib/aws-sdk-code-generator/generators/client_api_module.rb +334 -0
  32. data/lib/aws-sdk-code-generator/generators/client_class.rb +389 -0
  33. data/lib/aws-sdk-code-generator/generators/client_operation_documentation.rb +166 -0
  34. data/lib/aws-sdk-code-generator/generators/errors_module.rb +25 -0
  35. data/lib/aws-sdk-code-generator/generators/resource/action.rb +88 -0
  36. data/lib/aws-sdk-code-generator/generators/resource/batch_builder.rb +211 -0
  37. data/lib/aws-sdk-code-generator/generators/resource/builder.rb +50 -0
  38. data/lib/aws-sdk-code-generator/generators/resource/client_getter.rb +15 -0
  39. data/lib/aws-sdk-code-generator/generators/resource/client_request.rb +49 -0
  40. data/lib/aws-sdk-code-generator/generators/resource/client_request_docs.rb +97 -0
  41. data/lib/aws-sdk-code-generator/generators/resource/client_request_params.rb +88 -0
  42. data/lib/aws-sdk-code-generator/generators/resource/collection_class.rb +180 -0
  43. data/lib/aws-sdk-code-generator/generators/resource/data_attribute_getter.rb +24 -0
  44. data/lib/aws-sdk-code-generator/generators/resource/data_loaded_method.rb +18 -0
  45. data/lib/aws-sdk-code-generator/generators/resource/data_method.rb +49 -0
  46. data/lib/aws-sdk-code-generator/generators/resource/exists_method.rb +29 -0
  47. data/lib/aws-sdk-code-generator/generators/resource/extract_identifier_method.rb +32 -0
  48. data/lib/aws-sdk-code-generator/generators/resource/has_association.rb +101 -0
  49. data/lib/aws-sdk-code-generator/generators/resource/has_many_association.rb +108 -0
  50. data/lib/aws-sdk-code-generator/generators/resource/identifier_getter.rb +26 -0
  51. data/lib/aws-sdk-code-generator/generators/resource/identifiers_method.rb +28 -0
  52. data/lib/aws-sdk-code-generator/generators/resource/initialize_method.rb +67 -0
  53. data/lib/aws-sdk-code-generator/generators/resource/load_method.rb +65 -0
  54. data/lib/aws-sdk-code-generator/generators/resource/value_source.rb +68 -0
  55. data/lib/aws-sdk-code-generator/generators/resource/waiter_method.rb +61 -0
  56. data/lib/aws-sdk-code-generator/generators/resource_class.rb +325 -0
  57. data/lib/aws-sdk-code-generator/generators/response_structure_example.rb +83 -0
  58. data/lib/aws-sdk-code-generator/generators/root_resource_class.rb +42 -0
  59. data/lib/aws-sdk-code-generator/generators/service_documentation.rb +64 -0
  60. data/lib/aws-sdk-code-generator/generators/shared_example.rb +132 -0
  61. data/lib/aws-sdk-code-generator/generators/structure_type_class.rb +95 -0
  62. data/lib/aws-sdk-code-generator/generators/syntax_example.rb +169 -0
  63. data/lib/aws-sdk-code-generator/generators/types_module.rb +52 -0
  64. data/lib/aws-sdk-code-generator/generators/waiter_class.rb +62 -0
  65. data/lib/aws-sdk-code-generator/generators/waiters_module.rb +20 -0
  66. data/lib/aws-sdk-code-generator/hash_formatter.rb +122 -0
  67. data/lib/aws-sdk-code-generator/helper.rb +215 -0
  68. data/lib/aws-sdk-code-generator/service.rb +126 -0
  69. data/lib/aws-sdk-code-generator/underscore.rb +45 -0
  70. data/lib/aws-sdk-code-generator/view.rb +23 -0
  71. data/lib/aws-sdk-code-generator/views.rb +3 -0
  72. data/lib/aws-sdk-code-generator/views/features/env.rb +24 -0
  73. data/lib/aws-sdk-code-generator/views/features/step_definitions.rb +20 -0
  74. data/lib/aws-sdk-code-generator/views/gemspec.rb +41 -0
  75. data/lib/aws-sdk-code-generator/views/service_module.rb +85 -0
  76. data/lib/aws-sdk-code-generator/views/spec/spec_helper.rb +24 -0
  77. data/lib/aws-sdk-code-generator/views/version.rb +16 -0
  78. metadata +120 -0
@@ -0,0 +1,334 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ class ClientApiModule < Dsl::Module
4
+
5
+ include Helper
6
+
7
+ SKIP_TRAITS = Set.new(%w(shape deprecated location locationName documentation))
8
+
9
+ SHAPE_CLASSES = {
10
+ 'blob' => 'BlobShape',
11
+ 'byte' => 'IntegerShape',
12
+ 'boolean' => 'BooleanShape',
13
+ 'character' => 'StringShape',
14
+ 'double' => 'FloatShape',
15
+ 'float' => 'FloatShape',
16
+ 'integer' => 'IntegerShape',
17
+ 'list' => 'ListShape',
18
+ 'long' => 'IntegerShape',
19
+ 'map' => 'MapShape',
20
+ 'string' => 'StringShape',
21
+ 'structure' => 'StructureShape',
22
+ 'timestamp' => 'TimestampShape',
23
+ }
24
+
25
+ SHAPE_KEYS = {
26
+ # keep
27
+ 'flattened' => true,
28
+ 'timestampFormat' => true, # glacier api customization
29
+ 'xmlNamespace' => true,
30
+ # ignore
31
+ 'box' => false,
32
+ 'fault' => false,
33
+ 'error' => false,
34
+ 'deprecated' => false,
35
+ 'type' => false,
36
+ 'documentation' => false,
37
+ 'members' => false,
38
+ 'member' => false,
39
+ 'key' => false,
40
+ 'locationName' => false,
41
+ 'value' => false,
42
+ 'required' => false,
43
+ 'streaming' => false,
44
+ 'enum' => false,
45
+ 'exception' => false,
46
+ 'payload' => false,
47
+ 'pattern' => false,
48
+ 'sensitive' => false,
49
+ 'min' => false,
50
+ 'max' => false,
51
+ 'wrapper' => false,
52
+ 'xmlOrder' => false,
53
+ }
54
+
55
+ METADATA_KEYS = {
56
+ # keep
57
+ 'endpointPrefix' => true,
58
+ 'signatureVersion' => true,
59
+ 'signingName' => true,
60
+ 'serviceFullName' => true,
61
+ 'protocol' => true,
62
+ 'targetPrefix' => true,
63
+ 'jsonVersion' => true,
64
+ 'errorPrefix' => true,
65
+ 'timestampFormat' => true, # glacier api customization
66
+ 'xmlNamespace' => true,
67
+
68
+ # ignore
69
+ 'apiVersion' => false,
70
+ 'checksumFormat' => false,
71
+ 'globalEndpoint' => false,
72
+ 'serviceAbbreviation' => false,
73
+ 'uid' => false,
74
+ }
75
+
76
+ # @option options [required, Hash] :api
77
+ # @option options [required, Hash, nil] :paginators
78
+ def initialize(options)
79
+ @api = options.fetch(:api)
80
+ @paginators = options.fetch(:paginators)
81
+ super('ClientApi')
82
+ docstring("@api private")
83
+ include('Seahorse::Model')
84
+ apply_shape_classes(self)
85
+ apply_shape_definitions(self)
86
+ apply_api_const(self)
87
+ end
88
+
89
+ private
90
+
91
+ def metadata
92
+ Dsl::CodeLiteral.new do |c|
93
+ c << "api.metadata = {"
94
+ c.indent do
95
+ (@api['metadata'] || {}).keys.sort.each do |key|
96
+ if METADATA_KEYS[key]
97
+ c << "#{key.inspect} => #{@api['metadata'][key].inspect},"
98
+ elsif METADATA_KEYS[key].nil?
99
+ raise "unhandled metadata key #{key.inspect}"
100
+ end
101
+ end
102
+ end
103
+ c << "}"
104
+ end
105
+ end
106
+
107
+ def operations
108
+ (@api['operations'] || {}).map do |name, operation|
109
+ operation(name, operation)
110
+ end
111
+ end
112
+
113
+ def operation(name, operation)
114
+ Dsl::CodeLiteral.new do |code|
115
+ code << "api.add_operation(:#{underscore(name)}, Seahorse::Model::Operation.new.tap do |o|"
116
+ code.indent do |c|
117
+ c << "o.name = #{name.inspect}"
118
+ c << "o.http_method = #{operation['http']['method'].inspect}"
119
+ c << "o.http_request_uri = #{operation['http']['requestUri'].inspect}"
120
+ c << "o.deprecated = true" if operation['deprecated']
121
+ c << "o['authtype'] = #{operation['authtype'].inspect}" if operation['authtype']
122
+ %w(input output).each do |mode|
123
+ c << "o.#{mode} = #{operation_shape_ref(operation[mode])}"
124
+ end
125
+ Array(operation['errors']).each do |error|
126
+ c << "o.errors << #{operation_shape_ref(error)}"
127
+ end
128
+ apply_operation_pager(c, name)
129
+ end
130
+ code << "end)"
131
+ end
132
+ end
133
+
134
+ def operation_shape_ref(ref)
135
+ if ref
136
+ metadata = ref.dup
137
+ shape_name = metadata.delete('shape')
138
+ if metadata.empty?
139
+ options = ''
140
+ else
141
+ options = {}
142
+ metadata.each_pair do |key, value|
143
+ next if key == 'resultWrapper'
144
+ if key == 'locationName'
145
+ options[:location_name] = value.inspect
146
+ else
147
+ options[:metadata] ||= {}
148
+ options[:metadata][key] = value.inspect
149
+ end
150
+ end
151
+ if options.empty?
152
+ options = ''
153
+ else
154
+ options = ', ' + HashFormatter.new(wrap:false).format(options)
155
+ end
156
+ end
157
+ "Shapes::ShapeRef.new(shape: #{shape_name}#{options})"
158
+ else
159
+ "Shapes::ShapeRef.new(shape: Shapes::StructureShape.new(struct_class: Aws::EmptyStructure))"
160
+ end
161
+ end
162
+
163
+ def shape_ref(ref, member_name = nil, required = Set.new)
164
+ line = "Shapes::ShapeRef.new(shape: #{ref['shape']}"
165
+ line += shape_ref_required(required, member_name)
166
+ line += shape_ref_deprecated(ref)
167
+ line += shape_ref_location(ref)
168
+ line += shape_ref_location_name(member_name, ref)
169
+ line += shape_ref_metadata(ref)
170
+ line += ")"
171
+ line
172
+ end
173
+
174
+ def apply_operation_pager(code, operation_name)
175
+ if @paginators && @paginators['pagination'][operation_name]
176
+ p = @paginators['pagination'][operation_name]
177
+ input = Array(p['input_token'])
178
+ output = Array(p['output_token'])
179
+ tokens = {}
180
+ input.each.with_index do |key, n|
181
+ tokens[underscore_jmespath(output[n])] = underscore_jmespath(key)
182
+ end
183
+ options = {}
184
+ options[:more_results] = underscore_jmespath(p['more_results']) if p['more_results']
185
+ options[:limit_key] = underscore_jmespath(p['limit_key']) if p['limit_key']
186
+ options[:tokens] = tokens
187
+ options = HashFormatter.new(
188
+ quote_strings: true,
189
+ inline: true,
190
+ wrap: false,
191
+ ).format(options)
192
+ code << "o[:pager] = Aws::Pager.new(#{options})" unless tokens.empty?
193
+ end
194
+ end
195
+
196
+ def underscore_jmespath(str)
197
+ Underscore.underscore_jmespath(str)
198
+ end
199
+
200
+ def apply_shape_classes(m)
201
+ m.code do |c|
202
+ shape_defs.each do |shape_name, shape|
203
+ attrs = []
204
+ attrs << "name: '#{shape_name}'"
205
+ shape.each_pair do |key, value|
206
+ if SHAPE_KEYS[key]
207
+ attrs << "#{key}: #{value.inspect}"
208
+ elsif SHAPE_KEYS[key].nil?
209
+ raise "unhandled shape key #{key.inspect}"
210
+ end
211
+ end
212
+ attrs = attrs.join(', ')
213
+ c << "#{shape_name} = Shapes::#{shape_class(shape['type'])}.new(#{attrs})"
214
+ end
215
+ end
216
+ end
217
+
218
+ def apply_shape_definitions(m)
219
+ m.code do |c|
220
+ shape_defs.each do |shape_name, shape|
221
+ if shape['type'] == 'structure' && !shape['error'] && !shape['exception']
222
+ required = Set.new(shape['required'] || [])
223
+ shape['members'].each do |member_name, member_ref|
224
+ c << "#{shape_name}.add_member(:#{underscore(member_name)}, #{shape_ref(member_ref, member_name, required)})"
225
+ end
226
+ c << "#{shape_name}.struct_class = Types::#{shape_name}"
227
+ if payload = shape['payload']
228
+ c << "#{shape_name}[:payload] = :#{underscore(payload)}"
229
+ c << "#{shape_name}[:payload_member] = #{shape_name}.member(:#{underscore(payload)})"
230
+ end
231
+ elsif shape['type'] == 'list'
232
+ c << "#{shape_name}.member = #{shape_ref(shape['member'])}"
233
+ elsif shape['type'] == 'map'
234
+ c << "#{shape_name}.key = #{shape_ref(shape['key'])}"
235
+ c << "#{shape_name}.value = #{shape_ref(shape['value'])}"
236
+ else
237
+ next
238
+ end
239
+ c.newline
240
+ end
241
+ end
242
+ end
243
+
244
+ def apply_api_const(m)
245
+ m.code do |c|
246
+ c << "# @api private"
247
+ c << "API = Seahorse::Model::Api.new.tap do |api|"
248
+ c.indent do
249
+ c.newline
250
+ if @api['metadata'] && @api['metadata']['apiVersion']
251
+ c << "api.version = #{@api['metadata']['apiVersion'].inspect}"
252
+ c.newline
253
+ end
254
+ c << metadata
255
+ operations.each do |operation|
256
+ c.newline
257
+ c << operation
258
+ end
259
+ end
260
+ c << "end"
261
+ end
262
+ end
263
+
264
+ def shape_ref_required(required, member_name)
265
+ if required.include?(member_name)
266
+ ", required: true"
267
+ else
268
+ ""
269
+ end
270
+ end
271
+
272
+ def shape_ref_deprecated(ref)
273
+ if ref['deprecated'] || @api['shapes'][ref['shape']]['deprecated']
274
+ ", deprecated: true"
275
+ else
276
+ ""
277
+ end
278
+ end
279
+
280
+ def shape_ref_location(ref)
281
+ if ref['location']
282
+ ", location: #{ref['location'].inspect}"
283
+ else
284
+ ''
285
+ end
286
+ end
287
+
288
+ def shape_ref_location_name(member_name, member_ref)
289
+ location_name = member_ref['locationName']
290
+ location_name ||= member_name unless member_ref['location'] == 'headers'
291
+ location_name ? ", location_name: #{location_name.inspect}" : ""
292
+ end
293
+
294
+ def shape_ref_metadata(member_ref)
295
+ metadata = member_ref.inject({}) do |hash, (key, value)|
296
+ hash[key] = value unless SKIP_TRAITS.include?(key)
297
+ hash
298
+ end
299
+ if metadata.empty?
300
+ ""
301
+ else
302
+ ", metadata: #{metadata.inspect}"
303
+ end
304
+ end
305
+
306
+ def shape_class(type)
307
+ if SHAPE_CLASSES.key?(type)
308
+ SHAPE_CLASSES[type]
309
+ else
310
+ raise ArgumentError, "unsupported shape type `#{type}'"
311
+ end
312
+ end
313
+
314
+ def shape_defs
315
+ Enumerator.new do |y|
316
+ (@api['shapes'] || {}).keys.sort.each do |shape_name|
317
+ y.yield(shape_name, @api['shapes'][shape_name])
318
+ end
319
+ end
320
+ end
321
+
322
+ def structure_defs
323
+ Enumerator.new do |y|
324
+ shape_defs.each do |shape_name, shape|
325
+ if shape['type'] == 'structure' && !shape['error'] && !shape['exception']
326
+ y.yield(shape_name, shape)
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,389 @@
1
+ require 'set'
2
+ require 'pp'
3
+ require 'seahorse/client/plugin'
4
+
5
+ module AwsSdkCodeGenerator
6
+ module Generators
7
+ class ClientClass < Dsl::Class
8
+
9
+ include Helper
10
+
11
+ # @option options [required, Main, Module] :parent
12
+ # @option options [required, String] :identifier
13
+ # @option options [required, Hash] :api
14
+ # @option options [required, Hash, nil] :waiters
15
+ # @option options [required, Hash, nil] :examples
16
+ # @option options [Hash] :add_plugins ({})
17
+ # @option options [Array<String>] :remove_plugins ([])
18
+ def initialize(options)
19
+ @identifier = options.fetch(:identifier)
20
+ @api = options.fetch(:api)
21
+ @waiters = options.fetch(:waiters)
22
+ @examples = options.fetch(:examples)
23
+ @add_plugins = options.fetch(:add_plugins, {})
24
+ @remove_plugins = options.fetch(:remove_plugins, [])
25
+ @gem_name = options.fetch(:gem_name, nil)
26
+ @gem_version = options.fetch(:gem_version, nil)
27
+ super('Client', extends: 'Seahorse::Client::Base', parent: options.fetch(:parent))
28
+ apply_modules(self)
29
+ apply_identifier(self)
30
+ apply_api(self)
31
+ apply_plugins(self)
32
+ apply_initialize_method(self)
33
+ top("\nAws::Plugins::GlobalConfiguration.add_identifier(:#{@identifier})")
34
+ apply_operations(self)
35
+ apply_waiter_methods(self)
36
+ eigenclass do |eigenclass|
37
+ eigenclass.attr_reader('identifier', api_private: true)
38
+ eigenclass.method('errors_module', api_private: true) do |m|
39
+ m.code('Errors')
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def client_plugins
47
+ @client_plugins ||= begin
48
+ plugins = {}
49
+ plugins.update(default_plugins)
50
+ plugins.update(signature_plugins)
51
+ plugins.update(protocol_plugins(@api['metadata']['protocol']))
52
+ plugins.update(@add_plugins)
53
+ @remove_plugins.each do |plugin_name|
54
+ plugins.delete(plugin_name)
55
+ end
56
+ plugins.map do |class_name, path|
57
+ path = "./#{path}" unless path[0] == '/'
58
+ Kernel.require(path)
59
+ ClientPlugin.new(
60
+ class_name: class_name,
61
+ options: const_get(class_name).options,
62
+ path: path)
63
+ end
64
+ end
65
+ end
66
+
67
+ def const_get(class_name)
68
+ const_names = class_name.split('::')
69
+ const_names.inject(Kernel) do |const, const_name|
70
+ const.const_get(const_name)
71
+ end
72
+ end
73
+
74
+ def documented_plugin_options
75
+ client_plugins.map(&:options).flatten.select(&:documented?).sort_by do |opt|
76
+ [opt.required ? 'a' : 'b', opt.name]
77
+ end
78
+ end
79
+
80
+ def apply_modules(klass)
81
+ klass.include('Aws::ClientStubs')
82
+ end
83
+
84
+ def apply_identifier(klass)
85
+ klass.code do |c|
86
+ c << "@identifier = :#{@identifier}"
87
+ end
88
+ end
89
+
90
+ def apply_api(klass)
91
+ klass.code("set_api(ClientApi::API)")
92
+ end
93
+
94
+ def apply_plugins(klass)
95
+ klass.code do |c|
96
+ client_plugins.each do |plugin|
97
+ klass.top("require '#{plugin.require_path}'")
98
+ c << "add_plugin(#{plugin.class_name})"
99
+ end
100
+ end
101
+ end
102
+
103
+ def apply_initialize_method(klass)
104
+ klass.method(:initialize) do |m|
105
+ documented_plugin_options.each do |option|
106
+ m.option(
107
+ name: option.name,
108
+ type: option.doc_type,
109
+ required: option.required,
110
+ default: option.doc_default,
111
+ docstring: option.docstring
112
+ )
113
+ end
114
+ m.param('*args')
115
+ m.code('super')
116
+ end
117
+ end
118
+
119
+ def apply_operations(klass)
120
+ code('# @!group API Operations')
121
+ (@api['operations'] || {}).each do |operation_name, operation|
122
+ method_name = underscore(operation_name)
123
+ klass.method(method_name) do |m|
124
+ ClientOperationDocumentation.apply(
125
+ api: @api,
126
+ service_identifier: @identifier,
127
+ operation_name: operation_name,
128
+ operation: operation,
129
+ examples: @examples,
130
+ method: m
131
+ )
132
+ m.param('params', type: Hash, default: {})
133
+ m.param('options', type: Hash, default: {}, documented: false)
134
+ m.code do |c|
135
+ c << "req = build_request(:#{method_name}, params)"
136
+ c << "req.send_request(options)"
137
+ end
138
+ end
139
+ end
140
+ code('# @!endgroup')
141
+
142
+
143
+ if @gem_name && @gem_version
144
+ gem_version = "\ncontext[:gem_name] = '#{@gem_name}'"
145
+ gem_version += "\ncontext[:gem_version] = '#{@gem_version}'"
146
+ end
147
+ klass.method(:build_request, api_private: true) do |m|
148
+ m.param(:operation_name)
149
+ m.param(:params, default: {})
150
+ m.code(<<-CODE)
151
+ handlers = @handlers.for(operation_name)
152
+ context = Seahorse::Client::RequestContext.new(
153
+ operation_name: operation_name,
154
+ operation: config.api.operation(operation_name),
155
+ client: self,
156
+ params: params,
157
+ config: config)#{gem_version}
158
+ Seahorse::Client::Request.new(handlers, context)
159
+ CODE
160
+ end
161
+ end
162
+
163
+ def apply_waiter_methods(klass)
164
+
165
+ # wait_until(waiter_name, params = {}, options = {}, &block)
166
+ if @waiters
167
+ klass.add(Dsl::Method.new('wait_until') do |m|
168
+ m.param('waiter_name', type: Symbol)
169
+ m.param('params', type: Hash, default: {})
170
+ m.param('options', type: Hash, default: {})
171
+ m.option(name: 'max_attempts', type: Integer)
172
+ m.option(name: 'delay', type: Integer)
173
+ m.option(name: 'before_attempt', type: Proc)
174
+ m.option(name: 'before_wait', type: Proc)
175
+ m.docstring(<<-DOCS)
176
+ Polls an API operation until a resource enters a desired state.
177
+
178
+ ## Basic Usage
179
+
180
+ A waiter will call an API operation until:
181
+
182
+ * It is successful
183
+ * It enters a terminal state
184
+ * It makes the maximum number of attempts
185
+
186
+ In between attempts, the waiter will sleep.
187
+
188
+ # polls in a loop, sleeping between attempts
189
+ client.waiter_until(waiter_name, params)
190
+
191
+ ## Configuration
192
+
193
+ You can configure the maximum number of polling attempts, and the
194
+ delay (in seconds) between each polling attempt. You can pass
195
+ configuration as the final arguments hash.
196
+
197
+ # poll for ~25 seconds
198
+ client.wait_until(waiter_name, params, {
199
+ max_attempts: 5,
200
+ delay: 5,
201
+ })
202
+
203
+ ## Callbacks
204
+
205
+ You can be notified before each polling attempt and before each
206
+ delay. If you throw `:success` or `:failure` from these callbacks,
207
+ it will terminate the waiter.
208
+
209
+ started_at = Time.now
210
+ client.wait_until(waiter_name, params, {
211
+
212
+ # disable max attempts
213
+ max_attempts: nil,
214
+
215
+ # poll for 1 hour, instead of a number of attempts
216
+ before_wait: -> (attempts, response) do
217
+ throw :failure if Time.now - started_at > 3600
218
+ end
219
+ })
220
+
221
+ ## Handling Errors
222
+
223
+ When a waiter is unsuccessful, it will raise an error.
224
+ All of the failure errors extend from
225
+ {Aws::Waiters::Errors::WaiterFailed}.
226
+
227
+ begin
228
+ client.wait_until(...)
229
+ rescue Aws::Waiters::Errors::WaiterFailed
230
+ # resource did not enter the desired state in time
231
+ end
232
+
233
+ ## Valid Waiters
234
+
235
+ The following table lists the valid waiter names, the operations they call,
236
+ and the default `:delay` and `:max_attempts` values.
237
+
238
+ #{waiter_table}
239
+
240
+ @raise [Errors::FailureStateError] Raised when the waiter terminates
241
+ because the waiter has entered a state that it will not transition
242
+ out of, preventing success.
243
+
244
+ @raise [Errors::TooManyAttemptsError] Raised when the configured
245
+ maximum number of attempts have been made, and the waiter is not
246
+ yet successful.
247
+
248
+ @raise [Errors::UnexpectedError] Raised when an error is encounted
249
+ while polling for a resource that is not expected.
250
+
251
+ @raise [Errors::NoSuchWaiterError] Raised when you request to wait
252
+ for an unknown state.
253
+
254
+ @return [Boolean] Returns `true` if the waiter was successful.
255
+ DOCS
256
+ m.code(<<-CODE)
257
+ w = waiter(waiter_name, options)
258
+ yield(w.waiter) if block_given? # deprecated
259
+ w.wait(params)
260
+ CODE
261
+ end)
262
+ end
263
+
264
+ # waiter_names
265
+ klass.add(Dsl::Method.new('waiter_names') do |m|
266
+ m.docstring("@api private")
267
+ m.docstring("@deprecated")
268
+ if @waiters
269
+ m.code("waiters.keys")
270
+ else
271
+ m.code('[]')
272
+ end
273
+ end)
274
+
275
+ # private: waiter(waiter_name)
276
+ klass.add(Dsl::Method.new('waiter', access: :private) do |m|
277
+ m.param('waiter_name', type: Symbol)
278
+ m.param('options', type: Hash, default: {})
279
+ m.code(<<-CODE)
280
+ waiter_class = waiters[waiter_name]
281
+ if waiter_class
282
+ waiter_class.new(options.merge(client: self))
283
+ else
284
+ raise Aws::Waiters::Errors::NoSuchWaiterError.new(waiter_name, waiters.keys)
285
+ end
286
+ CODE
287
+ end) if @waiters
288
+
289
+ # private: waiters
290
+ klass.add(Dsl::Method.new('waiters', access: :private) do |m|
291
+ waiters = {}
292
+ ((@waiters || {})['waiters'] || {}).each_pair do |name, definition|
293
+ class_name = "Waiters::#{name}"
294
+ waiters[underscore(name).to_sym] = class_name
295
+ end
296
+ m.code(HashFormatter.new.format(waiters))
297
+ end) if @waiters
298
+
299
+ end
300
+
301
+ def core_lib
302
+ # TODO : may need to register the default plugins directory rather
303
+ # than have the hard-coded here as a relative path
304
+ File.expand_path('../../../../../../gems/aws-sdk-core/lib', __FILE__)
305
+ end
306
+
307
+ def core_plugins
308
+ "#{core_lib}/aws-sdk-core/plugins"
309
+ end
310
+
311
+ def seahorse_plugins
312
+ "#{core_lib}/seahorse/client/plugins"
313
+ end
314
+
315
+ def default_plugins
316
+ {
317
+ 'Seahorse::Client::Plugins::ContentLength' => "#{seahorse_plugins}/content_length.rb",
318
+ 'Aws::Plugins::CredentialsConfiguration' => "#{core_plugins}/credentials_configuration.rb",
319
+ 'Aws::Plugins::Logging' => "#{core_plugins}/logging.rb",
320
+ 'Aws::Plugins::ParamConverter' => "#{core_plugins}/param_converter.rb",
321
+ 'Aws::Plugins::ParamValidator' => "#{core_plugins}/param_validator.rb",
322
+ 'Aws::Plugins::UserAgent' => "#{core_plugins}/user_agent.rb",
323
+ 'Aws::Plugins::HelpfulSocketErrors' => "#{core_plugins}/helpful_socket_errors.rb",
324
+ 'Aws::Plugins::RetryErrors' => "#{core_plugins}/retry_errors.rb",
325
+ 'Aws::Plugins::GlobalConfiguration' => "#{core_plugins}/global_configuration.rb",
326
+ 'Aws::Plugins::RegionalEndpoint' => "#{core_plugins}/regional_endpoint.rb",
327
+ 'Aws::Plugins::ResponsePaging' => "#{core_plugins}/response_paging.rb",
328
+ 'Aws::Plugins::StubResponses' => "#{core_plugins}/stub_responses.rb",
329
+ 'Aws::Plugins::IdempotencyToken' => "#{core_plugins}/idempotency_token.rb",
330
+ }
331
+ end
332
+
333
+ def protocol_plugins(protocol)
334
+ {
335
+ 'json' => { 'Aws::Plugins::Protocols::JsonRpc' => "#{core_plugins}/protocols/json_rpc.rb" },
336
+ 'rest-json' => { 'Aws::Plugins::Protocols::RestJson' => "#{core_plugins}/protocols/rest_json.rb" },
337
+ 'rest-xml' => { 'Aws::Plugins::Protocols::RestXml' => "#{core_plugins}/protocols/rest_xml.rb" },
338
+ 'query' => { 'Aws::Plugins::Protocols::Query' => "#{core_plugins}/protocols/query.rb" },
339
+ 'ec2' => { 'Aws::Plugins::Protocols::EC2' => "#{core_plugins}/protocols/ec2.rb" },
340
+ nil => {}
341
+ }[protocol]
342
+ end
343
+
344
+ def signature_plugins
345
+ case @api['metadata']['signatureVersion']
346
+ when 'v4'
347
+ { 'Aws::Plugins::SignatureV4' => "#{core_plugins}/signature_v4.rb" }
348
+ when 'v2'
349
+ { 'Aws::Plugins::SignatureV2' => "#{core_plugins}/signature_v2.rb" }
350
+ else
351
+ {}
352
+ end
353
+ end
354
+
355
+ def waiter_table
356
+ # insert one row for each supported service
357
+ table = []
358
+ @waiters['waiters'].each_pair do |name, waiter|
359
+ table << [underscore(name), "{##{underscore(waiter['operation'])}}", waiter['delay'], waiter['maxAttempts']]
360
+ end
361
+ table = table.sort_by(&:first)
362
+
363
+ # header row
364
+ table.unshift(['waiter_name', 'params', ':delay', ':max_attempts'])
365
+ markdown_table(table)
366
+ end
367
+
368
+ # @api private
369
+ class ClientPlugin
370
+
371
+ def initialize(options)
372
+ @class_name = options.fetch(:class_name)
373
+ @options = options.fetch(:options)
374
+ @require_path = options.fetch(:path).split('/lib/').last
375
+ end
376
+
377
+ # @return [String]
378
+ attr_reader :class_name
379
+
380
+ # @return [Array<Seahorse::Client::Plugin::PluginOption>]
381
+ attr_reader :options
382
+
383
+ # @return [String]
384
+ attr_reader :require_path
385
+
386
+ end
387
+ end
388
+ end
389
+ end