apify 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +25 -0
  4. data/Rakefile +40 -0
  5. data/VERSION +1 -0
  6. data/apify.gemspec +103 -0
  7. data/app/helpers/apify_helper.rb +14 -0
  8. data/app/views/apify/api/_actions.html.erb +38 -0
  9. data/app/views/apify/api/_client.html.erb +38 -0
  10. data/app/views/apify/api/_overview.html.erb +9 -0
  11. data/app/views/apify/api/_protocol.html.erb +38 -0
  12. data/app/views/apify/api/docs.html.erb +66 -0
  13. data/lib/apify.rb +7 -0
  14. data/lib/apify/action.rb +98 -0
  15. data/lib/apify/api.rb +65 -0
  16. data/lib/apify/api_controller.rb +99 -0
  17. data/lib/apify/client.rb +52 -0
  18. data/lib/apify/errors.rb +12 -0
  19. data/lib/apify/exchange.rb +53 -0
  20. data/lib/apify/schema_helper.rb +56 -0
  21. data/spec/apify/action_spec.rb +35 -0
  22. data/spec/apify/client_spec.rb +24 -0
  23. data/spec/app_root/app/controllers/api_controller.rb +8 -0
  24. data/spec/app_root/app/controllers/application_controller.rb +3 -0
  25. data/spec/app_root/app/models/api.rb +42 -0
  26. data/spec/app_root/config/boot.rb +114 -0
  27. data/spec/app_root/config/database.yml +21 -0
  28. data/spec/app_root/config/environment.rb +14 -0
  29. data/spec/app_root/config/environments/in_memory.rb +0 -0
  30. data/spec/app_root/config/environments/mysql.rb +0 -0
  31. data/spec/app_root/config/environments/postgresql.rb +0 -0
  32. data/spec/app_root/config/environments/sqlite.rb +0 -0
  33. data/spec/app_root/config/environments/sqlite3.rb +0 -0
  34. data/spec/app_root/config/routes.rb +12 -0
  35. data/spec/app_root/lib/console_with_fixtures.rb +4 -0
  36. data/spec/app_root/script/console +7 -0
  37. data/spec/controllers/api_controller_spec.rb +155 -0
  38. data/spec/rcov.opts +2 -0
  39. data/spec/spec.opts +4 -0
  40. data/spec/spec_helper.rb +31 -0
  41. metadata +151 -0
@@ -0,0 +1,7 @@
1
+ doc
2
+ pkg
3
+ *.gem
4
+ .idea
5
+ spec/app_root/log/*
6
+
7
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Henning Koch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ apify
2
+ =====
3
+
4
+ Installation
5
+ ------------
6
+
7
+ Install the gem with
8
+
9
+ sudo gem install apify
10
+
11
+ ...
12
+
13
+ Rails 3 compatibility
14
+ ---------------------
15
+
16
+ We cannot guarantee Rails 3 compatibility at this point, but we will upgrade the gem when Rails 3 is released.
17
+
18
+ Credits
19
+ -------
20
+
21
+ Henning Koch
22
+
23
+ {makandra.com}[http://makandra.com/]
24
+
25
+ {gem-session.com}[http://gem-session.com/]
@@ -0,0 +1,40 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+ require 'spec/rake/spectask'
4
+
5
+ desc 'Default: Run all specs.'
6
+ task :default => :spec
7
+
8
+ desc "Run all specs"
9
+ Spec::Rake::SpecTask.new() do |t|
10
+ t.spec_opts = ['--options', "\"spec/spec.opts\""]
11
+ t.spec_files = FileList['spec/**/*_spec.rb']
12
+ end
13
+
14
+ desc 'Generate documentation for the apify gem.'
15
+ Rake::RDocTask.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'apify'
18
+ rdoc.options << '--line-numbers' << '--inline-source'
19
+ rdoc.rdoc_files.include('README')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ begin
24
+ require 'jeweler'
25
+ Jeweler::Tasks.new do |gemspec|
26
+ gemspec.name = "apify"
27
+ gemspec.summary = "Compact definition of JSON APIs for Rails applications. "
28
+ gemspec.email = "github@makandra.de"
29
+ gemspec.homepage = "http://github.com/makandra/apify"
30
+ gemspec.description = "Compact definition of JSON APIs for Rails applications. "
31
+ gemspec.authors = ["Henning Koch"]
32
+ gemspec.add_dependency 'json'
33
+ gemspec.add_dependency 'jsonschema'
34
+ gemspec.add_dependency 'rest-client'
35
+ end
36
+ Jeweler::GemcutterTasks.new
37
+ rescue LoadError
38
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
39
+ end
40
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,103 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{apify}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Henning Koch"]
12
+ s.date = %q{2010-08-28}
13
+ s.description = %q{Compact definition of JSON APIs for Rails applications. }
14
+ s.email = %q{github@makandra.de}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "MIT-LICENSE",
21
+ "README.md",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "apify.gemspec",
25
+ "app/helpers/apify_helper.rb",
26
+ "app/views/apify/api/_actions.html.erb",
27
+ "app/views/apify/api/_client.html.erb",
28
+ "app/views/apify/api/_overview.html.erb",
29
+ "app/views/apify/api/_protocol.html.erb",
30
+ "app/views/apify/api/docs.html.erb",
31
+ "lib/apify.rb",
32
+ "lib/apify/action.rb",
33
+ "lib/apify/api.rb",
34
+ "lib/apify/api_controller.rb",
35
+ "lib/apify/client.rb",
36
+ "lib/apify/errors.rb",
37
+ "lib/apify/exchange.rb",
38
+ "lib/apify/schema_helper.rb",
39
+ "spec/apify/action_spec.rb",
40
+ "spec/apify/client_spec.rb",
41
+ "spec/app_root/app/controllers/api_controller.rb",
42
+ "spec/app_root/app/controllers/application_controller.rb",
43
+ "spec/app_root/app/models/api.rb",
44
+ "spec/app_root/config/boot.rb",
45
+ "spec/app_root/config/database.yml",
46
+ "spec/app_root/config/environment.rb",
47
+ "spec/app_root/config/environments/in_memory.rb",
48
+ "spec/app_root/config/environments/mysql.rb",
49
+ "spec/app_root/config/environments/postgresql.rb",
50
+ "spec/app_root/config/environments/sqlite.rb",
51
+ "spec/app_root/config/environments/sqlite3.rb",
52
+ "spec/app_root/config/routes.rb",
53
+ "spec/app_root/lib/console_with_fixtures.rb",
54
+ "spec/app_root/script/console",
55
+ "spec/controllers/api_controller_spec.rb",
56
+ "spec/rcov.opts",
57
+ "spec/spec.opts",
58
+ "spec/spec_helper.rb"
59
+ ]
60
+ s.homepage = %q{http://github.com/makandra/apify}
61
+ s.rdoc_options = ["--charset=UTF-8"]
62
+ s.require_paths = ["lib"]
63
+ s.rubygems_version = %q{1.3.6}
64
+ s.summary = %q{Compact definition of JSON APIs for Rails applications.}
65
+ s.test_files = [
66
+ "spec/apify/action_spec.rb",
67
+ "spec/apify/client_spec.rb",
68
+ "spec/spec_helper.rb",
69
+ "spec/app_root/app/controllers/api_controller.rb",
70
+ "spec/app_root/app/controllers/application_controller.rb",
71
+ "spec/app_root/app/models/api.rb",
72
+ "spec/app_root/lib/console_with_fixtures.rb",
73
+ "spec/app_root/config/environments/in_memory.rb",
74
+ "spec/app_root/config/environments/mysql.rb",
75
+ "spec/app_root/config/environments/postgresql.rb",
76
+ "spec/app_root/config/environments/sqlite.rb",
77
+ "spec/app_root/config/environments/sqlite3.rb",
78
+ "spec/app_root/config/boot.rb",
79
+ "spec/app_root/config/environment.rb",
80
+ "spec/app_root/config/routes.rb",
81
+ "spec/controllers/api_controller_spec.rb"
82
+ ]
83
+
84
+ if s.respond_to? :specification_version then
85
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
86
+ s.specification_version = 3
87
+
88
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
89
+ s.add_runtime_dependency(%q<json>, [">= 0"])
90
+ s.add_runtime_dependency(%q<jsonschema>, [">= 0"])
91
+ s.add_runtime_dependency(%q<rest-client>, [">= 0"])
92
+ else
93
+ s.add_dependency(%q<json>, [">= 0"])
94
+ s.add_dependency(%q<jsonschema>, [">= 0"])
95
+ s.add_dependency(%q<rest-client>, [">= 0"])
96
+ end
97
+ else
98
+ s.add_dependency(%q<json>, [">= 0"])
99
+ s.add_dependency(%q<jsonschema>, [">= 0"])
100
+ s.add_dependency(%q<rest-client>, [">= 0"])
101
+ end
102
+ end
103
+
@@ -0,0 +1,14 @@
1
+ module ApifyHelper
2
+
3
+ def api_docs_artifact(action, artifact, nature)
4
+ show_id = "#{nature}_#{artifact}_for_#{action.name}"
5
+ show_link = link_to_function('show', "document.getElementById('#{show_id}').style.display = 'block';")
6
+ download_url = url_for(:action => action.name, artifact.to_sym => nature.to_s)
7
+ download_tooltip = "#{action.method.to_s.upcase} #{download_url} to download"
8
+ download_link = link_to('download', download_url, :method => action.method, :title => download_tooltip)
9
+ json = JSON.pretty_generate(action.send(artifact, nature))
10
+ embedded = "<pre id='#{show_id}' style='display: none'>#{json}</pre>"
11
+ "#{artifact.to_s.humanize} (#{show_link}, #{download_link})#{embedded}".html_safe
12
+ end
13
+
14
+ end
@@ -0,0 +1,38 @@
1
+ <h2 id="api_actions">Available actions</h2>
2
+
3
+ <% api.actions.each do |action| %>
4
+
5
+ <div class="action">
6
+
7
+ <h3><%= action.method.to_s.upcase %> <%= action.name %></h3>
8
+
9
+ <% if action.description %>
10
+ <p><%=h action.description %></p>
11
+ <% end %>
12
+
13
+ <dl>
14
+ <dt>Arguments</dt>
15
+ <dd>
16
+ <% if action.takes_args? %>
17
+ <%= api_docs_artifact(action, :schema, :args) %>
18
+ <br />
19
+ <%= api_docs_artifact(action, :example, :args) %>
20
+ <% else %>
21
+ none
22
+ <% end %>
23
+ </dd>
24
+ <dt>Return value</dt>
25
+ <dd>
26
+ <% if action.returns_value? %>
27
+ <%= api_docs_artifact(action, :schema, :value) %>
28
+ <br />
29
+ <%= api_docs_artifact(action, :example, :value) %>
30
+ <% else %>
31
+ none
32
+ <% end %>
33
+ </dd>
34
+ </dl>
35
+
36
+ </div>
37
+
38
+ <% end %>
@@ -0,0 +1,38 @@
1
+ <h2 id="api_client">Using the Ruby client</h2>
2
+
3
+ <p>
4
+ An easy way to use the API is to use the <code>Apify::Client</code> class.
5
+ It takes care of the protocol details and lets you focus on exchanging data.
6
+ </p>
7
+
8
+ <h3>Installation</h3>
9
+
10
+ First install the Apify gem:
11
+
12
+ <pre><code>sudo gem install apify</code></pre>
13
+
14
+ In Rails 2, add the following to your environment.rb:
15
+
16
+ <pre><code>config.gem 'aegis'</code></pre>
17
+
18
+ In Rails 3, add the following to your Gemfile:
19
+
20
+ <pre><code>gem 'aegis'</code></pre>
21
+
22
+ <h3>Usage</h3>
23
+
24
+ <pre><code>client = Apify::Client.new(:host => 'localhost:3000', :user => 'api', :password => 'secret')
25
+ client.post('/api/hello', :name => 'Jack') # { 'message' => 'Hello Jack' }</code></pre>
26
+
27
+ <p>
28
+ Errors can be caught and inspected like this:
29
+ </p>
30
+
31
+ <pre><code>begin
32
+ client.get('/api/hello', :name => 'Jack') # { 'message' => 'Hello Jack' }
33
+ rescue Apify::RequestFailed => e
34
+ puts "Oh no! The API request failed."
35
+ puts "Message: #{e.message}"
36
+ puts "Response: #{e.response_body}"
37
+ end
38
+ </code></pre>
@@ -0,0 +1,9 @@
1
+ <h2>Overview</h2>
2
+
3
+ <ul>
4
+ <li>The API works by exchanging messages encoded in <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>.</li>
5
+ <li>There are no fancy message formats, it's all just sending arbitrary JSON objects back and forth.</li>
6
+ <li>There are some optional features like validating requests with schemas, but you don't need to use them if you don't like.</li>
7
+ <li>Building a lient that consumes the API is very straightforward. Also if you use Ruby, there is a helpful <a href="#api_client">client class</a> available.</li>
8
+ </ul>
9
+
@@ -0,0 +1,38 @@
1
+ <h2 id="api_protocol">Protocol</h2>
2
+
3
+ <h3>Requests</h3>
4
+
5
+ <ul>
6
+ <li>The API is accessed over plain HTTP.</li>
7
+ <% if authentication_configured? %>
8
+ <li>HTTP basic authentication is used.</li>
9
+ <% end %>
10
+ <li>Take care to use the correct HTTP method (<code>GET, POST, PUT, DELETE</code>) as documented for each action.</li>
11
+ <li>Some (but not all) actions take arguments.</li>
12
+ <li>Action arguments are serialized into a <strong>single</strong> HTTP parameter <code>args</code> as JSON.</li>
13
+ <li>You can download an example for a request's arguments by appending <code>?example=args</code> to the action URL.</li>
14
+ <li>You can download a <a href="http://json-schema.org/">JSON schema</a> for a valid request by appending <code>?schema=args</code> to the action URL. Using the schema and a library like <a href="http://github.com/Constellation/ruby-jsonchema">jasonschema</a> you can validate your requests before sending them. The API will only process requests that conform to this schema.</li>
15
+ </ul>
16
+
17
+ <h3>Responses</h3>
18
+
19
+ <ul>
20
+ <li>Successful requests are returned with a status of 200 (OK).</li>
21
+ <li>The body of a successful response is always a hash, serialized as JSON.</li>
22
+ <li>Requests that have errors are returned with a status of 500 (internal server error). The body of a error response is an error message in plain text (no JSON here).</li>
23
+ <li>You can download an example for a successful response value by appending <code>?example=value</code> to the action URL.</li>
24
+ <li>You can download a <a href="http://json-schema.org/">JSON schema</a> for a successful response by appending <code>?schema=value</code> to the URL. The API guarantees that all responses conform to this schema.</li>
25
+ </ul>
26
+
27
+ <h3>Example for a request/response exchange</h3>
28
+
29
+ <ol>
30
+ <li>
31
+ API client sends to API host: <code>POST http://api-host/api/hello?args=JSON</code>
32
+ <br />
33
+ where JSON is a serialized JSON object: <code>'{ "name": "Jack" }'</code>
34
+ </li>
35
+ <li>
36
+ API host returns a response with a status of 200 (OK) and a serialized JSON object as its body: <code>'{ "message": "Hello Jack" }'</code>
37
+ </li>
38
+ </ol>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html>
3
+ <head>
4
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
5
+ <title>API documentation</title>
6
+ <style type="text/css">
7
+ <!--
8
+ body {
9
+ font-family: arial, helvetica, sans-serif;
10
+ font-size: 15px;
11
+ line-height: 19px;
12
+ }
13
+ #container {
14
+ max-width: 80%;
15
+ margin: 40px auto;
16
+ }
17
+ dt, dd {
18
+ margin-left: 0;
19
+ padding-left: 0;
20
+ }
21
+ dt {
22
+ font-weight: bold;
23
+ }
24
+ dl {
25
+ margin-bottom: 0;
26
+ }
27
+ h1, h2, h3, p, dd, ol, ul {
28
+ margin-bottom: 0.6em;
29
+ margin-top: 0;
30
+ }
31
+ h2 {
32
+ margin-top: 1em;
33
+ }
34
+ code {
35
+ font-family: courier new, monospace;
36
+ font-size: 15px;
37
+ background-color: #eee;
38
+ padding: 0 2px
39
+ }
40
+ pre {
41
+ background-color: #eee;
42
+ padding: 5px;
43
+ }
44
+ .action {
45
+ border: 1px dotted #777;
46
+ padding: 10px;
47
+ margin-bottom: 0.8em;
48
+ background-color: #f4f4f4;
49
+ }
50
+ .action dl dd:last-child {
51
+ margin-bottom: 0;
52
+ }
53
+
54
+ -->
55
+ </style>
56
+ </head>
57
+ <body>
58
+ <div id="container">
59
+ <h1>API documentation</h1>
60
+ <%= render :partial => 'apify/api/overview' %>
61
+ <%= render :partial => 'apify/api/protocol' %>
62
+ <%= render :partial => 'apify/api/client' %>
63
+ <%= render :partial => 'apify/api/actions' %>
64
+ </div>
65
+ </body>
66
+ </html>
@@ -0,0 +1,7 @@
1
+ require 'apify/errors'
2
+ require 'apify/action'
3
+ require 'apify/exchange'
4
+ require 'apify/api'
5
+ require 'apify/schema_helper'
6
+ require 'apify/api_controller'
7
+ require 'apify/client'
@@ -0,0 +1,98 @@
1
+ module Apify
2
+ class Action
3
+
4
+ attr_reader :name, :description, :method
5
+
6
+ def initialize(method, name, &block)
7
+ @method = method
8
+ @name = name
9
+ @schemas = Hash.new({}.freeze)
10
+ instance_eval(&block)
11
+ end
12
+
13
+ def schema(nature, &block)
14
+ nature = nature.to_sym
15
+ if block
16
+ @schemas[nature] = eval_schema(block)
17
+ else
18
+ @schemas[nature]
19
+ end
20
+ end
21
+
22
+ def respond(args = nil, &block)
23
+ if block
24
+ @responder = block
25
+ else
26
+ Apify::Exchange.new.tap do |exchange|
27
+ exchange.respond(args, self)
28
+ end
29
+ end
30
+ end
31
+
32
+ def description(description = nil, &block)
33
+ if description || block
34
+ @description = description ? description : block.call
35
+ else
36
+ @description
37
+ end
38
+ end
39
+
40
+ def takes_args?
41
+ schema(:args) != {}
42
+ end
43
+
44
+ def returns_value?
45
+ schema(:value) != {}
46
+ end
47
+
48
+ def responder
49
+ @responder || lambda {}
50
+ end
51
+
52
+ def example(schema_nature)
53
+ example_for_schema(schema(schema_nature))
54
+ end
55
+
56
+ def uid
57
+ "#{method}_#{name}"
58
+ end
59
+
60
+ private
61
+
62
+ def example_for_schema(schema)
63
+ case schema['type']
64
+ when 'object'
65
+ {}.tap do |object|
66
+ if properties = schema['properties']
67
+ properties.each do |key, value|
68
+ object[key] = example_for_schema(value)
69
+ end
70
+ end
71
+ end
72
+ when 'array'
73
+ [].tap do |array|
74
+ if items = schema['items']
75
+ array.concat [example_for_schema(items)] * 2
76
+ end
77
+ end
78
+ when 'boolean'
79
+ true
80
+ when 'number'
81
+ 2.5
82
+ when 'integer'
83
+ 123
84
+ when 'string'
85
+ 'string'
86
+ else
87
+ raise "Unknown schema type: #{schema['type']}"
88
+ end
89
+ end
90
+
91
+ def eval_schema(schema)
92
+ lathe = Object.new
93
+ lathe.extend Apify::SchemaHelper
94
+ lathe.instance_eval(&schema)
95
+ end
96
+
97
+ end
98
+ end