aws-sdk-code-generator 0.2.2.pre → 0.3.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-sdk-code-generator/api.rb +12 -2
  3. data/lib/aws-sdk-code-generator/client_constructor.rb +4 -1
  4. data/lib/aws-sdk-code-generator/client_operation_documentation.rb +27 -7
  5. data/lib/aws-sdk-code-generator/client_response_structure_example.rb +8 -1
  6. data/lib/aws-sdk-code-generator/code_builder.rb +78 -9
  7. data/lib/aws-sdk-code-generator/codegenerated_plugin.rb +34 -0
  8. data/lib/aws-sdk-code-generator/gem_builder.rb +27 -5
  9. data/lib/aws-sdk-code-generator/helper.rb +10 -1
  10. data/lib/aws-sdk-code-generator/plugin_list.rb +30 -9
  11. data/lib/aws-sdk-code-generator/service.rb +22 -0
  12. data/lib/aws-sdk-code-generator/views/async_client_class.rb +6 -2
  13. data/lib/aws-sdk-code-generator/views/client_api_module.rb +40 -5
  14. data/lib/aws-sdk-code-generator/views/client_class.rb +7 -2
  15. data/lib/aws-sdk-code-generator/views/endpoint_parameters_class.rb +80 -0
  16. data/lib/aws-sdk-code-generator/views/endpoint_provider_class.rb +34 -0
  17. data/lib/aws-sdk-code-generator/views/endpoints_module.rb +177 -0
  18. data/lib/aws-sdk-code-generator/views/endpoints_plugin.rb +85 -0
  19. data/lib/aws-sdk-code-generator/views/gemspec.rb +6 -2
  20. data/lib/aws-sdk-code-generator/views/service_module.rb +22 -0
  21. data/lib/aws-sdk-code-generator/views/spec/endpoint_provider_spec_class.rb +198 -0
  22. data/lib/aws-sdk-code-generator/views/spec/spec_helper.rb +9 -4
  23. data/lib/aws-sdk-code-generator/views/types_module.rb +60 -24
  24. data/lib/aws-sdk-code-generator.rb +7 -1
  25. data/templates/async_client_class.mustache +5 -7
  26. data/templates/client_api_module.mustache +7 -0
  27. data/templates/client_class.mustache +4 -1
  28. data/templates/endpoint_parameters_class.mustache +50 -0
  29. data/templates/endpoint_provider_class.mustache +30 -0
  30. data/templates/endpoints_module.mustache +33 -0
  31. data/templates/endpoints_plugin.mustache +76 -0
  32. data/templates/gemspec.mustache +2 -1
  33. data/templates/license.txt +202 -0
  34. data/templates/service_module.mustache +11 -1
  35. data/templates/spec/endpoint_provider_spec_class.mustache +76 -0
  36. data/templates/spec/spec_helper.mustache +5 -0
  37. data/templates/types_module.mustache +7 -0
  38. metadata +16 -4
@@ -30,7 +30,10 @@ module AwsSdkCodeGenerator
30
30
  'timestampFormat' => true, # glacier api customization
31
31
  'xmlNamespace' => true,
32
32
  'streaming' => true, # transfer-encoding
33
- 'requiresLength' => true, # transder-encoding
33
+ 'requiresLength' => true, # transfer-encoding
34
+ 'union' => false, # should remain false
35
+ 'document' => true,
36
+ 'jsonvalue' => true,
34
37
  # event stream modeling
35
38
  'event' => false,
36
39
  'eventstream' => false,
@@ -60,7 +63,7 @@ module AwsSdkCodeGenerator
60
63
  'max' => false,
61
64
  'wrapper' => false,
62
65
  'xmlOrder' => false,
63
- 'retryable' => false
66
+ 'retryable' => false,
64
67
  }
65
68
 
66
69
  METADATA_KEYS = {
@@ -81,7 +84,10 @@ module AwsSdkCodeGenerator
81
84
  'checksumFormat' => true,
82
85
  'globalEndpoint' => true,
83
86
  'serviceAbbreviation' => true,
84
- 'uid' => true
87
+ 'uid' => true,
88
+ 'awsQueryCompatible' => true, # AwsQuery migration
89
+ # ignore
90
+ 'ripServiceName' => true
85
91
  }
86
92
 
87
93
  # @option options [required, Service] :service
@@ -127,13 +133,22 @@ module AwsSdkCodeGenerator
127
133
  shape_name = lstrip_prefix(upcase_first(shape_name))
128
134
  end
129
135
  lines = []
130
- if non_error_struct?(shape)
136
+ if non_error_struct?(shape) && !document_struct?(shape)
131
137
  required = Set.new(shape['required'] || [])
132
138
  unless shape['members'].nil?
133
139
  shape['members'].each do |member_name, member_ref|
134
140
  lines << "#{shape_name}.add_member(:#{underscore(member_name)}, #{shape_ref(member_ref, member_name, required)})"
135
141
  end
136
142
  end
143
+ if shape['union']
144
+ lines << "#{shape_name}.add_member(:unknown, Shapes::ShapeRef.new(shape: nil, location_name: 'unknown'))"
145
+ shape['members'].each do |member_name, member_ref|
146
+ member_name_underscore = underscore(member_name)
147
+ member_class_name = pascal_case(member_name_underscore)
148
+ lines << "#{shape_name}.add_member_subclass(:#{member_name_underscore}, Types::#{shape_name}::#{member_class_name})"
149
+ end
150
+ lines << "#{shape_name}.add_member_subclass(:unknown, Types::#{shape_name}::Unknown)"
151
+ end
137
152
  lines << "#{shape_name}.struct_class = Types::#{shape_name}"
138
153
  if payload = shape['payload']
139
154
  lines << "#{shape_name}[:payload] = :#{underscore(payload)}"
@@ -186,6 +201,15 @@ module AwsSdkCodeGenerator
186
201
  o.http_method = operation['http']['method']
187
202
  o.http_request_uri = operation['http']['requestUri']
188
203
  o.http_checksum_required = true if operation['httpChecksumRequired']
204
+ if operation.key?('httpChecksum')
205
+ operation['httpChecksum']['requestAlgorithmMember'] = underscore(operation['httpChecksum']['requestAlgorithmMember']) if operation['httpChecksum']['requestAlgorithmMember']
206
+ operation['httpChecksum']['requestValidationModeMember'] = underscore(operation['httpChecksum']['requestValidationModeMember']) if operation['httpChecksum']['requestValidationModeMember']
207
+
208
+ o.http_checksum = operation['httpChecksum'].inject([]) do |a, (k, v)|
209
+ a << { key: k.inspect, value: v.inspect }
210
+ a
211
+ end
212
+ end
189
213
  %w(input output).each do |key|
190
214
  if operation[key]
191
215
  o.shape_references << "o.#{key} = #{operation_ref(operation[key])}"
@@ -258,7 +282,11 @@ module AwsSdkCodeGenerator
258
282
  if @service.protocol == 'api-gateway' && type == 'timestamp'
259
283
  shape['timestampFormat'] = 'iso8601'
260
284
  end
261
- if SHAPE_CLASSES.key?(type)
285
+ if document_struct?(shape)
286
+ ["Shapes::DocumentShape", shape]
287
+ elsif shape['union']
288
+ ["Shapes::UnionShape", shape]
289
+ elsif SHAPE_CLASSES.key?(type)
262
290
  ["Shapes::#{SHAPE_CLASSES[type]}", shape]
263
291
  else
264
292
  raise ArgumentError, "unsupported shape type `#{type}'"
@@ -315,6 +343,10 @@ module AwsSdkCodeGenerator
315
343
  (shape['error'] || shape['exception'])
316
344
  end
317
345
 
346
+ def document_struct?(shape)
347
+ shape['type'] == 'structure' && shape['document']
348
+ end
349
+
318
350
  def structure_shape_enum
319
351
  Enumerator.new do |y|
320
352
  shape_enum.each do |shape_name, shape|
@@ -516,6 +548,9 @@ module AwsSdkCodeGenerator
516
548
  # @return [Boolean]
517
549
  attr_accessor :http_checksum_required
518
550
 
551
+ # @return [Hash]
552
+ attr_accessor :http_checksum
553
+
519
554
  # @return [Array<String>]
520
555
  attr_accessor :shape_references
521
556
 
@@ -18,6 +18,7 @@ module AwsSdkCodeGenerator
18
18
  # @option options [required, Hash] :api
19
19
  # @option options [Hash] :waiters
20
20
  # @option options [Hash] :client_examples
21
+ # @option options [Array<CodegeneratedPlugin] :codegenerated_plugins
21
22
  def initialize(options)
22
23
  @service_identifier = options.fetch(:service_identifier)
23
24
  @service_name = options.fetch(:service_name)
@@ -25,7 +26,11 @@ module AwsSdkCodeGenerator
25
26
  @gem_name = options.fetch(:gem_name)
26
27
  @gem_version = options.fetch(:gem_version)
27
28
  @plugins = PluginList.new(options)
28
- @client_constructor = ClientConstructor.new(options.merge(plugins: @plugins))
29
+ @codegenerated_plugins = options.fetch(:codegenerated_plugins, [])
30
+ @client_constructor = ClientConstructor.new(
31
+ options.merge(
32
+ plugins: @plugins,
33
+ codegenerated_plugins: @codegenerated_plugins))
29
34
  @operations = ClientOperationList.new(options).to_a
30
35
  @waiters = Waiter.build_list(options[:waiters])
31
36
  @custom = options.fetch(:custom)
@@ -65,7 +70,7 @@ module AwsSdkCodeGenerator
65
70
 
66
71
  # @return [Array<String>]
67
72
  def plugin_class_names
68
- @plugins.map(&:class_name)
73
+ @plugins.map(&:class_name) + @codegenerated_plugins.map(&:class_name)
69
74
  end
70
75
 
71
76
  # @return [Array<Waiter>]
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsSdkCodeGenerator
4
+ module Views
5
+ class EndpointParametersClass < View
6
+
7
+ # @option options [required, Service] :service
8
+ def initialize(options)
9
+ @service = options.fetch(:service)
10
+ if (parameters = @service.endpoint_rules&.fetch('parameters'))
11
+ @parameters = parameters.map do |k,p|
12
+ EndpointParameter.new(k, p)
13
+ end
14
+ end
15
+ end
16
+
17
+ # @return [Array<EndpointParameter>]
18
+ attr_reader :parameters
19
+
20
+ # @return [String|nil]
21
+ def generated_src_warning
22
+ return if @service.protocol == 'api-gateway'
23
+ GENERATED_SRC_WARNING
24
+ end
25
+
26
+ def module_name
27
+ @service.module_name
28
+ end
29
+
30
+ class EndpointParameter
31
+ def initialize(name, definition={})
32
+ @name = name
33
+ @type = definition['type']
34
+ @built_in = definition['builtIn']
35
+ @default = definition['default']
36
+ @required = definition['required']
37
+ @documentation = "# @!attribute #{underscore_name}\n"
38
+ if definition['documentation']
39
+ @documentation += " # #{definition['documentation']}\n"
40
+ end
41
+ if deprecated = definition['deprecated']
42
+ @documentation += " #\n # @deprecated\n"
43
+ if deprecated['message']
44
+ @documentation += " # #{deprecated['message']}\n"
45
+ end
46
+ if deprecated['since']
47
+ @documentation += " # Since: #{deprecated['since']}\n"
48
+ end
49
+ end
50
+ @documentation += " #\n # @return [#{@type}]\n #"
51
+ end
52
+
53
+ # @return [String]
54
+ attr_reader :name
55
+
56
+ # @return [String]
57
+ attr_reader :documentation
58
+
59
+ # @return [Boolean]
60
+ attr_reader :required
61
+
62
+ # @return [String,Boolean]
63
+ attr_reader :default
64
+
65
+ def default?
66
+ !@default.nil?
67
+ end
68
+
69
+ def boolean_default?
70
+ default? && (@default == true || @default == false)
71
+ end
72
+
73
+ def underscore_name
74
+ Underscore.underscore(name)
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,34 @@
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_encoded
30
+ Base64.encode64(JSON.dump(@endpoint_rules))
31
+ end
32
+ end
33
+ end
34
+ 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
@@ -30,7 +30,7 @@ module AwsSdkCodeGenerator
30
30
  end
31
31
 
32
32
  def email
33
- @custom ? 'yourname@email.com' : 'trevrowe@amazon.com'
33
+ @custom ? 'yourname@email.com' : 'aws-dr-rubygems@amazon.com'
34
34
  end
35
35
 
36
36
  # @return [String]
@@ -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
@@ -58,7 +62,7 @@ module AwsSdkCodeGenerator
58
62
 
59
63
  # @return [String]
60
64
  def code_uri
61
- "https://github.com/aws/aws-sdk-ruby/tree/master/gems/#{gem_name}"
65
+ "https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/#{gem_name}"
62
66
  end
63
67
 
64
68
  # @return [Array<Dependency>]
@@ -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]
@@ -51,15 +52,36 @@ module AwsSdkCodeGenerator
51
52
  @service.gem_dependencies.keys
52
53
  end
53
54
 
55
+ # @return [Boolean] - Return true if a check is needed before
56
+ # requiring core to prevent circular dependencies.
57
+ # This is required to support backwards compatibility for SSO which was
58
+ # moved from the aws-sdk-sso gem into aws-sdk-core.
59
+ def require_core_guard?
60
+ @service.included_in_core?
61
+ end
62
+
54
63
  # @return [Array<String>]
55
64
  def relative_requires
56
65
  paths = Set.new
57
66
  paths << "#{@prefix}/types"
58
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
+
59
74
  paths << "#{@prefix}/client"
60
75
  paths << "#{@prefix}/errors"
61
76
  paths << "#{@prefix}/waiters" if @service.waiters
62
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
+
63
85
  if @service.resources && @service.resources['resources']
64
86
  @service.resources['resources'].keys.each do |resource_name|
65
87
  path = "#{@prefix}/#{underscore(resource_name)}"