apiture 0.2.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 +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
|