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,21 @@
1
+ require 'apiture/swagger/node'
2
+
3
+ module Apiture
4
+ module Swagger
5
+ class Security < Node
6
+ attr_reader :id
7
+
8
+ attribute :scopes
9
+
10
+ def initialize(id)
11
+ super()
12
+ @id = id
13
+ end
14
+
15
+ def serializable_hash
16
+ scopes || []
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ require 'apiture/swagger/node'
2
+
3
+ module Apiture
4
+ module Swagger
5
+ class SecurityDefinition < Node
6
+ attr_reader :id
7
+
8
+ attribute :type
9
+ attribute :description
10
+ attribute :name
11
+ attribute :in, symbolize: true
12
+ attribute :flow
13
+ attribute :authorization_url
14
+ attribute :token_url
15
+ attribute :scopes
16
+
17
+ def initialize(id)
18
+ super()
19
+ @id = id
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ require 'apiture/swagger/info'
2
+ require 'apiture/swagger/external_docs'
3
+ require 'apiture/swagger/security'
4
+ require 'apiture/swagger/security_definition'
5
+ require 'apiture/swagger/path'
6
+ require 'apiture/swagger/definition'
7
+ # require 'apiture/swagger/response'
8
+
9
+ module Apiture
10
+ module Swagger
11
+ class Specification < Node
12
+ attribute :swagger
13
+ attribute :info, validate: true
14
+ attribute :host
15
+ attribute :base_path
16
+ attribute :external_docs, validate: true
17
+
18
+ list :schemes
19
+ list :consumes
20
+ list :produces
21
+ list :tags, validate: true
22
+
23
+ hash :paths, validate: true
24
+ hash :definitions, validate: true
25
+ hash :parameters, validate: true
26
+ hash :responses, validate: true
27
+ hash :security_definitions, validate: true
28
+ hash :security, validate: true
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ module Apiture
2
+ class URI
3
+
4
+ components = [:scheme, :host, :base_host, :subdomain, :base_path, :resource_path]
5
+
6
+ attr_reader *components
7
+
8
+ components.each do |comp|
9
+ define_method "#{comp}=".to_sym do |v|
10
+ instance_variable_set("@#{comp}", v)
11
+ build_url
12
+ end
13
+ end
14
+
15
+ def to_s
16
+ build_url
17
+ end
18
+
19
+ protected
20
+
21
+ def build_url
22
+ host = if base_host && subdomain
23
+ [subdomain, base_host].join('.')
24
+ else
25
+ self.host
26
+ end
27
+ [
28
+ scheme,
29
+ '://',
30
+ host,
31
+ base_path,
32
+ resource_path
33
+ ].join('')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ module Apiture
2
+ module Utils
3
+ module Inflections
4
+
5
+ def self.acronyms
6
+ @@acronyms ||= {}
7
+ end
8
+
9
+ def self.acronym_regex
10
+ @@acronym_regex ||= /(?=a)b/
11
+ end
12
+
13
+ def camelize(term, uppercase_first_letter = true)
14
+ string = term.to_s
15
+ if uppercase_first_letter
16
+ string = string.sub(/^[a-z\d]*/) { ::Apiture::Utils::Inflections.acronyms[$&] || $&.capitalize }
17
+ else
18
+ string = string.sub(/^(?:#{::Apiture::Utils::Inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
19
+ end
20
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{::Apiture::Utils::Inflections.acronyms[$2] || $2.capitalize}" }
21
+ string.gsub!('/', '::')
22
+ string
23
+ end
24
+
25
+ def underscore(camel_cased_word)
26
+ word = camel_cased_word.to_s.gsub('::', '/')
27
+ word.gsub!(/(?:([A-Za-z\d])|^)(#{::Apiture::Utils::Inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
28
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
29
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
30
+ word.tr!("-", "_")
31
+ word.downcase!
32
+ word
33
+ end
34
+
35
+ def constantize(camel_cased_word)
36
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
37
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
38
+ end
39
+
40
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ Apiture::Utils::Inflections.extend(Apiture::Utils::Inflections)
@@ -0,0 +1,3 @@
1
+ module Apiture
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'apiture'
3
+
4
+ describe Apiture::APIBuilder do
5
+
6
+ def build_spec(name)
7
+ Apiture.parse_specification(File.join(File.dirname(__FILE__), '..', 'files', "#{name}.json"))
8
+ end
9
+
10
+ it "should build a API class from specification" do
11
+ api = Apiture::APIBuilder.new(build_spec("pivotal_tracker")).build
12
+ expect(api).to_not be_nil
13
+ end
14
+
15
+ it "should build a API class from specification" do
16
+ api = Apiture::APIBuilder.new(build_spec("mandrill")).build
17
+ expect(api).to_not be_nil
18
+ end
19
+
20
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+ require 'apiture/swagger/parser'
3
+
4
+ describe Apiture::Swagger::Parser do
5
+ subject { Apiture::Swagger::Parser }
6
+
7
+ def load_spec(name)
8
+ File.read(File.join(File.dirname(__FILE__), '..', '..', 'files', "#{name}.json"))
9
+ end
10
+
11
+ it "should parse a swagger specification" do
12
+ specification = Apiture::Swagger::Parser.new.parse(load_spec('pivotal_tracker'))
13
+ expect(specification).to_not be_nil
14
+
15
+ expect(specification.host).to eq "www.pivotaltracker.com"
16
+ expect(specification.schemes).to include "https"
17
+ expect(specification.base_path).to eq "/services/v5"
18
+
19
+ expect(specification.info).to_not be_nil
20
+ expect(specification.info.title).to eq "PivotalTracker"
21
+ expect(specification.info.version).to eq "5"
22
+
23
+ api_token = specification.security_definitions['apiToken']
24
+ expect(api_token).to_not be_nil
25
+ expect(api_token.type).to eq "apiKey"
26
+ expect(api_token.in).to eq :header
27
+ expect(api_token.name).to eq "X-TrackerToken"
28
+
29
+ security = specification.security['apiToken']
30
+ expect(security).to_not be_nil
31
+
32
+ expect(specification.produces).to include "application/json"
33
+
34
+ expect(specification.paths.count).to eq 1
35
+
36
+ path = specification.paths["/projects/{projectId}/stories"]
37
+ expect(path).to_not be_nil
38
+
39
+ expect(path.post).to_not be_nil
40
+ expect(path.post.operation_id).to eq "createStory"
41
+ expect(path.post.external_docs.url).to eq "https://www.pivotaltracker.com/help/api/rest/v5#projects_project_id_stories_post"
42
+
43
+ expect(path.post.parameters.count).to eq 2
44
+
45
+ param1 = path.post.parameters[0]
46
+ expect(param1).to_not be_nil
47
+ expect(param1.in).to eq :path
48
+ expect(param1.name).to eq "projectId"
49
+ expect(param1).to be_required
50
+
51
+ param2 = path.post.parameters[1]
52
+ expect(param2).to_not be_nil
53
+ expect(param2.in).to eq :body
54
+ expect(param2.name).to eq "story"
55
+ expect(param2).to be_required
56
+ expect(param2.schema).to eq "NewStory"
57
+
58
+ expect(specification.definitions.count).to eq 1
59
+
60
+ definition = specification.definitions["NewStory"]
61
+ expect(definition).to_not be_nil
62
+ expect(definition.required).to eq ["name"]
63
+
64
+ expect(definition.properties.count).to eq 15
65
+
66
+ prop1 = definition.properties["cl_numbers"]
67
+ expect(prop1).to_not be_nil
68
+ expect(prop1.type).to eq :string
69
+
70
+ prop2 = definition.properties["current_state"]
71
+ expect(prop2).to_not be_nil
72
+ expect(prop2.type).to eq :string
73
+ expect(prop2.enum).to eq [
74
+ "accepted",
75
+ "delivered",
76
+ "finished",
77
+ "started",
78
+ "rejected",
79
+ "unstarted",
80
+ "unscheduled"
81
+ ]
82
+ end
83
+
84
+ it "should parse a swagger specification with an array of schema objects" do
85
+ specification = Apiture::Swagger::Parser.new.parse(load_spec('slack'))
86
+ payload = specification.definitions["Payload"]
87
+
88
+ attachments_prop = payload.properties['attachments']
89
+ expect(attachments_prop.type).to eq :array
90
+ expect(attachments_prop.items.count).to eq 1
91
+ expect(attachments_prop.items["$ref"]).to eq '#/definitions/Attachment'
92
+ end
93
+
94
+ it "should parse a swagger specification with operation specific security" do
95
+ specification = Apiture::Swagger::Parser.new.parse(load_spec('github'))
96
+ op = specification.paths["/applications/{clientId}/tokens/{accessToken}"].get
97
+ expect(op.security["basic"]).to_not be_nil
98
+ end
99
+
100
+ it "should parse nested schema objects" do
101
+ specification = Apiture::Swagger::Parser.new.parse(load_spec('mandrill'))
102
+ op = specification.paths["/messages/send.json"].post
103
+ request_param = op.parameters.first
104
+ expect(specification.consumes).to include "application/json"
105
+ expect(request_param.schema.class).to eq Apiture::Swagger::Definition
106
+ end
107
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ require 'apiture/swagger/specification'
3
+
4
+ describe Apiture::Swagger::Specification do
5
+
6
+ it "should serialize security node as key and scopes" do
7
+ spec = described_class.new
8
+
9
+ security = Apiture::Swagger::Security.new('oauth')
10
+ security.scopes = ['email']
11
+ spec.security = { 'oauth' => security }
12
+
13
+ hash = spec.serializable_hash
14
+ security_hash = hash['security']
15
+ expect(security_hash).to_not be_nil
16
+ scopes = security_hash['oauth']
17
+ expect(scopes).to eq ['email']
18
+ end
19
+ end
@@ -0,0 +1,228 @@
1
+ require 'spec_helper'
2
+ require 'apiture'
3
+ require 'logger'
4
+
5
+ describe Apiture do
6
+
7
+ def configure_client(client)
8
+ #client.logger = Logger.new(STDOUT)
9
+ #client.logger.level = Logger::DEBUG
10
+ client
11
+ end
12
+
13
+ def load_api(name)
14
+ fn = File.join(File.dirname(__FILE__), 'files', "#{name}.json")
15
+ Apiture.load_api(fn)
16
+ end
17
+
18
+ describe do
19
+ let(:parser) { double("parser") }
20
+
21
+ before do
22
+ allow(File).to receive(:read)
23
+ expect(Apiture::Swagger::Parser).to receive(:new).and_return(parser)
24
+ end
25
+
26
+ it "should auto-detect whether it is parsing YAML file" do
27
+ expect(parser).to receive(:parse_yaml)
28
+ Apiture.parse_specification("foo.yml")
29
+ end
30
+
31
+ it "should auto-detect whether it is parsing JSON file" do
32
+ expect(parser).to receive(:parse_json)
33
+ Apiture.parse_specification("foo.json")
34
+ end
35
+ end
36
+
37
+ describe "Pivotal Tracker API" do
38
+ before do
39
+ PivotalTracker = load_api('pivotal_tracker')
40
+ @client = configure_client(PivotalTracker.new(api_token: 'afaketoken'))
41
+ end
42
+
43
+ after do
44
+ Object.send(:remove_const, :PivotalTracker)
45
+ end
46
+
47
+ it "should be able to build a client" do
48
+ VCR.use_cassette('pivotal_tracker_create_story') do
49
+ result = @client.create_story(
50
+ project_id: '1053124',
51
+ story: {
52
+ name: 'Testing Pivotal API',
53
+ story_type: :chore
54
+ })
55
+ expect(result.body['kind']).to eq 'story'
56
+ expect(result.body).to have_key('id')
57
+ expect(result.status).to eq 200
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "Honeybadger API" do
63
+ before do
64
+ Honeybadger = load_api('honeybadger')
65
+ @client = configure_client(Honeybadger.new(auth_token: 'afaketoken'))
66
+ end
67
+
68
+ after do
69
+ Object.send(:remove_const, :Honeybadger)
70
+ end
71
+
72
+ it "should be able to build a Honeybadger API client" do
73
+ VCR.use_cassette('honeybadger') do
74
+ results = @client.get_faults(project_id: 3167)
75
+ expect(results.body['results'].first).to have_key('project_id')
76
+ expect(results.status).to eq 200
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "Slack API" do
82
+ before do
83
+ Slack = load_api('slack')
84
+ @client = configure_client(Slack.new)
85
+ end
86
+
87
+ after do
88
+ Object.send(:remove_const, :Slack)
89
+ end
90
+
91
+ it "should be able to build a Slack API client" do
92
+ VCR.use_cassette('slack') do
93
+ results = @client.post_message(path: 'abcd/efgh/ijkl',
94
+ payload: {
95
+ text: 'Testing',
96
+ username: 'TestBot',
97
+ attachments: [{
98
+ fallback: "test fallback text",
99
+ text: "test text",
100
+ pretext: "test pretext",
101
+ fields: [{
102
+ title: "test field 1",
103
+ value: "test value 1"
104
+ },{
105
+ title: "test field 2",
106
+ value: "test value 2"
107
+ }]
108
+ }]
109
+ })
110
+ expect(results.status).to eq 200
111
+ expect(results.body).to eq "ok"
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "Github API" do
117
+ before do
118
+ Github = load_api("github")
119
+ @client = configure_client(Github.new(
120
+ oauth2: { token: "afaketoken" },
121
+ basic: {
122
+ username: "afakeclientid",
123
+ password: "afakeclientsecret"
124
+ }
125
+ ))
126
+ end
127
+
128
+ after do
129
+ Object.send(:remove_const, :Github)
130
+ end
131
+
132
+ it "should be able to call a zero parameter operation" do
133
+ VCR.use_cassette('github_getUser') do
134
+ results = @client.get_user
135
+ expect(results.status).to eq 200
136
+ expect(results.body['login']).to eq "cyu"
137
+ end
138
+ end
139
+
140
+ it "should be able to use the appropriate security scheme" do
141
+ VCR.use_cassette('github_checkAuthorization') do
142
+ results = @client.check_authorization(
143
+ client_id: "afakeclientid",
144
+ access_token: "afaketoken"
145
+ )
146
+ expect(results.status).to eq 200
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "Uber API" do
152
+ before do
153
+ Uber = load_api("uber")
154
+ @client = configure_client(Uber.new(
155
+ server_token: "TEST_SERVER_TOKEN"
156
+ ))
157
+ end
158
+
159
+ after do
160
+ Object.send(:remove_const, :Uber)
161
+ end
162
+
163
+ it "should be able to support x-format extension for apiKey" do
164
+ VCR.use_cassette('uber_getProducts') do
165
+ results = @client.get_products(latitude: 33.776776, longitude: -84.389683)
166
+ expect(results.status).to eq 200
167
+ expect(results.body["products"].length).to eq 5
168
+ end
169
+ end
170
+ end
171
+
172
+ describe "Mandrill API" do
173
+ before do
174
+ Mandrill = load_api("mandrill")
175
+ @client = configure_client(Mandrill.new(
176
+ key: "fakekey"
177
+ ))
178
+ end
179
+
180
+ after do
181
+ Object.send(:remove_const, :Mandrill)
182
+ end
183
+
184
+ it "should be able to support apiKey in JSON body" do
185
+ VCR.use_cassette('mandrill_userPing') do
186
+ result = @client.user_ping
187
+ expect(result.body).to eq "PONG!"
188
+ end
189
+ end
190
+
191
+ it "should be able to build request with apiKey and parameters in JSON body" do
192
+ VCR.use_cassette('mandrill_messageSend') do
193
+ result = @client.message_send(request: {
194
+ message: {
195
+ text: "testing",
196
+ subject: "testing",
197
+ from_email: "test@test.com",
198
+ to: [ { email: "test@example.com" } ]
199
+ }
200
+ }).body
201
+ expect(result.first["email"]).to eq "test@example.com"
202
+ expect(result.first["status"]).to eq "sent"
203
+ end
204
+ end
205
+ end
206
+
207
+ describe "Harvest API" do
208
+ before do
209
+ Harvest = load_api("harvest")
210
+ @client = configure_client(Harvest.new(
211
+ subdomain: "fakedomain",
212
+ oauth2: {
213
+ token: "faketoken"
214
+ }))
215
+ end
216
+
217
+ after do
218
+ Object.send(:remove_const, :Harvest)
219
+ end
220
+
221
+ it "should support x-base-host extension" do
222
+ VCR.use_cassette('harvest_invoiceList') do
223
+ result = @client.invoice_list.body
224
+ expect(result.length).to eq 2
225
+ end
226
+ end
227
+ end
228
+ end