apify 0.3.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 (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