apiture 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 160ccb2fb353faafd6d123a0b8a85cf274105a60
4
+ data.tar.gz: a9a7661f0953d01ec9146fa2b406833bb83ad9fe
5
+ SHA512:
6
+ metadata.gz: e182986957f2b292e3df895db859b6dd60aaabb112053b0efe467e760e1ad342568b6f8264bf14199c5631323d7121c261a75124a340966ed28770a1293193b0
7
+ data.tar.gz: 786d2f22e4d43157f07a81780ccf0462b297ebb7c4d912db01e6790f9330eea1689ca195e747ef76882d4fd23ca4a8d2a5685a3523ffc368d2b4b48b1c4f4af9
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
19
+ *.swo
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem "pry-byebug"
5
+ end
6
+
7
+ # Specify your gem's dependencies in apiture.gemspec
8
+ gemspec
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.
@@ -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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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
@@ -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,3 @@
1
+ module Apiture
2
+ class APIError < Exception; end
3
+ 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