apical 0.1.4

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 (44) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +24 -0
  4. data/Gemfile.lock +70 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +94 -0
  7. data/Rakefile +55 -0
  8. data/VERSION +1 -0
  9. data/apical.gemspec +122 -0
  10. data/bin/apical +7 -0
  11. data/examples/taco_truck/Gemfile +7 -0
  12. data/examples/taco_truck/Gemfile.lock +50 -0
  13. data/examples/taco_truck/app.rb +13 -0
  14. data/examples/taco_truck/taco_truck.apical +18 -0
  15. data/lib/apical.rb +30 -0
  16. data/lib/apical/adapter.rb +21 -0
  17. data/lib/apical/adapters/http_adapter.rb +30 -0
  18. data/lib/apical/adapters/rack_adapter.rb +16 -0
  19. data/lib/apical/cli.rb +36 -0
  20. data/lib/apical/content_types.rb +62 -0
  21. data/lib/apical/resource.rb +138 -0
  22. data/lib/apical/resource_types.rb +36 -0
  23. data/lib/apical/runner.rb +104 -0
  24. data/lib/apical/writers/console_writer.rb +26 -0
  25. data/lib/apical/writers/html_writer.rb +37 -0
  26. data/spec/apical/adapters/http_adapter_spec.rb +24 -0
  27. data/spec/apical/cli_spec.rb +81 -0
  28. data/spec/apical/content_types_spec.rb +9 -0
  29. data/spec/apical/rack_adapter_spec.rb +12 -0
  30. data/spec/apical_spec.rb +360 -0
  31. data/spec/before_and_after_spec.rb +211 -0
  32. data/spec/fixtures/cli_example_1.rb +7 -0
  33. data/spec/fixtures/cli_example_2.rb +8 -0
  34. data/spec/fixtures/example_require.rb +3 -0
  35. data/spec/fixtures/load_paths_example.apical +1 -0
  36. data/spec/html_writer_spec.rb +117 -0
  37. data/spec/http_apical_spec.rb +23 -0
  38. data/spec/load_paths_spec.rb +12 -0
  39. data/spec/spec_helper.rb +19 -0
  40. data/spec/support/test_apps.rb +34 -0
  41. data/templates/apical_helper.rb +11 -0
  42. data/templates/layout.mustache +180 -0
  43. data/templates/resource.mustache +14 -0
  44. metadata +248 -0
@@ -0,0 +1,26 @@
1
+ module Apical
2
+ class ConsoleWriter
3
+ def initialize(runner)
4
+ @runner = runner
5
+ end
6
+
7
+ def write(stream)
8
+ @runner.run
9
+
10
+ stream.write( @runner.resources.map{|r| resource_text(r) }.join )
11
+ stream.rewind if @runner.resources.any?
12
+ stream
13
+ end
14
+
15
+ def resource_text(resource)
16
+ <<-TXT
17
+ method: #{resource.method}
18
+ path: #{resource.path}
19
+ desc: #{resource.desc}
20
+ params: #{resource.formatted_params}
21
+ response: #{resource.formatted_response}
22
+
23
+ TXT
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ module Apical
2
+ class HtmlWriter
3
+ def initialize(runner)
4
+ @runner = runner
5
+ end
6
+
7
+ def markdown_to_html(str)
8
+ Kramdown::Document.new(str).to_html
9
+ end
10
+
11
+ def write(stream)
12
+ @runner.run
13
+
14
+ stream.write(
15
+ Mustache.to_html(File.read(File.dirname(__FILE__) + '/../../../templates/layout.mustache'), {
16
+ name: @runner.name,
17
+ desc: markdown_to_html(@runner.desc),
18
+ content: @runner.resources.map {|r| resource_html(r) }.join
19
+ })
20
+ )
21
+ stream.rewind
22
+ stream
23
+ end
24
+
25
+ def resource_html(resource)
26
+ Mustache.to_html(File.read(File.dirname(__FILE__) + '/../../../templates/resource.mustache'), {
27
+ method: resource.method,
28
+ path: resource.path,
29
+ desc: markdown_to_html(resource.desc),
30
+ accept: resource.accept,
31
+ params: resource.formatted_params,
32
+ content_type: resource.content_type,
33
+ response: resource.formatted_response
34
+ })
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apical::HttpAdapter do
4
+ before do
5
+ stub_request(:get, "http://mocksite.com/tacos.json").
6
+ to_return(:status => 200, :body => "{some:'json'}", :headers => {})
7
+ stub_request(:post, "http://mocksite.com/tacos.json").
8
+ to_return(:status => 200, :body => "{made: 'a taco'}", :headers => {})
9
+ end
10
+
11
+ subject { Apical::HttpAdapter.new(base_uri: "http://mocksite.com") }
12
+ describe "when making a GET request" do
13
+ it "should return the raw response" do
14
+ subject.get('/tacos.json').body.should == "{some:'json'}"
15
+ end
16
+ end
17
+
18
+ describe "when making a POST request" do
19
+ it "should return the raw response" do
20
+ subject.post("/tacos.json").body.should == "{made: 'a taco'}"
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+ require 'sinatra/base'
4
+ require 'fileutils'
5
+
6
+ describe Apical::CLI do
7
+
8
+ def capture(*streams)
9
+ streams.map! { |stream| stream.to_s }
10
+ begin
11
+ result = StringIO.new
12
+ streams.each { |stream| eval "$#{stream} = result" }
13
+ yield
14
+ ensure
15
+ streams.each { |stream| eval("$#{stream} = #{stream.upcase}") }
16
+ end
17
+ result.string
18
+ end
19
+
20
+ describe "when printing the version information" do
21
+ let(:output) { capture(:stdout) { subject.version } }
22
+ it "should print the version number to STDOUT" do
23
+ output.should == "#{Apical::VERSION}\n"
24
+ end
25
+ end
26
+
27
+ describe "when compiling" do
28
+ before do
29
+ class ExampleApp < Sinatra::Base
30
+ get '/example.json' do
31
+ JSON.generate({ status: 'example, mothafucka' })
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "using an input file" do
37
+ before { subject.options = { app: "ExampleApp" } }
38
+ let(:output) { capture(:stdout) { subject.compile(ROOT.join(*%w{spec fixtures cli_example_1.rb})) } }
39
+
40
+ it "should print its output to the console by default" do
41
+ output.should include("example get resource")
42
+ end
43
+ end
44
+
45
+ describe "with the app specified in the API file itself" do
46
+ let(:output) { capture(:stdout) { subject.compile(ROOT.join(*%w{spec fixtures cli_example_2.rb})) } }
47
+
48
+ it "should print its output to the console by default" do
49
+ output.should include("example get resource")
50
+ end
51
+ end
52
+
53
+ describe "with HTML format specified" do
54
+ before { subject.options = { format: "html" } }
55
+ let(:output) { capture(:stdout) { subject.compile(ROOT.join(*%w{spec fixtures cli_example_1.rb})) } }
56
+
57
+ it "should print HTML output to STDOUT" do
58
+ output.should include("<html")
59
+ end
60
+ end
61
+
62
+ describe "with an output file specified" do
63
+ before do
64
+ subject.options = {
65
+ app: "ExampleApp",
66
+ output_path: ROOT.join(*%w{spec fixtures cli_example_1.html})
67
+ }
68
+ end
69
+
70
+ after do
71
+ FileUtils.rm subject.options[:output_path]
72
+ end
73
+
74
+ it "should write the output to the file instead of stdout" do
75
+ subject.compile(ROOT.join(*%w{spec fixtures cli_example_1.rb}))
76
+ File.read(subject.options[:output_path]).should include('example get resource')
77
+ end
78
+ end
79
+ end
80
+ end
81
+
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apical::JsonContentType do
4
+ its(:to_s) { should == 'json' }
5
+ end
6
+
7
+ describe Apical::FormContentType do
8
+ its(:to_s) { should == 'form' }
9
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apical::RackAdapter do
4
+ subject { Apical::RackAdapter.new(app: TestApp) }
5
+ describe "when making a GET request" do
6
+ it "should return the raw response" do
7
+ subject.get('/tacos.json').body.should ==
8
+ JSON.generate([ { meat: 'beef' }, { meat: 'chicken' } ])
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,360 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Apical do
4
+ def stream_from writer, doc
5
+ StringIO.new.tap do |stream|
6
+ writer.new(doc).write(stream)
7
+ end.read
8
+ end
9
+
10
+ before do
11
+ @doc = Apical.new do
12
+ adapter :rack, app: TestApp
13
+ name "The Tacos API"
14
+ desc "An API for the making and consumption of delicious tacos"
15
+
16
+ get '/tacos.json' do
17
+ desc "Get all tacos"
18
+ end
19
+ end
20
+ end
21
+
22
+ subject { @doc }
23
+
24
+ its(:name) { should == "The Tacos API" }
25
+ its(:desc) { should == "An API for the making and consumption of delicious tacos" }
26
+
27
+ it "should have one resource" do
28
+ subject.resources.length.should == 1
29
+ end
30
+
31
+ it "returns itself after a run" do
32
+ @doc.run.should be(@doc)
33
+ end
34
+
35
+ describe "the /tacos.json resource" do
36
+ before do
37
+ @doc.run
38
+ end
39
+
40
+ subject { @doc.resources.first }
41
+ its(:method) { should == 'GET' }
42
+ its(:path) { should == '/tacos.json' }
43
+ its(:response_body) { should == JSON.generate([ { meat: 'beef' }, { meat: 'chicken' } ]) }
44
+ end
45
+
46
+ context "a Resource" do
47
+ describe "when setting the accept, content type and authorization headers" do
48
+ let(:doc) do
49
+ Apical.new do
50
+
51
+ adapter :rack, app: TestApp
52
+
53
+ get '/tacos.json' do
54
+ desc "Stuff"
55
+ accept :json
56
+ content_type :json
57
+ auth_header "One To Rule Them All"
58
+ end
59
+ end
60
+ end
61
+
62
+ it "sets the headers on the resource" do
63
+ doc.run.resources.first.tap do |res|
64
+ res.accept.should be_a(Apical::JsonContentType)
65
+ res.content_type.should be_a(Apical::JsonContentType)
66
+ res.custom_header('Authorization').should == 'One To Rule Them All'
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "when setting the default accept, content type and authorization headers" do
72
+ before do
73
+ @doc = Apical.new do
74
+ adapter :rack, app: TestApp
75
+ accept :json
76
+ content_type :json
77
+ auth_header "OAuth 123abc456"
78
+
79
+ post '/tacos.json' do
80
+ desc "Make a new delicious taco"
81
+ params do
82
+ { meat: 'beef', lettuce: true }
83
+ end
84
+ end
85
+ end
86
+
87
+ @doc.run
88
+ end
89
+
90
+ subject { @doc }
91
+
92
+ it "should propagate the accept type to its resources" do
93
+ @doc.resources.first.accept.should be_a(Apical::JsonContentType)
94
+ end
95
+
96
+ it "should propagate the content type to its resources" do
97
+ @doc.resources.first.content_type.should be_a(Apical::JsonContentType)
98
+ end
99
+
100
+ it "propagates the authorization header to its resources" do
101
+ @doc.resources.first.auth_header.should == "OAuth 123abc456"
102
+ end
103
+ end
104
+ end
105
+
106
+ describe "when setting a custom header" do
107
+ let(:doc) do
108
+ Apical.new do
109
+ adapter :rack, app: TestApp
110
+ get '/tacos.json' do
111
+ before do
112
+ @character = "Gandalf"
113
+ end
114
+
115
+ header "Custom-Header", "One To Rule Them All"
116
+ header "Custom-Header-By-Lambda", Proc.new { @character }
117
+ end
118
+ end
119
+ end
120
+
121
+ it "sets the header on the resource when specifying a string value" do
122
+ doc.run.resources.first.custom_header('Custom-Header').should == "One To Rule Them All"
123
+ end
124
+
125
+ it "calls the proc bound to the resource when setting a header with a proc value" do
126
+ doc.run.resources.first.custom_header('Custom-Header-By-Lambda').should == "Gandalf"
127
+ end
128
+ end
129
+
130
+ describe "when writing to the console" do
131
+ subject { stream_from Apical::ConsoleWriter, @doc }
132
+
133
+ it "should write the request method" do
134
+ subject.should include('GET')
135
+ end
136
+
137
+ it "should write the request path" do
138
+ subject.should include("/tacos.json")
139
+ end
140
+
141
+ it "should write the response body" do
142
+ subject.should include('meat')
143
+ end
144
+
145
+ describe "when accepting JSON as input" do
146
+ before do
147
+ @doc = Apical.new do
148
+ adapter :rack, app: TestApp
149
+ post '/tacos.json' do
150
+ desc "Make a new delicious taco"
151
+ accept :json
152
+ params do
153
+ { meat: 'beef', lettuce: true }
154
+ end
155
+ end
156
+ end
157
+
158
+ @doc.run
159
+ end
160
+
161
+ subject { stream_from Apical::ConsoleWriter, @doc }
162
+
163
+ it "should prett-print the JSON data" do
164
+ subject.should include(%Q|{\n \"meat\": \"beef\",\n \"lettuce\": true\n}\n|)
165
+ end
166
+ end
167
+
168
+ describe "when providing JSON as output" do
169
+ before do
170
+ @doc = Apical.new do
171
+ adapter :rack, app: TestApp
172
+ post '/tacos.json' do
173
+ desc "Make a new delicious taco"
174
+ content_type :json
175
+ params do
176
+ { meat: 'beef', lettuce: true }
177
+ end
178
+ end
179
+ end
180
+
181
+ @doc.run
182
+ end
183
+
184
+ subject { stream_from Apical::ConsoleWriter, @doc }
185
+
186
+ it "should pretty-print the response JSON data" do
187
+ subject.should include(%Q|"body": "meat=beef&lettuce=true"|)
188
+ end
189
+ end
190
+ end
191
+
192
+ describe "when writing to HTML" do
193
+ subject { stream_from Apical::HtmlWriter, @doc }
194
+
195
+ it "should write the request method" do
196
+ subject.should include('GET')
197
+ end
198
+
199
+ it "should write the request path" do
200
+ subject.should include("/tacos.json")
201
+ end
202
+
203
+ it "should write the response body" do
204
+ subject.should include('meat')
205
+ end
206
+
207
+ describe "when accepting JSON as input" do
208
+ before do
209
+ @doc = Apical.new do
210
+ adapter :rack, app: TestApp
211
+ post '/tacos.json' do
212
+ desc "Make a new delicious taco"
213
+ accept :json
214
+ params do
215
+ { meat: 'beef', lettuce: true }
216
+ end
217
+ end
218
+ end
219
+
220
+ @doc.run
221
+ end
222
+
223
+ subject { stream_from Apical::HtmlWriter, @doc }
224
+
225
+ it "should pretty-print the JSON data in the IN section" do
226
+ subject.should include("{\n &quot;meat&quot;: &quot;beef&quot;,\n &quot;lettuce&quot;: true\n}")
227
+ end
228
+ end
229
+
230
+ describe "when providing JSON as output" do
231
+ before do
232
+ @doc = Apical.new do
233
+ adapter :rack, app: TestApp
234
+ post '/tacos.json' do
235
+ desc "Make a new delicious taco"
236
+ content_type :json
237
+ params do
238
+ { meat: 'beef', lettuce: true }
239
+ end
240
+ end
241
+ end
242
+
243
+ @doc.run
244
+ end
245
+
246
+ subject { stream_from Apical::HtmlWriter, @doc }
247
+
248
+ it "should pretty-print the JSON data in the OUT section" do
249
+ # subject.should include("{\n &quot;meat&quot;: &quot;beef&quot;,\n &quot;lettuce&quot;: true\n}")
250
+ subject.should include("&quot;body&quot;: &quot;meat=beef&amp;lettuce=true&quot;")
251
+ end
252
+ end
253
+ end
254
+
255
+ describe "different request methods" do
256
+ before do
257
+ @doc = Apical.new do
258
+ adapter :rack, app: RequestMethodsApp
259
+ get '/get.json' do
260
+ desc "a get request"
261
+ end
262
+
263
+ post '/post.json' do
264
+ desc "a post request"
265
+ end
266
+
267
+ put '/put.json' do
268
+ desc "a put request"
269
+ end
270
+
271
+ delete '/delete.json' do
272
+ desc "a delete request"
273
+ end
274
+
275
+ options '/options.json' do
276
+ desc "an options request"
277
+ end
278
+ end
279
+ end
280
+
281
+ it "should have all five resources" do
282
+ @doc.resources.length.should == 5
283
+ end
284
+ end
285
+
286
+ describe "when specifying a request with parameters" do
287
+ before do
288
+ @doc = Apical.new do
289
+ adapter :rack, app: TestApp
290
+ post '/tacos.json' do
291
+ accept :json
292
+ content_type :json
293
+ desc "Make a new delicious taco"
294
+ params do
295
+ { meat: 'beef', lettuce: true }
296
+ end
297
+ end
298
+ end
299
+
300
+ @doc.run
301
+ end
302
+
303
+ subject { @doc.resources.first }
304
+
305
+ its(:params) { should == { meat: 'beef', lettuce: true } }
306
+ end
307
+
308
+ describe "when specifying a request with an interpolated path" do
309
+ before do
310
+ @doc = Apical.new do
311
+ adapter :rack, app: TestApp
312
+ get '/tacos/:id.json' do
313
+ accept :json
314
+ content_type :json
315
+ desc "Get a specific taco by its ID"
316
+ params do
317
+ { id: 123 }
318
+ end
319
+ end
320
+ end
321
+
322
+ @doc.run
323
+ end
324
+
325
+ subject { @doc.resources.first }
326
+
327
+ its(:path) { should == '/tacos/:id.json' }
328
+ its(:params) { should == { id: 123 } }
329
+ its(:response_body) { should == JSON.generate({ meat: 'beef', id: '123' }) }
330
+ end
331
+
332
+ describe "when specifying a resource with params that reference instance variables" do
333
+ before do
334
+ @doc = Apical.new do
335
+ adapter :rack, app: TestApp
336
+ get '/tacos/:id.json' do
337
+ accept :json
338
+ content_type :json
339
+ desc "Get a specific taco by its ID"
340
+
341
+ before do
342
+ @taco = { id: 123 }
343
+ end
344
+
345
+ params do
346
+ { id: @taco[:id] }
347
+ end
348
+ end
349
+ end
350
+
351
+ @doc.run
352
+ end
353
+
354
+ subject { @doc.resources.first }
355
+
356
+ its(:params) { should == { id: 123 } }
357
+ end
358
+
359
+ end
360
+