aws-sdk-code-generator 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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