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,166 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ class ClientOperationDocumentation
4
+
5
+ include Helper
6
+
7
+ def self.apply(options)
8
+ new(options).apply(options)
9
+ end
10
+
11
+ # @option options [required, Hash] :api
12
+ # @option options [required, String] :service_identifier
13
+ # @option options [required, String] :operation_name
14
+ # @option options [required, Hash] :operation
15
+ # @option options [Hash] :examples
16
+ def initialize(options)
17
+ @api = options.fetch(:api)
18
+ @service_id = options.fetch(:service_identifier)
19
+ @operation_name = options.fetch(:operation_name)
20
+ @method_name = underscore(@operation_name)
21
+ @operation = options.fetch(:operation)
22
+ @examples = options.fetch(:examples, nil) || { 'examples' => {} }
23
+ end
24
+
25
+ def apply(options)
26
+ method = options.fetch(:method)
27
+ method.docstring do |docstring|
28
+ apply_operation_docs(docstring)
29
+ apply_option_tags(docstring)
30
+ apply_return_tags(docstring)
31
+ apply_shared_examples(docstring)
32
+ apply_examples_from_disk(docstring)
33
+ apply_request_syntax_example(docstring)
34
+ apply_response_struture_example(docstring)
35
+ apply_overload_tag(docstring)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def apply_operation_docs(docstring)
42
+ docstring.append(markdown(@operation['documentation']))
43
+ end
44
+
45
+ def apply_option_tags(docstring)
46
+ # document the `:response_target` option if the response is streaming
47
+ if output = shape(@operation['output'])
48
+ if output['payload'] && output['members'][output['payload']]['streaming']
49
+ docstring.lines.concat(Dsl::OptionTag.new(
50
+ name: 'response_target',
51
+ type: 'String, IO',
52
+ param: 'params',
53
+ required: false,
54
+ docstring: 'Where to write response data, file path, or IO object.'
55
+ ).lines)
56
+ end
57
+ end
58
+
59
+ if input_shape = shape(@operation['input'])
60
+ required = input_shape['required'] || []
61
+ input_shape['members'].each_pair do |member_name, member_ref|
62
+ docstring.lines.concat(Dsl::OptionTag.new(
63
+ name: underscore(member_name),
64
+ type: ruby_input_type(member_ref),
65
+ param: 'params',
66
+ required: required.include?(member_name),
67
+ docstring: documentation(member_ref),
68
+ ).lines)
69
+ end
70
+ end
71
+ end
72
+
73
+ def apply_return_tags(docstring)
74
+ output_shape = shape(@operation['output'])
75
+ resp = '{Seahorse::Client::Response response}'
76
+ if output_shape && output_shape['members'].size > 0
77
+ type = ruby_type(@operation['output'])
78
+ returns = "@return [#{type}] Returns a #{resp} object which responds to "
79
+ returns << "the following methods:\n\n"
80
+ output_shape['members'].each_pair do |mname, mref|
81
+ mtype = ruby_type(mref).gsub(/</, '&lt;').gsub(/>/, '&gt;')
82
+ returns << " * {#{type}##{underscore(mname)} ##{mname}} => #{mtype}\n"
83
+ end
84
+ docstring.append(returns)
85
+ else
86
+ docstring.append("@return [Struct] Returns an empty #{resp}.")
87
+ end
88
+ end
89
+
90
+ def apply_shared_examples(docstring)
91
+ (@examples[@operation_name] || []).size.times do |n|
92
+ begin
93
+ # TODO : known issue with an ec2 shared example that
94
+ # attempts to document a member that is not
95
+ # present in the model any longer (intentionally
96
+ # removed in customizations) - raises runtime
97
+ # error. This should be cleaned up.
98
+ docstring.append(
99
+ SharedExample.new(
100
+ operation_name: @operation_name,
101
+ api: @api,
102
+ examples: @examples,
103
+ example: n
104
+ ).to_s
105
+ )
106
+ rescue
107
+ end
108
+ end
109
+ end
110
+
111
+ def apply_shared_example(docstring, example)
112
+ return
113
+
114
+ input_comments = json_ex['comments']['input']
115
+ input = SharedExample.new(json_ex['input'], method_name, operation, input_comments).to_str_input
116
+ parts = []
117
+ parts << "@example Example: #{json_ex['title']}\n\n"
118
+ parts << " # #{json_ex['description']}\n\n"
119
+ parts += input.lines.map { |line| " " + line }
120
+ if json_ex['output']
121
+ output_comments = json_ex['comments']['output']
122
+ output = SharedExample.new(json_ex['output'], method_name, operation, output_comments).to_str_output
123
+ parts << "\n\n # resp.to_h outputs the following:\n"
124
+ parts += output.lines.map { |line| " " + line }
125
+ end
126
+ tag(parts.join)
127
+ end
128
+
129
+ def apply_examples_from_disk(docstring)
130
+ examples = File.expand_path('../../../../../../doc-src/examples', __FILE__)
131
+ glob = "#{examples}/#{@service_id}/client/#{@method_name}/*.rb"
132
+ Dir.glob(glob).map do |path|
133
+ title = File.basename(path).split(/\./).first
134
+ title = title.sub(/^\d+_/, '').gsub(/_/, ' ')
135
+ title = title[0].upcase + title[1..-1]
136
+ docstring.append("\n@example #{title}")
137
+ docstring.append(" " + File.read(path).lines.join(' '))
138
+ end
139
+ end
140
+
141
+ def apply_request_syntax_example(docstring)
142
+ if @operation['input']
143
+ syntax = SyntaxExample.new(
144
+ struct_shape: shape(@operation['input']),
145
+ api: @api,
146
+ indent: ' '
147
+ ).format.strip
148
+ docstring.append("\n@example Request syntax with placeholder values")
149
+ docstring.append(" resp = client.#{@method_name}(#{syntax})")
150
+ end
151
+ end
152
+
153
+ def apply_response_struture_example(docstring)
154
+ output = @operation['output']
155
+ if output && shape(output)['members'].size > 0
156
+ docstring.append(ResponseStructureExample.new(shape_ref:output, api:@api).to_s)
157
+ end
158
+ end
159
+
160
+ def apply_overload_tag(docstring)
161
+ docstring.append("@overload #{@method_name}(params = {})")
162
+ end
163
+
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,25 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ class ErrorsModule < Dsl::Module
4
+
5
+ include Helper
6
+
7
+ def initialize(options)
8
+ @api = options.fetch(:api)
9
+ super('Errors')
10
+ self.extend('Aws::Errors::DynamicErrors')
11
+ self.class('ResourceNotLoadable', extends: 'RuntimeError') do |c|
12
+ c.docstring(<<-DOCSTRING)
13
+ Raised when calling #load or #data on a resource class that can not be
14
+ loaded. This can happen when:
15
+
16
+ * A resource class has identifiers, but no data attributes.
17
+ * Resource data is only available when making an API call that
18
+ enumerates all resources of that type.
19
+ DOCSTRING
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,88 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ module Resource
4
+ class Action < Dsl::Method
5
+
6
+ include Helper
7
+
8
+ # @option options [required, String] :name
9
+ # @option options [required, Hash] :action
10
+ # @option options [required, Hash] :api
11
+ # @option options [String] :var_name ('')
12
+ def initialize(options = {})
13
+ @api = options.fetch(:api)
14
+ @request = options.fetch(:action).fetch('request')
15
+ @resource = options.fetch(:action).fetch('resource', nil)
16
+ @var_name = options.fetch(:var_name, '')
17
+ super(underscore(options.fetch(:name)))
18
+ param('options', type:Hash, default:{})
19
+ apply_client_request_docs
20
+ apply_response
21
+ apply_return_tag
22
+ end
23
+
24
+ private
25
+
26
+ def apply_client_request_docs
27
+ ClientRequestDocs.new(
28
+ request: @request,
29
+ api: @api,
30
+ var_name: @var_name,
31
+ returns: @resource ? @resource['type'].downcase : nil
32
+ ).apply(self)
33
+ end
34
+
35
+ def apply_response
36
+ if @resource && batch?(@resource)
37
+ code('batch = []')
38
+ add(client_request)
39
+ code(BatchBuilder.new(resource: @resource))
40
+ code("#{resource_type}::Collection.new([batch], size: batch.size)")
41
+ elsif @resource
42
+ add(client_request)
43
+ code(Builder.new(resource: @resource, request_made: true))
44
+ else
45
+ add(client_request)
46
+ code('resp.data')
47
+ end
48
+ end
49
+
50
+ def client_request
51
+ ClientRequest.new(
52
+ request: @request,
53
+ resp: true
54
+ )
55
+ end
56
+
57
+ def apply_return_tag
58
+ if @resource && batch?(@resource)
59
+ returns("#{resource_type}::Collection")
60
+ elsif @resource
61
+ returns(resource_type)
62
+ else
63
+ returns(request_return_type)
64
+ end
65
+ end
66
+
67
+ def resource_type
68
+ @resource['type']
69
+ end
70
+
71
+ def request_return_type
72
+ operation = @api['operations'][@request['operation']]
73
+ if operation['output']
74
+ "Types::#{operation['output']['shape']}"
75
+ else
76
+ 'EmptyStructure'
77
+ end
78
+ end
79
+
80
+ def batch?(resource)
81
+ paths = (@resource['identifiers'] || []).map {|i| i['path'] }
82
+ paths << @resource['path']
83
+ paths.compact.any? { |path| path.match(/\[/) }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,211 @@
1
+ require 'set'
2
+
3
+ module AwsSdkCodeGenerator
4
+ module Generators
5
+ module Resource
6
+ class BatchBuilder < Dsl::CodeLiteral
7
+
8
+ include Helper
9
+
10
+ # @option options [required, Hash] :resource
11
+ # @option options [String] :resp_var_name ('resp')
12
+ def initialize(options)
13
+ @resource = options.fetch(:resource)
14
+ @resp_var_name = options.fetch(:resp_var_name, 'resp')
15
+ verify_resource!
16
+ @context = loop_context
17
+ super()
18
+
19
+ # create nested blocks from each loop expression
20
+ stack = [self]
21
+ loops.each do |loop_expression|
22
+ stack.last.append(loop_expression)
23
+ stack << stack.last.indent
24
+ end
25
+
26
+ # add resource to the batch
27
+ stack.last.append("batch << #{resource_class}.new(#{constructor_args})")
28
+
29
+ # add closing end tags
30
+ stack.pop
31
+ stack.each { |c| c.append('end') }
32
+ end
33
+
34
+ private
35
+
36
+ def resource_class
37
+ @resource['type']
38
+ end
39
+
40
+ def constructor_args
41
+ hash = {}
42
+ hash.update(identifiers)
43
+ hash[:data] = data_path if @resource['path']
44
+ hash[:client] = "@client"
45
+ HashFormatter.new(wrap:false, inline:true).format(hash)
46
+ end
47
+
48
+ def identifiers
49
+ (@resource['identifiers'] || []).inject({}) do |hash, identifier|
50
+ value = relative_identifier_path(identifier)
51
+ hash[underscore(identifier['target']).to_sym] = value
52
+ hash
53
+ end
54
+ end
55
+
56
+ def data_path
57
+ relative_identifier_path({
58
+ 'source' => 'data',
59
+ 'path' => @resource['path'],
60
+ })
61
+ end
62
+
63
+ def relative_identifier_path(identifier)
64
+ path = identifier['path']
65
+ if path && path.include?('[]')
66
+ prefix = loops.last.match(/\|(.+)\|/)[1]
67
+ suffix = underscore(path[common_prefix.length..-1])
68
+ if @context == :options
69
+ suffix = suffix.gsub(/\.\w+/) { |word| "[:#{word[1..-1]}]" }
70
+ end
71
+ suffix.length == 0 ? prefix : prefix + suffix
72
+ else
73
+ ValueSource.new(identifier).to_s
74
+ end
75
+ end
76
+
77
+ def common_prefix
78
+ paths = plural_paths
79
+ if paths.empty?
80
+ ''
81
+ elsif paths.size == 1
82
+ # grab everything upto and including the final []
83
+ paths.first.match(/(.+\[\]).*?$/)[1]
84
+ else
85
+ prefix = find_prefix(paths)
86
+ prefix = prefix.sub(/\[\].+?$/, '[]')
87
+ if prefix[-2..-1] != '[]'
88
+ msg = 'response paths must have a common prefix ending in [], got :'
89
+ msg << paths.inspect
90
+ raise ArgumentError, msg
91
+ else
92
+ prefix
93
+ end
94
+ end
95
+ end
96
+
97
+ def find_prefix(paths)
98
+ prefix = ''
99
+ loop.with_index do |_, n|
100
+ return prefix if paths.empty?
101
+ letter = paths[0][n]
102
+ paths.each do |path|
103
+ return prefix if path[n].nil?
104
+ return prefix if path[n] != letter
105
+ end
106
+ prefix += letter
107
+ end
108
+ end
109
+
110
+ def loops
111
+ loop_var =
112
+ case @context
113
+ when :data then 'data.'
114
+ when :options then 'options'
115
+ when :response then "#{@resp_var_name}.data."
116
+ end
117
+
118
+ used_vars = Set.new
119
+ used_vars << loop_var
120
+
121
+ parts = common_prefix.split('[]')
122
+ parts = parts.map.with_index do |part,n|
123
+ part = underscore(part)
124
+ if @context == :options
125
+ part = part.gsub(/\w+/) { |word| "[:#{word}]" }
126
+ part = part.gsub(/\./, '')
127
+ end
128
+ part = "#{loop_var}#{part}"
129
+ loop_var = loop_letter(part, used_vars)
130
+ part = part + ".each do |#{loop_var}|"
131
+ part
132
+ end
133
+ parts
134
+ end
135
+
136
+ def loop_letter(str, used_vars)
137
+ letter = if @context == :options
138
+ str.scan(/:\w/).last[1]
139
+ else
140
+ str.split('.').last[0]
141
+ end
142
+ n = 1
143
+ var = letter
144
+ while used_vars.include?(var)
145
+ n += 1
146
+ var = "#{letter}#{n}"
147
+ end
148
+ used_vars << var
149
+ var
150
+ end
151
+
152
+ def all_plural_paths
153
+ paths = {}
154
+ @resource['identifiers'].each do |i|
155
+ if i['path'] && i['path'].include?('[]')
156
+ paths[i['source']] ||= []
157
+ paths[i['source']] << i['path']
158
+ end
159
+ end
160
+ #if @resource['path'] && @resource['path'].include?('[]')
161
+ # type = @context == :data ? 'data' : 'response'
162
+ # paths[type] ||= []
163
+ # paths[type] << @resource['path']
164
+ #end
165
+ paths
166
+ end
167
+
168
+ def plural_paths
169
+ all_plural_paths.values.first
170
+ end
171
+
172
+ def verify_resource!
173
+ verify_plural_paths!
174
+ end
175
+
176
+ def verify_plural_paths!
177
+ paths = all_plural_paths
178
+ case paths.size
179
+ when 0
180
+ msg = 'expected at least one plural identifier path, got none'
181
+ raise ArgumentError, msg
182
+ when 1
183
+ case paths.keys.first
184
+ when 'requestParameter'
185
+ when 'response'
186
+ when 'data'
187
+ else
188
+ msg = "unsupported identifier source #{paths.keys.first.inspect}"
189
+ raise ArgumentError, msg
190
+ end
191
+ else
192
+ msg = 'mixing plural source types is not supported'
193
+ raise ArgumentError, msg
194
+ end
195
+ end
196
+
197
+ def loop_context
198
+ case all_plural_paths.keys
199
+ when ['response'] then :response
200
+ when ['data'] then :data
201
+ when ['requestParameter'] then :options
202
+ else
203
+ msg = "unable to determine loop context: #{@resource.inspect}"
204
+ raise msg
205
+ end
206
+ end
207
+
208
+ end
209
+ end
210
+ end
211
+ end