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.
- 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
|