apiture 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/apiture.gemspec +30 -0
- data/lib/apiture.rb +24 -0
- data/lib/apiture/api_base.rb +27 -0
- data/lib/apiture/api_builder.rb +196 -0
- data/lib/apiture/api_error.rb +3 -0
- data/lib/apiture/api_group.rb +28 -0
- data/lib/apiture/data_model.rb +24 -0
- data/lib/apiture/endpoint.rb +25 -0
- data/lib/apiture/middleware/auth/api_key.rb +39 -0
- data/lib/apiture/middleware/auth/basic.rb +25 -0
- data/lib/apiture/middleware/auth/oauth2.rb +31 -0
- data/lib/apiture/middleware/convert_json_body.rb +15 -0
- data/lib/apiture/middleware/debug.rb +20 -0
- data/lib/apiture/middleware/set_body_parameter.rb +20 -0
- data/lib/apiture/middleware/set_header.rb +15 -0
- data/lib/apiture/middleware/set_parameter_base.rb +37 -0
- data/lib/apiture/middleware/set_path_parameter.rb +18 -0
- data/lib/apiture/middleware/set_query_parameter.rb +13 -0
- data/lib/apiture/middleware_builder.rb +15 -0
- data/lib/apiture/middleware_stack.rb +21 -0
- data/lib/apiture/request_context.rb +103 -0
- data/lib/apiture/swagger/data_type_field.rb +25 -0
- data/lib/apiture/swagger/definition.rb +20 -0
- data/lib/apiture/swagger/external_docs.rb +10 -0
- data/lib/apiture/swagger/info.rb +14 -0
- data/lib/apiture/swagger/node.rb +149 -0
- data/lib/apiture/swagger/operation.rb +32 -0
- data/lib/apiture/swagger/parameter.rb +35 -0
- data/lib/apiture/swagger/parser.rb +148 -0
- data/lib/apiture/swagger/path.rb +37 -0
- data/lib/apiture/swagger/property.rb +15 -0
- data/lib/apiture/swagger/security.rb +21 -0
- data/lib/apiture/swagger/security_definition.rb +23 -0
- data/lib/apiture/swagger/specification.rb +31 -0
- data/lib/apiture/uri.rb +36 -0
- data/lib/apiture/utils/inflections.rb +46 -0
- data/lib/apiture/version.rb +3 -0
- data/spec/apiture/api_builder_spec.rb +20 -0
- data/spec/apiture/swagger/parser_spec.rb +107 -0
- data/spec/apiture/swagger/specification_spec.rb +19 -0
- data/spec/apiture_spec.rb +228 -0
- data/spec/files/github.json +186 -0
- data/spec/files/harvest.json +75 -0
- data/spec/files/honeybadger.json +80 -0
- data/spec/files/mandrill.json +502 -0
- data/spec/files/pivotal_tracker.json +94 -0
- data/spec/files/slack.json +88 -0
- data/spec/files/uber.json +43 -0
- data/spec/fixtures/vcr_cassettes/github_checkAuthorization.yml +70 -0
- data/spec/fixtures/vcr_cassettes/github_getUser.yml +82 -0
- data/spec/fixtures/vcr_cassettes/harvest_invoiceList.yml +85 -0
- data/spec/fixtures/vcr_cassettes/honeybadger.yml +98 -0
- data/spec/fixtures/vcr_cassettes/mandrill_messageSend.yml +44 -0
- data/spec/fixtures/vcr_cassettes/mandrill_userPing.yml +44 -0
- data/spec/fixtures/vcr_cassettes/pivotal_tracker_create_story.yml +61 -0
- data/spec/fixtures/vcr_cassettes/slack.yml +43 -0
- data/spec/fixtures/vcr_cassettes/uber_getProducts.yml +60 -0
- data/spec/spec_helper.rb +11 -0
- metadata +241 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'apiture/middleware_builder'
|
2
|
+
require 'apiture/middleware_stack'
|
3
|
+
|
4
|
+
module Apiture
|
5
|
+
class Endpoint
|
6
|
+
attr_reader :name, :url, :request_method
|
7
|
+
|
8
|
+
def initialize(name, url, request_method)
|
9
|
+
@name, @url, @request_method = name, url, request_method
|
10
|
+
end
|
11
|
+
|
12
|
+
def middlewares
|
13
|
+
@middlewares ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def middleware_stack
|
17
|
+
@middleware_stack ||= MiddlewareStack.new(middlewares)
|
18
|
+
end
|
19
|
+
|
20
|
+
def config_middleware(&block)
|
21
|
+
builder = MiddlewareBuilder.new(middlewares)
|
22
|
+
builder.build(&block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Apiture
|
2
|
+
module Middleware
|
3
|
+
module Auth
|
4
|
+
class APIKey
|
5
|
+
|
6
|
+
def initialize(app, options)
|
7
|
+
@app = app
|
8
|
+
@id = options[:id]
|
9
|
+
@in = options[:in]
|
10
|
+
@name = options[:name]
|
11
|
+
@format = options[:format]
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
context = env[:context]
|
16
|
+
value = format_value(context.options[@id])
|
17
|
+
if @in == :header
|
18
|
+
env[:request_headers][@name] = value
|
19
|
+
elsif @in == :query
|
20
|
+
env[:params][@name] = value
|
21
|
+
elsif @in == :body
|
22
|
+
env[:body] = if body = env[:body]
|
23
|
+
body.merge(@name => value)
|
24
|
+
else
|
25
|
+
{ @name => value }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@app.call(env)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
def format_value(val)
|
33
|
+
return val unless @format
|
34
|
+
@format % val
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Apiture
|
2
|
+
module Middleware
|
3
|
+
module Auth
|
4
|
+
class Basic
|
5
|
+
|
6
|
+
AUTHORIZATION_HEADER = 'Authorization'.freeze
|
7
|
+
|
8
|
+
def initialize(app, options)
|
9
|
+
@app = app
|
10
|
+
@id = options[:id]
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
auth_options = env[:context].options[@id]
|
15
|
+
env[:basic_auth] = {
|
16
|
+
username: auth_options[:username],
|
17
|
+
password: auth_options[:password]
|
18
|
+
}
|
19
|
+
@app.call(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Apiture
|
2
|
+
module Middleware
|
3
|
+
module Auth
|
4
|
+
class OAuth2
|
5
|
+
|
6
|
+
AUTHORIZATION_HEADER = 'Authorization'.freeze
|
7
|
+
AUTHORIZATION_HEADER_FORMAT = 'Bearer %s'.freeze
|
8
|
+
|
9
|
+
def initialize(app, options)
|
10
|
+
@app = app
|
11
|
+
@id = options[:id]
|
12
|
+
@in = options[:in]
|
13
|
+
@name = options[:name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
context = env[:context]
|
18
|
+
auth_options = context.options[@id]
|
19
|
+
token = auth_options[:token]
|
20
|
+
if @in == :query
|
21
|
+
env[:params][@name] = token
|
22
|
+
else
|
23
|
+
env[:request_headers][AUTHORIZATION_HEADER] = AUTHORIZATION_HEADER_FORMAT % token
|
24
|
+
end
|
25
|
+
@app.call(env)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Apiture
|
2
|
+
module Middleware
|
3
|
+
class Debug
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
def call(env)
|
8
|
+
@app.call(env)
|
9
|
+
logger = env[:logger]
|
10
|
+
if logger && logger.debug?
|
11
|
+
logger.debug("Request Method: #{env[:method]}")
|
12
|
+
logger.debug("URL: #{env[:url]}")
|
13
|
+
logger.debug("Request Headers: #{env[:request_headers].inspect}")
|
14
|
+
logger.debug("Params: #{env[:params].inspect}")
|
15
|
+
logger.debug("Body: #{env[:body]}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'apiture/middleware/set_parameter_base'
|
2
|
+
|
3
|
+
module Apiture
|
4
|
+
module Middleware
|
5
|
+
class SetBodyParameter < SetParameterBase
|
6
|
+
def apply_parameter_value(env, value)
|
7
|
+
if value
|
8
|
+
env[:body] = if body = env[:body]
|
9
|
+
body.merge(value)
|
10
|
+
else
|
11
|
+
value
|
12
|
+
end
|
13
|
+
if env[:request_headers]["Content-Type"].nil?
|
14
|
+
env[:request_headers]["Content-Type"] = "application/json"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Apiture
|
2
|
+
module Middleware
|
3
|
+
class SetParameterBase
|
4
|
+
|
5
|
+
def initialize(app, options)
|
6
|
+
@app = app
|
7
|
+
@name = options[:name]
|
8
|
+
@schema = options[:schema]
|
9
|
+
@default = options[:default]
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
value = find_parameter_value(env)
|
14
|
+
if value == nil && @default
|
15
|
+
value = @default
|
16
|
+
end
|
17
|
+
apply_parameter_value(env, value)
|
18
|
+
@app.call(env)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def find_parameter_value(env)
|
24
|
+
if @schema
|
25
|
+
@schema.build(@name, env)
|
26
|
+
else
|
27
|
+
context = env[:context]
|
28
|
+
context.get_attribute(@name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def apply_parameter_value(env, value)
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'apiture/middleware/set_parameter_base'
|
2
|
+
|
3
|
+
module Apiture
|
4
|
+
module Middleware
|
5
|
+
class SetPathParameter < SetParameterBase
|
6
|
+
|
7
|
+
def initialize(app, options)
|
8
|
+
super
|
9
|
+
@regex = Regexp.new(Regexp.quote("{#{@name}}"))
|
10
|
+
end
|
11
|
+
|
12
|
+
def apply_parameter_value(env, value)
|
13
|
+
uri = env[:url]
|
14
|
+
uri.resource_path = uri.resource_path.gsub(@regex, value.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'apiture/middleware/set_parameter_base'
|
2
|
+
|
3
|
+
module Apiture
|
4
|
+
module Middleware
|
5
|
+
class SetQueryParameter < SetParameterBase
|
6
|
+
def apply_parameter_value(env, value)
|
7
|
+
unless value.nil?
|
8
|
+
env[:params][@name] = value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Apiture
|
2
|
+
class MiddlewareBuilder
|
3
|
+
def initialize(middlewares)
|
4
|
+
@middlewares = middlewares
|
5
|
+
end
|
6
|
+
|
7
|
+
def build(&block)
|
8
|
+
instance_eval(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def use(middleware_klass, options = {})
|
12
|
+
@middlewares << [middleware_klass, options]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Apiture
|
2
|
+
class MiddlewareStack
|
3
|
+
|
4
|
+
def initialize(middlewares)
|
5
|
+
cur = ->(env){}
|
6
|
+
middlewares.reverse.each do |(klass, options)|
|
7
|
+
cur = if klass.instance_method(:initialize).arity == 1
|
8
|
+
klass.new(cur)
|
9
|
+
else
|
10
|
+
klass.new(cur, options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
@first = cur
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
@first.call(env)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'apiture/utils/inflections'
|
4
|
+
|
5
|
+
module FaradayMiddleware
|
6
|
+
class ParseJsonWithQuirksMode < ParseJson
|
7
|
+
define_parser do |body|
|
8
|
+
::JSON.parse(body, quirks_mode: true) unless body.strip.empty?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
Faraday::Response.register_middleware json_quirks: lambda { FaradayMiddleware::ParseJsonWithQuirksMode }
|
13
|
+
|
14
|
+
module Apiture
|
15
|
+
class RequestContext
|
16
|
+
include Apiture::Utils::Inflections
|
17
|
+
|
18
|
+
attr_reader :options, :group, :endpoint, :attributes
|
19
|
+
|
20
|
+
def initialize(options, group, endpoint, attributes)
|
21
|
+
@options, @group, @endpoint, @attributes = options, group, endpoint, attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform
|
25
|
+
url = endpoint.url.dup
|
26
|
+
|
27
|
+
if url.base_host
|
28
|
+
url.subdomain = options[:subdomain]
|
29
|
+
end
|
30
|
+
|
31
|
+
env = {
|
32
|
+
method: endpoint.request_method,
|
33
|
+
url: url,
|
34
|
+
params: {},
|
35
|
+
request_headers: {},
|
36
|
+
logger: logger,
|
37
|
+
context: self
|
38
|
+
}
|
39
|
+
|
40
|
+
endpoint.middleware_stack.call(env)
|
41
|
+
perform_request(env)
|
42
|
+
end
|
43
|
+
|
44
|
+
def authenticator
|
45
|
+
group.authenticator
|
46
|
+
end
|
47
|
+
|
48
|
+
def logger
|
49
|
+
group.logger
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_attribute(name)
|
53
|
+
name = name.to_sym
|
54
|
+
unless attributes.has_key?(name)
|
55
|
+
name = underscore(name).to_sym
|
56
|
+
end
|
57
|
+
attributes[name]
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
def perform_request(env)
|
62
|
+
request_method = env[:method] ? env[:method].to_sym : :get
|
63
|
+
headers = env[:request_headers]
|
64
|
+
|
65
|
+
conn = Faraday.new do |faraday|
|
66
|
+
faraday.adapter Faraday.default_adapter
|
67
|
+
configure_format_handlers(faraday, headers)
|
68
|
+
end
|
69
|
+
|
70
|
+
if auth = env[:basic_auth]
|
71
|
+
conn.basic_auth(auth[:username], auth[:password])
|
72
|
+
end
|
73
|
+
|
74
|
+
conn.__send__(request_method) do |req|
|
75
|
+
req.url env[:url].to_s, env[:params]
|
76
|
+
req.headers.merge!(headers) if headers
|
77
|
+
if body = env[:body]
|
78
|
+
req.body = body
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def configure_format_handlers(conn, headers)
|
84
|
+
if headers && accept = headers["Accept"]
|
85
|
+
content_types = accept.split(",").map { |s| s.split(";").first.strip }
|
86
|
+
content_types.each do |type|
|
87
|
+
content_type_mappings.each do |(parser, type_match)|
|
88
|
+
if type.match(type_match)
|
89
|
+
conn.response(parser, content_type: type_match, preserve_raw: true)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def content_type_mappings
|
97
|
+
[
|
98
|
+
[:xml, /\bxml$/],
|
99
|
+
[:json_quirks, /\bjson$/]
|
100
|
+
]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'apiture/swagger/node'
|
2
|
+
|
3
|
+
module Apiture
|
4
|
+
module Swagger
|
5
|
+
class DataTypeField < Node
|
6
|
+
attribute :type, symbolize: true
|
7
|
+
attribute :format
|
8
|
+
attribute :default_value
|
9
|
+
attribute :multiple_of
|
10
|
+
attribute :maximum
|
11
|
+
attribute :exclusive_maximum, type: :boolean
|
12
|
+
attribute :minimum
|
13
|
+
attribute :exclusive_minimum, type: :boolean
|
14
|
+
attribute :max_length
|
15
|
+
attribute :min_length
|
16
|
+
attribute :pattern
|
17
|
+
attribute :max_items
|
18
|
+
attribute :min_items
|
19
|
+
attribute :unique_items, type: :boolean
|
20
|
+
attribute :items
|
21
|
+
|
22
|
+
list :enum
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'apiture/swagger/node'
|
2
|
+
require 'apiture/swagger/property'
|
3
|
+
|
4
|
+
module Apiture
|
5
|
+
module Swagger
|
6
|
+
class Definition < Node
|
7
|
+
attr_reader :id
|
8
|
+
|
9
|
+
attribute :required
|
10
|
+
attribute :discriminator
|
11
|
+
|
12
|
+
hash :properties
|
13
|
+
|
14
|
+
def initialize(id = "<Inline>")
|
15
|
+
super()
|
16
|
+
@id = id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|