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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE +20 -0
  5. data/README.md +38 -0
  6. data/Rakefile +6 -0
  7. data/apiture.gemspec +30 -0
  8. data/lib/apiture.rb +24 -0
  9. data/lib/apiture/api_base.rb +27 -0
  10. data/lib/apiture/api_builder.rb +196 -0
  11. data/lib/apiture/api_error.rb +3 -0
  12. data/lib/apiture/api_group.rb +28 -0
  13. data/lib/apiture/data_model.rb +24 -0
  14. data/lib/apiture/endpoint.rb +25 -0
  15. data/lib/apiture/middleware/auth/api_key.rb +39 -0
  16. data/lib/apiture/middleware/auth/basic.rb +25 -0
  17. data/lib/apiture/middleware/auth/oauth2.rb +31 -0
  18. data/lib/apiture/middleware/convert_json_body.rb +15 -0
  19. data/lib/apiture/middleware/debug.rb +20 -0
  20. data/lib/apiture/middleware/set_body_parameter.rb +20 -0
  21. data/lib/apiture/middleware/set_header.rb +15 -0
  22. data/lib/apiture/middleware/set_parameter_base.rb +37 -0
  23. data/lib/apiture/middleware/set_path_parameter.rb +18 -0
  24. data/lib/apiture/middleware/set_query_parameter.rb +13 -0
  25. data/lib/apiture/middleware_builder.rb +15 -0
  26. data/lib/apiture/middleware_stack.rb +21 -0
  27. data/lib/apiture/request_context.rb +103 -0
  28. data/lib/apiture/swagger/data_type_field.rb +25 -0
  29. data/lib/apiture/swagger/definition.rb +20 -0
  30. data/lib/apiture/swagger/external_docs.rb +10 -0
  31. data/lib/apiture/swagger/info.rb +14 -0
  32. data/lib/apiture/swagger/node.rb +149 -0
  33. data/lib/apiture/swagger/operation.rb +32 -0
  34. data/lib/apiture/swagger/parameter.rb +35 -0
  35. data/lib/apiture/swagger/parser.rb +148 -0
  36. data/lib/apiture/swagger/path.rb +37 -0
  37. data/lib/apiture/swagger/property.rb +15 -0
  38. data/lib/apiture/swagger/security.rb +21 -0
  39. data/lib/apiture/swagger/security_definition.rb +23 -0
  40. data/lib/apiture/swagger/specification.rb +31 -0
  41. data/lib/apiture/uri.rb +36 -0
  42. data/lib/apiture/utils/inflections.rb +46 -0
  43. data/lib/apiture/version.rb +3 -0
  44. data/spec/apiture/api_builder_spec.rb +20 -0
  45. data/spec/apiture/swagger/parser_spec.rb +107 -0
  46. data/spec/apiture/swagger/specification_spec.rb +19 -0
  47. data/spec/apiture_spec.rb +228 -0
  48. data/spec/files/github.json +186 -0
  49. data/spec/files/harvest.json +75 -0
  50. data/spec/files/honeybadger.json +80 -0
  51. data/spec/files/mandrill.json +502 -0
  52. data/spec/files/pivotal_tracker.json +94 -0
  53. data/spec/files/slack.json +88 -0
  54. data/spec/files/uber.json +43 -0
  55. data/spec/fixtures/vcr_cassettes/github_checkAuthorization.yml +70 -0
  56. data/spec/fixtures/vcr_cassettes/github_getUser.yml +82 -0
  57. data/spec/fixtures/vcr_cassettes/harvest_invoiceList.yml +85 -0
  58. data/spec/fixtures/vcr_cassettes/honeybadger.yml +98 -0
  59. data/spec/fixtures/vcr_cassettes/mandrill_messageSend.yml +44 -0
  60. data/spec/fixtures/vcr_cassettes/mandrill_userPing.yml +44 -0
  61. data/spec/fixtures/vcr_cassettes/pivotal_tracker_create_story.yml +61 -0
  62. data/spec/fixtures/vcr_cassettes/slack.yml +43 -0
  63. data/spec/fixtures/vcr_cassettes/uber_getProducts.yml +60 -0
  64. data/spec/spec_helper.rb +11 -0
  65. 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,15 @@
1
+ module Apiture
2
+ module Middleware
3
+ class ConvertJSONBody
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+ def call(env)
8
+ @app.call(env)
9
+ if body = env[:body]
10
+ env[:body] = MultiJson.dump(body)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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,15 @@
1
+ module Apiture
2
+ module Middleware
3
+ class SetHeader
4
+ def initialize(app, headers)
5
+ @app = app
6
+ @headers = headers
7
+ end
8
+
9
+ def call(env)
10
+ env[:request_headers].merge!(@headers)
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ 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