api_gateway_dsl 0.1.0

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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +20 -0
  5. data/.ruby-version +1 -0
  6. data/.simplecov +2 -0
  7. data/.travis.yml +2 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE +21 -0
  10. data/README.md +40 -0
  11. data/Rakefile +4 -0
  12. data/api_gateway_dsl.gemspec +30 -0
  13. data/bin/api_gateway_dsl +32 -0
  14. data/lib/api_gateway_dsl.rb +26 -0
  15. data/lib/api_gateway_dsl/context.rb +13 -0
  16. data/lib/api_gateway_dsl/document.rb +41 -0
  17. data/lib/api_gateway_dsl/dsl/document_node.rb +46 -0
  18. data/lib/api_gateway_dsl/dsl/integration_node.rb +32 -0
  19. data/lib/api_gateway_dsl/dsl/operation_node.rb +66 -0
  20. data/lib/api_gateway_dsl/dsl/response_node.rb +24 -0
  21. data/lib/api_gateway_dsl/integration.rb +47 -0
  22. data/lib/api_gateway_dsl/integration/collection.rb +7 -0
  23. data/lib/api_gateway_dsl/integration/http.rb +26 -0
  24. data/lib/api_gateway_dsl/integration/http_proxy.rb +29 -0
  25. data/lib/api_gateway_dsl/integration/lambda.rb +38 -0
  26. data/lib/api_gateway_dsl/integration/mock.rb +33 -0
  27. data/lib/api_gateway_dsl/mapping.rb +68 -0
  28. data/lib/api_gateway_dsl/mapping/collection.rb +15 -0
  29. data/lib/api_gateway_dsl/operation.rb +70 -0
  30. data/lib/api_gateway_dsl/operation/collection.rb +62 -0
  31. data/lib/api_gateway_dsl/parameter.rb +24 -0
  32. data/lib/api_gateway_dsl/parameter/body.rb +21 -0
  33. data/lib/api_gateway_dsl/parameter/collection.rb +7 -0
  34. data/lib/api_gateway_dsl/parameter/header.rb +13 -0
  35. data/lib/api_gateway_dsl/parameter/path.rb +14 -0
  36. data/lib/api_gateway_dsl/parameter/query.rb +13 -0
  37. data/lib/api_gateway_dsl/parameter/simple.rb +23 -0
  38. data/lib/api_gateway_dsl/response.rb +53 -0
  39. data/lib/api_gateway_dsl/response/collection.rb +19 -0
  40. data/lib/api_gateway_dsl/response_header.rb +19 -0
  41. data/lib/api_gateway_dsl/response_header/collection.rb +11 -0
  42. data/lib/api_gateway_dsl/response_integration.rb +30 -0
  43. data/lib/api_gateway_dsl/response_integration/collection.rb +11 -0
  44. data/lib/api_gateway_dsl/template.rb +48 -0
  45. data/lib/api_gateway_dsl/template/collection.rb +32 -0
  46. data/lib/api_gateway_dsl/version.rb +5 -0
  47. data/spec/api_gateway_dsl/document_spec.rb +20 -0
  48. data/spec/fixtures/greedy_http_proxy/README.md +69 -0
  49. data/spec/fixtures/greedy_http_proxy/index.rb +13 -0
  50. data/spec/fixtures/greedy_http_proxy/index.yml +35 -0
  51. data/spec/fixtures/greedy_http_proxy/pets/proxy.rb +9 -0
  52. data/spec/fixtures/http_get/README.md +150 -0
  53. data/spec/fixtures/http_get/index.rb +17 -0
  54. data/spec/fixtures/http_get/index.yml +74 -0
  55. data/spec/fixtures/http_get/pets/get.rb +15 -0
  56. data/spec/fixtures/http_get/pets/response/200.vtl +1 -0
  57. data/spec/fixtures/http_get/pets/response/200.yml +4 -0
  58. data/spec/fixtures/http_get/pets/response/500.vtl +3 -0
  59. data/spec/fixtures/http_get/pets/response/500.yml +4 -0
  60. data/spec/fixtures/lambda_post/README.md +185 -0
  61. data/spec/fixtures/lambda_post/index.rb +18 -0
  62. data/spec/fixtures/lambda_post/index.yml +89 -0
  63. data/spec/fixtures/lambda_post/pets/post.rb +11 -0
  64. data/spec/fixtures/lambda_post/pets/request/body.vtl +9 -0
  65. data/spec/fixtures/lambda_post/pets/request/body.yml +7 -0
  66. data/spec/fixtures/lambda_post/pets/response/201.vtl +1 -0
  67. data/spec/fixtures/lambda_post/pets/response/201.yml +1 -0
  68. data/spec/fixtures/lambda_post/pets/response/500.vtl +3 -0
  69. data/spec/fixtures/lambda_post/pets/response/500.yml +4 -0
  70. data/spec/fixtures/lambda_post_with_cors/README.md +193 -0
  71. data/spec/fixtures/lambda_post_with_cors/index.rb +9 -0
  72. data/spec/fixtures/lambda_post_with_cors/index.yml +106 -0
  73. data/spec/fixtures/lambda_post_with_cors/pets/post.rb +11 -0
  74. data/spec/fixtures/lambda_post_with_cors/pets/request/body.vtl +9 -0
  75. data/spec/fixtures/lambda_post_with_cors/pets/request/body.yml +7 -0
  76. data/spec/fixtures/lambda_post_with_cors/pets/response/201.vtl +1 -0
  77. data/spec/fixtures/lambda_post_with_cors/pets/response/201.yml +1 -0
  78. data/spec/fixtures/lambda_post_with_cors/pets/response/500.vtl +3 -0
  79. data/spec/fixtures/lambda_post_with_cors/pets/response/500.yml +4 -0
  80. data/spec/fixtures/markdown.rb +73 -0
  81. data/spec/fixtures/mock_options/README.md +80 -0
  82. data/spec/fixtures/mock_options/index.rb +15 -0
  83. data/spec/fixtures/mock_options/index.yml +44 -0
  84. data/spec/fixtures/mock_options/pets/options.rb +9 -0
  85. data/spec/spec_helper.rb +5 -0
  86. metadata +265 -0
@@ -0,0 +1,47 @@
1
+ module APIGatewayDSL
2
+ class Integration
3
+
4
+ attr_reader :method, :url, :mappings, :templates, :context
5
+
6
+ def initialize(operation, *args, &_block)
7
+ options = args.last.is_a?(Hash) ? args.last : {}
8
+
9
+ @operation = operation
10
+
11
+ @context = @operation.context.dup.tap { |c| c.default_body_file = 'request/body' }
12
+
13
+ @passthrough_behavior = options[:passthrough_behavior] || 'WHEN_NO_TEMPLATES'
14
+ @content_handling = options[:content_handling] || 'CONVERT_TO_TEXT'
15
+ @credentials = options[:credentials]
16
+
17
+ @mappings = Mapping::Collection.new
18
+ @templates = Template::Collection.new(@context)
19
+ end
20
+
21
+ def as_json # rubocop:disable Metrics/MethodLength
22
+ {}.tap do |result|
23
+ if (request_parameters = mappings.as_json).present?
24
+ result[:requestParameters] = request_parameters
25
+ end
26
+
27
+ result[:passthroughBehavior] = @passthrough_behavior
28
+ result[:contentHandling] = @content_handling
29
+
30
+ if (request_templates = @templates.as_json).present?
31
+ result[:requestTemplates] = request_templates
32
+ end
33
+
34
+ if (responses = @operation.responses.response_integrations.as_json).present?
35
+ result[:responses] = responses
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+
43
+ require 'api_gateway_dsl/integration/collection'
44
+ require 'api_gateway_dsl/integration/http'
45
+ require 'api_gateway_dsl/integration/http_proxy'
46
+ require 'api_gateway_dsl/integration/lambda'
47
+ require 'api_gateway_dsl/integration/mock'
@@ -0,0 +1,7 @@
1
+ module APIGatewayDSL
2
+ class Integration
3
+ class Collection < Array
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ module APIGatewayDSL
2
+ class Integration
3
+ class HTTP < Integration
4
+
5
+ attr_reader :method, :url
6
+
7
+ def initialize(_, method, url, **options, &block)
8
+ super
9
+
10
+ @method = method
11
+ @url = url
12
+
13
+ DSL::IntegrationNode.new(self, &block)
14
+ end
15
+
16
+ def as_json
17
+ super.tap do |result|
18
+ result[:type] = 'http'
19
+ result[:httpMethod] = @method
20
+ result[:uri] = @url
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ module APIGatewayDSL
2
+ class Integration
3
+ class HTTPProxy < Integration
4
+
5
+ attr_reader :method, :url
6
+
7
+ def initialize(_, method, url, **options, &block)
8
+ super
9
+
10
+ @method = method
11
+ @url = url
12
+
13
+ DSL::IntegrationNode.new(self, &block)
14
+ end
15
+
16
+ def as_json
17
+ super.tap do |result|
18
+ result.delete(:contentHandling)
19
+
20
+ result[:type] = 'http_proxy'
21
+ result[:httpMethod] = @method
22
+ result[:uri] = @url
23
+ result[:passthroughBehavior] = 'WHEN_NO_MATCH'
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ module APIGatewayDSL
2
+ class Integration
3
+ class Lambda < Integration
4
+
5
+ attr_reader :method, :url
6
+
7
+ def initialize(_, lambda_arn, **options, &block)
8
+ super
9
+
10
+ @lambda_arn = lambda_arn
11
+
12
+ DSL::IntegrationNode.new(self, &block)
13
+ end
14
+
15
+ def as_json
16
+ super.tap do |result|
17
+ result[:type] = 'aws'
18
+ result[:httpMethod] = 'POST'
19
+ result[:uri] = uri
20
+
21
+ result[:credentials] = @credentials if @credentials
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # https://docs.aws.amazon.com/apigateway/api-reference/resource/integration/#uri
28
+ def uri
29
+ "arn:aws:apigateway:#{region}:lambda:path/2015-03-31/functions/#{@lambda_arn}/invocations"
30
+ end
31
+
32
+ def region
33
+ @lambda_arn.split(':')[3]
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module APIGatewayDSL
2
+ class Integration
3
+ class Mock < Integration
4
+
5
+ attr_reader :status_code, :templates
6
+
7
+ def initialize(_, status_code, **options, &block)
8
+ super
9
+
10
+ @status_code = status_code
11
+
12
+ DSL::IntegrationNode.new(self, &block)
13
+ end
14
+
15
+ def as_json # rubocop:disable Metrics/MethodLength
16
+ super.tap do |result|
17
+ result.delete(:contentHandling)
18
+
19
+ result[:type] = 'mock'
20
+
21
+ result[:requestTemplates] = {
22
+ 'application/json' => <<-EOS.strip_heredoc
23
+ {
24
+ "statusCode": #{@status_code}
25
+ }
26
+ EOS
27
+ }
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,68 @@
1
+ module APIGatewayDSL
2
+ class Mapping
3
+
4
+ # Maps Swagger parameter types to API Gateway parameter types
5
+ TYPES = {
6
+ 'path' => 'path',
7
+ 'query' => 'querystring',
8
+ 'header' => 'header'
9
+ }.freeze
10
+
11
+ DEFAULT_SOURCE = {
12
+ 'integration' => 'method',
13
+ 'method' => 'integration'
14
+ }.freeze
15
+
16
+ def initialize(destination, direction, type, name, source)
17
+ @destination = destination
18
+ @direction = direction
19
+ @type = type
20
+ @name = name
21
+ @source = source
22
+ end
23
+
24
+ def key
25
+ "#{@destination}.#{@direction}.#{type}.#{@name}"
26
+ end
27
+
28
+ def as_json
29
+ case @source
30
+ when ::NilClass
31
+ flatten(default_source => { @direction => { type => @name } })
32
+ when ::Symbol
33
+ flatten(default_source => { @direction => { type => @source } })
34
+ when ::Hash
35
+ flatten(@source)
36
+ else
37
+ "'#{@source}'"
38
+ end
39
+ end
40
+
41
+ def response_header
42
+ ResponseHeader.new(@name)
43
+ end
44
+
45
+ private
46
+
47
+ def type
48
+ TYPES[@type]
49
+ end
50
+
51
+ def default_source
52
+ DEFAULT_SOURCE[@destination]
53
+ end
54
+
55
+ def flatten(object)
56
+ case object
57
+ when ::Hash
58
+ key = object.keys.first
59
+ "#{key}.#{flatten(object[key])}"
60
+ else
61
+ object
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+
68
+ require 'api_gateway_dsl/mapping/collection'
@@ -0,0 +1,15 @@
1
+ module APIGatewayDSL
2
+ class Mapping
3
+ class Collection < Array
4
+
5
+ def response_headers
6
+ ResponseHeader::Collection.new.concat(map(&:response_header))
7
+ end
8
+
9
+ def as_json
10
+ index_by(&:key).transform_values(&:as_json)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,70 @@
1
+ module APIGatewayDSL
2
+ class Operation
3
+
4
+ attr_reader :path, :cors, :parameters, :integrations, :responses, :context
5
+ attr_accessor :summary, :description
6
+
7
+ def initialize(context, method, path, **options, &block)
8
+ @context = context.dup
9
+
10
+ @method = method
11
+ @path = path
12
+
13
+ @cors = options[:cors]
14
+ @security = options[:security]
15
+
16
+ @parameters = Parameter::Collection.new
17
+ @integrations = Integration::Collection.new
18
+ @responses = Response::Collection.new
19
+
20
+ DSL::OperationNode.new(self, &block)
21
+ end
22
+
23
+ def parameters_with_body
24
+ Parameter::Collection.new.concat(parameters).tap do |result|
25
+ if (integration = integrations.first)
26
+ result.concat(integration.templates.parameters)
27
+ end
28
+ end
29
+ end
30
+
31
+ def method
32
+ case @method
33
+ when 'ANY' then 'x-amazon-apigateway-any-method'
34
+ else @method.downcase
35
+ end
36
+ end
37
+
38
+ def cors_method
39
+ @method # not downcased
40
+ end
41
+
42
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
43
+ def as_json
44
+ {}.tap do |result|
45
+ result[:summary] = summary if summary
46
+ result[:description] = description.strip_heredoc if description
47
+ result[:security] = [@security => []] if @security
48
+
49
+ if (produces = responses.content_types).present?
50
+ result[:produces] = produces
51
+ end
52
+ result[:responses] = responses.as_json
53
+
54
+ if (integration = integrations.first)
55
+ if (consumes = integration.templates.content_types).present?
56
+ result[:consumes] = consumes
57
+ end
58
+ if (params = parameters_with_body.as_json).present?
59
+ result[:parameters] = params
60
+ end
61
+ result[:'x-amazon-apigateway-integration'] = integration.as_json
62
+ end
63
+ end
64
+ end
65
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
66
+
67
+ end
68
+ end
69
+
70
+ require 'api_gateway_dsl/operation/collection'
@@ -0,0 +1,62 @@
1
+ module APIGatewayDSL
2
+ class Operation
3
+ class Collection < Array
4
+
5
+ ACCESS_CONTROL_ALLOW_HEADERS = %w(
6
+ Content-Type
7
+ Authorization
8
+ X-Amz-Date
9
+ X-Api-Key
10
+ X-Amz-Security-Token
11
+ ).freeze
12
+
13
+ # Indexes a flat array of operarations by path and HTTP method:
14
+ #
15
+ # Given:
16
+ #
17
+ # - operationA ( path = '/path1', method = 'get' )
18
+ # - operationB ( path = '/path1', method = 'post' )
19
+ # - operationC ( path = '/path2', method = 'get' )
20
+ #
21
+ # Result:
22
+ #
23
+ # /path1:
24
+ # get:
25
+ # operationA
26
+ # post:
27
+ # operationB
28
+ # /path2:
29
+ # get:
30
+ # operationC
31
+ #
32
+ def as_json
33
+ group_by(&:path).transform_values do |operations|
34
+ append_cors_operation!(operations)
35
+ operations.index_by(&:method).transform_values(&:as_json)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # All passed operations must have the same path
42
+ def append_cors_operation!(operations)
43
+ cors_enabled_ops = operations.select(&:cors)
44
+ return if cors_enabled_ops.none?
45
+ operations << cors_operation(operations.first.path, (cors_enabled_ops.map(&:cors_method) << 'OPTIONS').sort)
46
+ end
47
+
48
+ def cors_operation(path, methods)
49
+ Operation.new(Context.new, 'OPTIONS', path) do
50
+ MOCK 200
51
+
52
+ RESPONSE 200 do
53
+ header 'Access-Control-Allow-Headers', ACCESS_CONTROL_ALLOW_HEADERS.join(',')
54
+ header 'Access-Control-Allow-Methods', methods.join(',')
55
+ header 'Access-Control-Allow-Origin', '*'
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ module APIGatewayDSL
2
+ class Parameter
3
+
4
+ def initialize(name, **options)
5
+ @name = name
6
+ @description = options[:description].try(:strip_heredoc)
7
+ @required = !!options[:required]
8
+ end
9
+
10
+ def as_json
11
+ {}.tap do |result|
12
+ result[:name] = @name
13
+ result[:description] = @description if @description
14
+ result[:in] = @in
15
+ result[:required] = @required
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+
22
+ require 'api_gateway_dsl/parameter/body'
23
+ require 'api_gateway_dsl/parameter/collection'
24
+ require 'api_gateway_dsl/parameter/simple'
@@ -0,0 +1,21 @@
1
+ module APIGatewayDSL
2
+ class Parameter
3
+ class Body # does not extend Parameter, but is built from Template
4
+
5
+ def initialize(template)
6
+ @template = template
7
+ end
8
+
9
+ def as_json
10
+ {}.tap do |result|
11
+ result[:name] = File.basename(@template.schema)
12
+ result[:description] = @template.description if @template.description
13
+ result[:in] = 'body'
14
+ result[:required] = true
15
+ result[:schema] = @template.schema_value
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end