api_gateway_dsl 0.1.0

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