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,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