opi 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d5fda4ffbb7acce5d6c4a7f0f0fcc91d9c1cd2f
4
- data.tar.gz: 1e27c75791cd7020ae3e94657dca5b42eedeebd6
3
+ metadata.gz: c83ed04de99dfd5352546b0548455cd791b4434c
4
+ data.tar.gz: 436349cc1d191ae3d38447f991fdac2f64ef26c2
5
5
  SHA512:
6
- metadata.gz: 3628bf167bae180b00fc035355e8841b0840eeed852c4e7d040563198fb3b24ac531c637066d40937b1b39692b6ea96954c8a635378e03dc5c39b968a240984c
7
- data.tar.gz: 12ebe8eb10d4f4144f841051d03d8a11d6331363d47d8332f4d54e98b61c64ebe44f3bb3bf0b2a734ad210ea52b14a4ee8de8c6a948b97e9d7f20d98d4a68f94
6
+ metadata.gz: 9d446295c4c31a858d8d0a1795c4fc87e864fb47bad18b143349f3a20b7138165fda6669ed1d04f3ea512ba730464eeb81152bc8722dda339b2e006c812a59d2
7
+ data.tar.gz: 25f21eae30b44928e3af096159e39bda11f7d0ee2d098d1873e4337a8b6ef56f2df2dc191876ac84fcf008569fad7f55b84bec0c10be42b0cac43afc7875fd88
@@ -1,10 +1,10 @@
1
- h1. Opi - The very opinionated API service library
1
+ # Opi - The very opinionated API service library
2
2
 
3
- h2. Install the gem
3
+ ## Install the gem
4
4
 
5
5
  gem 'opi'
6
6
 
7
- h2. About
7
+ ## About
8
8
 
9
9
  Opi is a very opinionated rack-compliant API service library. In fact, it is
10
10
  so opinionated it is very likely to offend. But that is alright.
@@ -12,64 +12,75 @@ so opinionated it is very likely to offend. But that is alright.
12
12
  Opi was born out of frustration with writing too much boilerplate code for api
13
13
  services.
14
14
 
15
- *JSON-only*. The server CANNOT respond with anything else other than JSON.
15
+ **JSON-only**. The server CANNOT respond with anything else other than JSON.
16
16
  All error responses are JSON out of the box. You CANNOT respond with HTML.
17
17
  The response content type is hardcoded to JSON.
18
18
 
19
- *No Controllers*. Well.. there are route blocks which are the equivalent
19
+ **No Controllers**. Well.. there are route blocks which are the equivalent
20
20
  of the controller but you are strongly encouraged to only ever execute actions
21
21
  in these blocks (the server is looking out for those actions as responses).
22
22
  The only role of the 'controller' here is to map HTTP inputs to Action inputs.
23
23
 
24
- *Action-based*. All logic is an action. Actions validate their own inputs
24
+ **Action-based**. All logic is an action. Actions validate their own inputs
25
25
  and have no access to anything HTTP-related. These are domain-specific actions
26
26
  that can be pulled out and used anywhere.
27
27
 
28
- *No Sessions or Cookies*. None.
28
+ **No Sessions or Cookies**. None.
29
29
 
30
30
  But this has its advantages. It is *fast* and *simple*.
31
31
 
32
- h2. Example
32
+ ## Example
33
33
 
34
34
  This simple example doesn't really go into the detail of Actions but it does
35
35
  demonstrate the routes, responses, before filters and helpers.
36
36
 
37
37
  <code>api.rb</code>
38
38
 
39
- <pre><code>module Example
39
+ ```ruby
40
+ module Example
40
41
  class API < Opi::API
41
42
 
42
43
  before :authorize!
43
44
 
45
+ get '/boom', :skip => :authorize! do
46
+ raise 'boom'
47
+ end
48
+
44
49
  get '/ping', :skip => :authorize! do
45
50
  {:pong => Time.now.to_i}
46
51
  end
47
52
 
48
- get '/meaning' do
53
+ get '/users/:id' do
54
+ params
55
+ end
56
+
57
+ post '/meaning' do
49
58
  {:answer => 42}
50
59
  end
51
60
 
52
61
  helpers do
53
62
  def authorize!
54
- error!('401 Unauthorized', 401) unless params['secret'] == '1234'
63
+ error!('401 Unauthorized', 401) unless params[:secret] == '1234'
55
64
  end
56
65
  end
57
66
 
58
67
  end
59
68
  end
60
- </code></pre>
69
+ ```
61
70
 
62
71
  <code>config.ru</code>
63
72
 
64
- <pre><code>require 'opi'
73
+ ```ruby
74
+ require 'opi'
65
75
 
66
76
  load './api.rb'
67
77
  run Example::API.new
68
- </code></pre>
78
+ ```
69
79
 
70
80
  <code>output</code>
71
81
 
72
- <pre><code>$ curl -i http://0.0.0.0:9292/ping
82
+ ```bash
83
+ $ curl -i http://0.0.0.0:9292/ping
73
84
  HTTP/1.1 200 OK
74
85
  Content-Type: application/json
75
86
  Transfer-Encoding: chunked
@@ -90,10 +101,24 @@ Transfer-Encoding: chunked
90
101
 
91
102
  {"answer":42}
92
103
 
104
+ $ curl -i http://0.0.0.0:9292/users/1?secret=1234
105
+ HTTP/1.1 200 OK
106
+ Content-Type: application/json
107
+ Transfer-Encoding: chunked
108
+
109
+ {"secret":"1234","id":"1"}
110
+
93
111
  $ curl -i http://0.0.0.0:9292/nonexistant
94
112
  HTTP/1.1 404 Not Found
95
113
  Content-Type: application/json
96
114
  Transfer-Encoding: chunked
97
115
 
98
116
  {"error":"404 Not Found"}
99
- </code></pre>
117
+
118
+ $ curl -i http://0.0.0.0:9292/boom
119
+ HTTP/1.1 500 Internal Server Error
120
+ Content-Type: application/json
121
+ Transfer-Encoding: chunked
122
+
123
+ {"error":"500 Internal Server Error", "message":"boom"}
124
+ ```
data/lib/opi.rb CHANGED
@@ -4,6 +4,7 @@ require 'json'
4
4
  require 'mutations'
5
5
 
6
6
  require_relative './opi/version'
7
+ require_relative './opi/router'
7
8
  require_relative './opi/api'
8
9
  require_relative './opi/request'
9
10
  require_relative './opi/response'
@@ -12,5 +13,4 @@ require_relative './opi/context'
12
13
  require_relative './opi/loader'
13
14
 
14
15
  module Opi
15
- VERSION = '1.0'
16
16
  end
@@ -2,6 +2,9 @@ module Opi
2
2
  class API
3
3
 
4
4
  class << self
5
+
6
+ puts "* Opi Version: #{Opi::VERSION}".green
7
+
5
8
  def get(path, options={}, &block)
6
9
  route 'GET', path, options, block
7
10
  end
@@ -20,7 +23,7 @@ module Opi
20
23
 
21
24
  def route(method, path, options={}, block)
22
25
  # TODO: remove&replace existing routes (on reload)
23
- routes.unshift({:method => method, :path => path, :options => options, :block => block})
26
+ router.routes.unshift({:method => method, :path => path, :options => options, :block => block})
24
27
  end
25
28
 
26
29
  def before(method)
@@ -39,8 +42,8 @@ module Opi
39
42
  @after_filters ||= []
40
43
  end
41
44
 
42
- def routes
43
- @routes ||= []
45
+ def router
46
+ @router ||= Router.new
44
47
  end
45
48
 
46
49
  def helpers(&block)
@@ -57,7 +60,9 @@ module Opi
57
60
 
58
61
  request = Request.new(env)
59
62
 
60
- route = self.class.routes.detect{|x| x[:method] == request.method and x[:path] == request.path}
63
+ route, params = self.class.router.route(request.method, request.path)
64
+ request.params.merge!(params) if params and params.is_a? Hash
65
+ request.params.merge!('splat' => params.join(',')) if params and params.is_a? Array
61
66
 
62
67
  return [404, {'Content-Type' => 'application/json'}, ["{\"error\":\"404 Not Found\"}", "\n"]] unless route
63
68
 
@@ -14,7 +14,7 @@ module Opi
14
14
  end
15
15
 
16
16
  def params
17
- @request.params
17
+ {}.tap {|h| @request.params.each{|x| h[x.first.to_sym] = x.last}}
18
18
  end
19
19
 
20
20
  def error!(message, status)
@@ -43,7 +43,7 @@ module Opi
43
43
  # before filters must have succeeded
44
44
  action = instance_eval &route[:block]
45
45
 
46
- if action.is_a? Opi::Action
46
+ if action.kind_of? Opi::Action or action.kind_of? Mutations::Outcome
47
47
  if action.success?
48
48
  response.status = 200
49
49
  response.body = [action.result.to_json, "\n"]
@@ -0,0 +1,32 @@
1
+ class Router
2
+ attr_accessor :routes
3
+
4
+ WILDCARD_PATTERN = /\/\*/
5
+ NAMED_SEGMENTS_PATTERN = /\/:([^$\/]+)/
6
+ NAMED_SEGMENTS_REPLACEMENT_PATTERN = /\/:([^$\/]+)/
7
+
8
+ def initialize(routes=[])
9
+ @routes = routes
10
+ end
11
+
12
+ def route(method, path)
13
+ method_routes = self.routes.find_all{|x| x[:method] == method}
14
+ method_routes.each do |route|
15
+ if route[:path] =~ WILDCARD_PATTERN
16
+ src = "\\A#{route[:path].gsub('*','(.*)')}\\Z"
17
+ if match = path.match(Regexp.new(src))
18
+ return [route, match[1].split('/')]
19
+ end
20
+ elsif route[:path] =~ NAMED_SEGMENTS_PATTERN
21
+ src = "\\A#{route[:path].gsub(NAMED_SEGMENTS_REPLACEMENT_PATTERN, '/(?<\1>[^$/]+)')}\\Z"
22
+ if match = path.match(Regexp.new(src))
23
+ return [route, Hash[match.names.zip(match.captures)]]
24
+ end
25
+ elsif path == route[:path]
26
+ return [route]
27
+ end
28
+ end
29
+ nil
30
+ end
31
+
32
+ end
@@ -1,3 +1,3 @@
1
1
  module Opi
2
- VERSION = "0.1"
3
- end
2
+ VERSION = "0.2"
3
+ end
metadata CHANGED
@@ -1,69 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opi
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Taylor
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-22 00:00:00.000000000 Z
11
+ date: 2014-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.5.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.5.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: colored
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: json
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: 1.8.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.8.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: mutations
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: 0.6.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.6.0
69
69
  description: The very opinionated API service library.
@@ -72,8 +72,8 @@ executables: []
72
72
  extensions: []
73
73
  extra_rdoc_files: []
74
74
  files:
75
- - README.textile
76
75
  - LICENSE
76
+ - README.md
77
77
  - lib/opi.rb
78
78
  - lib/opi/action.rb
79
79
  - lib/opi/api.rb
@@ -81,29 +81,30 @@ files:
81
81
  - lib/opi/loader.rb
82
82
  - lib/opi/request.rb
83
83
  - lib/opi/response.rb
84
+ - lib/opi/router.rb
84
85
  - lib/opi/version.rb
85
86
  homepage: http://github.com/moomerman/opi
86
87
  licenses: []
87
88
  metadata: {}
88
89
  post_install_message:
89
90
  rdoc_options:
90
- - --inline-source
91
- - --charset=UTF-8
91
+ - "--inline-source"
92
+ - "--charset=UTF-8"
92
93
  require_paths:
93
94
  - lib
94
95
  required_ruby_version: !ruby/object:Gem::Requirement
95
96
  requirements:
96
- - - '>='
97
+ - - ">="
97
98
  - !ruby/object:Gem::Version
98
99
  version: '0'
99
100
  required_rubygems_version: !ruby/object:Gem::Requirement
100
101
  requirements:
101
- - - '>='
102
+ - - ">="
102
103
  - !ruby/object:Gem::Version
103
104
  version: '0'
104
105
  requirements: []
105
106
  rubyforge_project: opi
106
- rubygems_version: 2.0.0
107
+ rubygems_version: 2.2.0.rc.1
107
108
  signing_key:
108
109
  specification_version: 4
109
110
  summary: The very opinionated API service library.