api_gateway_dsl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rubocop.yml +20 -0
- data/.ruby-version +1 -0
- data/.simplecov +2 -0
- data/.travis.yml +2 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/Rakefile +4 -0
- data/api_gateway_dsl.gemspec +30 -0
- data/bin/api_gateway_dsl +32 -0
- data/lib/api_gateway_dsl.rb +26 -0
- data/lib/api_gateway_dsl/context.rb +13 -0
- data/lib/api_gateway_dsl/document.rb +41 -0
- data/lib/api_gateway_dsl/dsl/document_node.rb +46 -0
- data/lib/api_gateway_dsl/dsl/integration_node.rb +32 -0
- data/lib/api_gateway_dsl/dsl/operation_node.rb +66 -0
- data/lib/api_gateway_dsl/dsl/response_node.rb +24 -0
- data/lib/api_gateway_dsl/integration.rb +47 -0
- data/lib/api_gateway_dsl/integration/collection.rb +7 -0
- data/lib/api_gateway_dsl/integration/http.rb +26 -0
- data/lib/api_gateway_dsl/integration/http_proxy.rb +29 -0
- data/lib/api_gateway_dsl/integration/lambda.rb +38 -0
- data/lib/api_gateway_dsl/integration/mock.rb +33 -0
- data/lib/api_gateway_dsl/mapping.rb +68 -0
- data/lib/api_gateway_dsl/mapping/collection.rb +15 -0
- data/lib/api_gateway_dsl/operation.rb +70 -0
- data/lib/api_gateway_dsl/operation/collection.rb +62 -0
- data/lib/api_gateway_dsl/parameter.rb +24 -0
- data/lib/api_gateway_dsl/parameter/body.rb +21 -0
- data/lib/api_gateway_dsl/parameter/collection.rb +7 -0
- data/lib/api_gateway_dsl/parameter/header.rb +13 -0
- data/lib/api_gateway_dsl/parameter/path.rb +14 -0
- data/lib/api_gateway_dsl/parameter/query.rb +13 -0
- data/lib/api_gateway_dsl/parameter/simple.rb +23 -0
- data/lib/api_gateway_dsl/response.rb +53 -0
- data/lib/api_gateway_dsl/response/collection.rb +19 -0
- data/lib/api_gateway_dsl/response_header.rb +19 -0
- data/lib/api_gateway_dsl/response_header/collection.rb +11 -0
- data/lib/api_gateway_dsl/response_integration.rb +30 -0
- data/lib/api_gateway_dsl/response_integration/collection.rb +11 -0
- data/lib/api_gateway_dsl/template.rb +48 -0
- data/lib/api_gateway_dsl/template/collection.rb +32 -0
- data/lib/api_gateway_dsl/version.rb +5 -0
- data/spec/api_gateway_dsl/document_spec.rb +20 -0
- data/spec/fixtures/greedy_http_proxy/README.md +69 -0
- data/spec/fixtures/greedy_http_proxy/index.rb +13 -0
- data/spec/fixtures/greedy_http_proxy/index.yml +35 -0
- data/spec/fixtures/greedy_http_proxy/pets/proxy.rb +9 -0
- data/spec/fixtures/http_get/README.md +150 -0
- data/spec/fixtures/http_get/index.rb +17 -0
- data/spec/fixtures/http_get/index.yml +74 -0
- data/spec/fixtures/http_get/pets/get.rb +15 -0
- data/spec/fixtures/http_get/pets/response/200.vtl +1 -0
- data/spec/fixtures/http_get/pets/response/200.yml +4 -0
- data/spec/fixtures/http_get/pets/response/500.vtl +3 -0
- data/spec/fixtures/http_get/pets/response/500.yml +4 -0
- data/spec/fixtures/lambda_post/README.md +185 -0
- data/spec/fixtures/lambda_post/index.rb +18 -0
- data/spec/fixtures/lambda_post/index.yml +89 -0
- data/spec/fixtures/lambda_post/pets/post.rb +11 -0
- data/spec/fixtures/lambda_post/pets/request/body.vtl +9 -0
- data/spec/fixtures/lambda_post/pets/request/body.yml +7 -0
- data/spec/fixtures/lambda_post/pets/response/201.vtl +1 -0
- data/spec/fixtures/lambda_post/pets/response/201.yml +1 -0
- data/spec/fixtures/lambda_post/pets/response/500.vtl +3 -0
- data/spec/fixtures/lambda_post/pets/response/500.yml +4 -0
- data/spec/fixtures/lambda_post_with_cors/README.md +193 -0
- data/spec/fixtures/lambda_post_with_cors/index.rb +9 -0
- data/spec/fixtures/lambda_post_with_cors/index.yml +106 -0
- data/spec/fixtures/lambda_post_with_cors/pets/post.rb +11 -0
- data/spec/fixtures/lambda_post_with_cors/pets/request/body.vtl +9 -0
- data/spec/fixtures/lambda_post_with_cors/pets/request/body.yml +7 -0
- data/spec/fixtures/lambda_post_with_cors/pets/response/201.vtl +1 -0
- data/spec/fixtures/lambda_post_with_cors/pets/response/201.yml +1 -0
- data/spec/fixtures/lambda_post_with_cors/pets/response/500.vtl +3 -0
- data/spec/fixtures/lambda_post_with_cors/pets/response/500.yml +4 -0
- data/spec/fixtures/markdown.rb +73 -0
- data/spec/fixtures/mock_options/README.md +80 -0
- data/spec/fixtures/mock_options/index.rb +15 -0
- data/spec/fixtures/mock_options/index.yml +44 -0
- data/spec/fixtures/mock_options/pets/options.rb +9 -0
- data/spec/spec_helper.rb +5 -0
- 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,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
|