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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 160ccb2fb353faafd6d123a0b8a85cf274105a60
|
4
|
+
data.tar.gz: a9a7661f0953d01ec9146fa2b406833bb83ad9fe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e182986957f2b292e3df895db859b6dd60aaabb112053b0efe467e760e1ad342568b6f8264bf14199c5631323d7121c261a75124a340966ed28770a1293193b0
|
7
|
+
data.tar.gz: 786d2f22e4d43157f07a81780ccf0462b297ebb7c4d912db01e6790f9330eea1689ca195e747ef76882d4fd23ca4a8d2a5685a3523ffc368d2b4b48b1c4f4af9
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Calvin Yu
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Apiture Gem
|
2
|
+
|
3
|
+
Create clients for REST APIs from their Swagger specification
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'apiture'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install apiture
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
PivotalTracker = Apiture.load_api("pivotal_tracker.json")
|
23
|
+
client = PivotalTracker.new(api_token: 'afaketoken')
|
24
|
+
client.create_story(
|
25
|
+
project_id: '1234567',
|
26
|
+
story: {
|
27
|
+
name: 'Testing Pivotal API',
|
28
|
+
story_type: :chore
|
29
|
+
})
|
30
|
+
```
|
31
|
+
|
32
|
+
## Contributing
|
33
|
+
|
34
|
+
1. Fork it
|
35
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
36
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
37
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
38
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/apiture.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'apiture/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "apiture"
|
8
|
+
spec.version = Apiture::VERSION
|
9
|
+
spec.authors = ["Calvin Yu"]
|
10
|
+
spec.email = ["me@sourcebender.com"]
|
11
|
+
spec.description = %q{Create clients for REST APIs from their Swagger specification }
|
12
|
+
spec.summary = %q{}
|
13
|
+
spec.homepage = "http://github.com/cyu/apiture"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.11.0"
|
22
|
+
spec.add_development_dependency "rake", "~> 11.1.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.4.0"
|
24
|
+
spec.add_development_dependency "vcr", "~> 2.9.0"
|
25
|
+
spec.add_development_dependency "webmock", "~> 1.20.0"
|
26
|
+
|
27
|
+
spec.add_dependency "faraday", "~> 0.9.0"
|
28
|
+
spec.add_dependency "faraday_middleware", "~> 0.10.0"
|
29
|
+
spec.add_dependency "multi_json", "~> 1.3"
|
30
|
+
end
|
data/lib/apiture.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'apiture/version'
|
2
|
+
require 'apiture/swagger/parser'
|
3
|
+
require 'apiture/api_builder'
|
4
|
+
|
5
|
+
|
6
|
+
module Apiture
|
7
|
+
|
8
|
+
def self.parse_specification(path)
|
9
|
+
if path.match(/\.yml$/)
|
10
|
+
Apiture::Swagger::Parser.new.parse_yaml(File.read(path))
|
11
|
+
else
|
12
|
+
Apiture::Swagger::Parser.new.parse_json(File.read(path))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.build_api(specification)
|
17
|
+
Apiture::APIBuilder.new(specification).build
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.load_api(path)
|
21
|
+
build_api(parse_specification(path))
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'apiture/api_group'
|
2
|
+
|
3
|
+
module Apiture
|
4
|
+
class APIError < Exception; end
|
5
|
+
end
|
6
|
+
|
7
|
+
module Apiture
|
8
|
+
class APIBase
|
9
|
+
attr_accessor :logger, :options
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def logger=(logger)
|
16
|
+
@logger = logger
|
17
|
+
self.class.api_groups.each { |g| g.logger = logger }
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def api_groups; @api_groups ||= []; end
|
22
|
+
def api_groups=(api_groups)
|
23
|
+
@api_groups = api_groups
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'apiture/version'
|
2
|
+
require 'apiture/api_error'
|
3
|
+
require 'apiture/utils/inflections'
|
4
|
+
require 'apiture/api_base'
|
5
|
+
require 'apiture/data_model'
|
6
|
+
require 'apiture/uri'
|
7
|
+
|
8
|
+
require 'apiture/middleware/debug'
|
9
|
+
require 'apiture/middleware/set_header'
|
10
|
+
require 'apiture/middleware/convert_json_body'
|
11
|
+
|
12
|
+
module Apiture
|
13
|
+
class APIBuilder
|
14
|
+
include Apiture::Utils::Inflections
|
15
|
+
|
16
|
+
attr_reader :specification
|
17
|
+
|
18
|
+
def initialize(specification)
|
19
|
+
@specification = specification
|
20
|
+
end
|
21
|
+
|
22
|
+
def build
|
23
|
+
validate!
|
24
|
+
|
25
|
+
create_api_class.tap do |klass|
|
26
|
+
klass.api_groups = [
|
27
|
+
APIGroup.new.tap do |group|
|
28
|
+
group.endpoints = build_endpoints
|
29
|
+
group.endpoints.each do |endpoint|
|
30
|
+
klass.send(:define_method, endpoint.name.to_sym) do |*args|
|
31
|
+
group.execute(self.options, endpoint, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
]
|
36
|
+
|
37
|
+
#Apiture.const_set create_class_name(name), klass
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def validate!
|
44
|
+
api_errors = specification.collect_errors
|
45
|
+
if api_errors.any?
|
46
|
+
raise APIError, "API specification is invalid: \n #{api_errors.join("\n")}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_api_class
|
51
|
+
Class.new(Apiture::APIBase)
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_endpoint_url(specification, path)
|
55
|
+
uri = Apiture::URI.new
|
56
|
+
uri.scheme = specification.schemes.first
|
57
|
+
uri.host = specification.host
|
58
|
+
uri.base_host = specification.extensions[:base_host]
|
59
|
+
uri.base_path = specification.base_path.chomp('/')
|
60
|
+
uri.resource_path = path.sub(/^([^\/])/, '/\1').chomp('/')
|
61
|
+
uri
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_endpoints
|
65
|
+
spec = specification
|
66
|
+
data_models = self.data_models
|
67
|
+
|
68
|
+
spec.paths.map do |path, path_model|
|
69
|
+
path_model.operations_map.map do |(method, operation)|
|
70
|
+
endpoint_name = underscore(operation.operation_id)
|
71
|
+
endpoint_url = build_endpoint_url(spec, path)
|
72
|
+
|
73
|
+
Apiture::Endpoint.new(endpoint_name, endpoint_url, method).tap do |endpoint|
|
74
|
+
endpoint.config_middleware do
|
75
|
+
consumes =
|
76
|
+
(operation.consumes && operation.consumes.first) ||
|
77
|
+
(spec.consumes && spec.consumes.first)
|
78
|
+
|
79
|
+
produces =
|
80
|
+
(operation.produces && operation.produces.first) ||
|
81
|
+
(spec.produces && spec.produces.first)
|
82
|
+
|
83
|
+
security = operation.security || spec.security
|
84
|
+
|
85
|
+
use Apiture::Middleware::Debug
|
86
|
+
use Apiture::Middleware::SetHeader, 'User-Agent' => "apiture-rb/#{Apiture::VERSION}"
|
87
|
+
|
88
|
+
if consumes
|
89
|
+
use Apiture::Middleware::SetHeader, 'Content-Type' => consumes
|
90
|
+
end
|
91
|
+
|
92
|
+
if produces
|
93
|
+
use Apiture::Middleware::SetHeader, 'Accept' => produces
|
94
|
+
end
|
95
|
+
|
96
|
+
security_ids = if security
|
97
|
+
security.keys
|
98
|
+
else
|
99
|
+
if operation.security_definitions && !operation.security_definitions.empty?
|
100
|
+
[operation.security_definitions.keys.first]
|
101
|
+
elsif spec.security_definitions && !spec.security_definitions.empty?
|
102
|
+
[spec.security_definitions.keys.first]
|
103
|
+
else
|
104
|
+
[]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
unless security_ids.empty?
|
109
|
+
security_defs = spec.security_definitions || {}
|
110
|
+
if operation.security_definitions
|
111
|
+
security_defs = security_defs.merge(operation.security_definitions)
|
112
|
+
end
|
113
|
+
|
114
|
+
security_ids.each do |name|
|
115
|
+
security_def = security_defs[name]
|
116
|
+
|
117
|
+
if security_def.nil?
|
118
|
+
raise APIError, "Missing security definition: #{name}, operation=#{endpoint_name}, method=#{method}"
|
119
|
+
end
|
120
|
+
|
121
|
+
case security_def.type
|
122
|
+
when 'apiKey'
|
123
|
+
require 'apiture/middleware/auth/api_key'
|
124
|
+
use Apiture::Middleware::Auth::APIKey,
|
125
|
+
id: Apiture::Utils::Inflections.underscore(name).to_sym,
|
126
|
+
in: security_def.in,
|
127
|
+
name: security_def.name,
|
128
|
+
format: security_def.extensions[:format]
|
129
|
+
when 'oauth2'
|
130
|
+
require 'apiture/middleware/auth/oauth2'
|
131
|
+
use Apiture::Middleware::Auth::OAuth2,
|
132
|
+
id: Apiture::Utils::Inflections.underscore(name).to_sym,
|
133
|
+
in: security_def.in,
|
134
|
+
name: security_def.name
|
135
|
+
when "basic"
|
136
|
+
require "apiture/middleware/auth/basic"
|
137
|
+
use Apiture::Middleware::Auth::Basic,
|
138
|
+
id: Apiture::Utils::Inflections.underscore(name).to_sym
|
139
|
+
else
|
140
|
+
raise APIError, "Unsupported security definition: #{security_def.type}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
operation.parameters.each do |parameter|
|
146
|
+
param_class_path = "apiture/middleware/set_#{parameter.in}_parameter"
|
147
|
+
param_class_name = Apiture::Utils::Inflections.camelize(param_class_path)
|
148
|
+
require param_class_path unless Object::const_defined?(param_class_name)
|
149
|
+
param_class = Apiture::Utils::Inflections.constantize(param_class_name)
|
150
|
+
middleware_opts = {name: parameter.name}
|
151
|
+
if parameter.schema?
|
152
|
+
schema = parameter.schema
|
153
|
+
if schema.kind_of? String
|
154
|
+
schema = data_models[parameter.schema]
|
155
|
+
else
|
156
|
+
schema = DataModel.new(schema)
|
157
|
+
end
|
158
|
+
unless schema
|
159
|
+
raise APIError, "Unspecified schema: #{parameter.schema}; parameter=#{parameter.name}"
|
160
|
+
end
|
161
|
+
middleware_opts[:schema] = schema
|
162
|
+
end
|
163
|
+
if spec.extensions[:version_parameter] && spec.extensions[:version_parameter]["name"] == parameter.name
|
164
|
+
middleware_opts[:default] = spec.extensions[:version_parameter]["version"]
|
165
|
+
end
|
166
|
+
use param_class, middleware_opts
|
167
|
+
end
|
168
|
+
|
169
|
+
if consumes == "application/json" || (operation.parameters && operation.parameters.detect { |p| p.in == :body })
|
170
|
+
use Apiture::Middleware::ConvertJSONBody
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end.flatten.compact
|
176
|
+
end
|
177
|
+
|
178
|
+
def data_models
|
179
|
+
@data_models ||= build_data_models
|
180
|
+
end
|
181
|
+
|
182
|
+
def build_data_models
|
183
|
+
specification.definitions.reduce({}) do |m, (name, definition)|
|
184
|
+
m[name] = DataModel.new(definition)
|
185
|
+
m
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def create_class_name(name)
|
190
|
+
string = name.to_s.sub(/.*\./, '')
|
191
|
+
string = string.sub(/^[a-z\d]*/) { $&.capitalize }
|
192
|
+
string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'apiture/request_context'
|
2
|
+
require 'apiture/endpoint'
|
3
|
+
|
4
|
+
module Apiture
|
5
|
+
class APIGroup
|
6
|
+
attr_accessor :logger
|
7
|
+
|
8
|
+
def endpoints; @endpoints ||= []; end
|
9
|
+
def endpoints=(endpoints)
|
10
|
+
@endpoints = endpoints
|
11
|
+
end
|
12
|
+
|
13
|
+
def authenticators; @authenticators; end
|
14
|
+
def authenticators=(auth)
|
15
|
+
@authenticators = auth
|
16
|
+
end
|
17
|
+
|
18
|
+
def data_models; @data_models ||= {}; end
|
19
|
+
def data_models=(data_models)
|
20
|
+
@data_models = data_models
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute(options, endpoint, *parameters)
|
24
|
+
params = parameters.last || {}
|
25
|
+
RequestContext.new(options, self, endpoint, params).perform
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Apiture
|
4
|
+
class DataModel
|
5
|
+
attr_reader :definition
|
6
|
+
|
7
|
+
def initialize(definition)
|
8
|
+
@definition = definition
|
9
|
+
end
|
10
|
+
|
11
|
+
def build(parameter_name, env)
|
12
|
+
context = env[:context]
|
13
|
+
h = context.get_attribute(parameter_name)
|
14
|
+
return nil unless h
|
15
|
+
|
16
|
+
json = definition.properties.reduce({}) do |m, (name, property)|
|
17
|
+
name = name.to_sym
|
18
|
+
m[name] = h[name] if h[name]; m
|
19
|
+
end
|
20
|
+
|
21
|
+
json
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|