navigable-server 0.4.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 126d577099fc83bd854908258e60fb7e66f50bd2c6756d17d43141662a1a9270
4
- data.tar.gz: 9cd7e23c64365f991eb4a89d348b8585a66039f63a0ac1719bf3b01fdf4c77a8
3
+ metadata.gz: 9290813f3f31d905b2d7dc3a39700b23da833d04ea3397ef09a08af3fa78e3cd
4
+ data.tar.gz: a067219239e0d23bb0fde60c641fa5dbe19fe24cf9d8a52f07ef0c59d87c576d
5
5
  SHA512:
6
- metadata.gz: 778df9f366e96e7a609b995d2271200e5371dd30c5c2fabb7f18bdda82463bcd880954ea9cd9a96ee0d9e0a84b17f11ad18bfb13ece8d92b81d069034847a058
7
- data.tar.gz: 289bad94b8b4a0bb72cec07a40ad87870b6fc03fb6cbc0a3fe39bc50aae311c584e82c975a80baf8704934c138372a74b70e5418a12489b1833ba21efaebd5f6
6
+ metadata.gz: dd4f369eab8a65a32dbef752cb3d315584d3390981ba3784050876382f6cf1974fb2652500b61a865e33c2f577f33ea5a7d35bf5878a6714fb38f1303c5144d1
7
+ data.tar.gz: a17f8c2565b80ae4ea06809b057a2dc7227ce1dd6a55e50edd1babbdaa39857b6dc4a410b5a97a233ae3b06949c92997fc4c53b4ab00b1fa85e63e6dcd183557
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
- --format documentation
2
1
  --color
3
2
  --require spec_helper
data/README.md CHANGED
@@ -30,29 +30,20 @@ A simple, highly-performant, Rack-based router.
30
30
  **[Navigable Server][server]** *(coming soon)*<br>
31
31
  A Rack-based server for building Ruby and Navigable web applications.
32
32
 
33
- </td>
34
- </tr>
35
- <tr height="140">
36
- <td width="130"><img alt="Telescope" src="https://raw.githubusercontent.com/first-try-software/navigable/main/assets/telescope.png"></td>
37
- <td>
38
-
39
- **Navigable API** *(coming soon)*<br>
40
- An extension of Navigable Server for building restful JSON APIs.
41
-
42
33
  </td>
43
34
  </tr>
44
35
  <tr height="140">
45
36
  <td width="130"><img alt="Map" src="https://raw.githubusercontent.com/first-try-software/navigable/main/assets/map.png"></td>
46
37
  <td>
47
38
 
48
- **Navigable GraphQL** *(coming soon)*<br>
39
+ **[Navigable GraphQL][graphql]** *(coming soon)*<br>
49
40
  An extension of Navigable Server for building GraphQL APIs.
50
41
 
51
42
  </td>
52
43
  </tr>
53
44
  </table>
54
45
 
55
- <br><br>
46
+ <br>
56
47
 
57
48
  ## Installation
58
49
 
@@ -133,19 +124,81 @@ class ShowTreasureMapEndpoint
133
124
  end
134
125
  end
135
126
  ```
136
- If you are considering creating a JSON API with `Navigable::Server`, you should know about `Navigable::API` and `Navigable::GraphQL`. Both of these gems extend `Navigable::Server` in ways that bring all of Navigable together from `Commands` and `Observers`, to `Endpoints` and `Resolvers`.
127
+ Alternatively, you can declare that your endpoint executes a specific Navigable command by calling the `executes` method, like this:
137
128
 
138
- ## Development
129
+ ```ruby
130
+ class RecruitSwabbieEndpoint
131
+ extend Navigable::Server::Endpoint
139
132
 
140
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
133
+ responds_to :get, '/ahoy'
134
+ executes :recruit_swabbie
135
+ end
136
+ ```
137
+ This tells the server to automatically execute the command associated with the key `:recruit_swabbie`. The command might look something like this:
141
138
 
142
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
139
+ ```ruby
140
+ class RecruitSwabbie
141
+ extend Navigable::Command
142
+
143
+ corresponds_to :recruit_swabbie
144
+
145
+ def execute
146
+ return failed(recruit) unless swabbie_material?
147
+
148
+ successfully recruited_swabbie
149
+ end
150
+
151
+ private
152
+
153
+ def recruit
154
+ Swabbie.new(params)
155
+ end
156
+
157
+ def swabbie_material?
158
+ !recruit.drunk? && !recruit.pirate? && !recruit.seasick?
159
+ end
160
+
161
+ def recruited_swabbie
162
+ SwabbieRepository.create(recruit)
163
+ end
164
+ end
165
+ ```
166
+ Finally, you can use a Resolver class to handle requests for specific MIME types. Here's a `JSONResolver` class that prepares the data from the command to be returned as JSON:
167
+
168
+ ```ruby
169
+ class JSONResolver
170
+ extend Navigable::Resolver
171
+
172
+ resolves 'application/json'
173
+
174
+ def resolve
175
+ @response
176
+ end
177
+
178
+ def on_success(recruit)
179
+ @response = { json: recruit }
180
+ end
181
+
182
+ def on_failure(recruit)
183
+ @response = { json: { errors: errors(recruit) } }
184
+ end
185
+
186
+ private
187
+
188
+ def errors(recruit)
189
+ errors = []
190
+ errors << 'They are a drunken mess!' if recruit.drunk?
191
+ errors << 'They are wanted for piracy on three continents!' if recruit.pirate?
192
+ errors << 'One step aboard and they turned blue and tossed!' if recruit.seasick?
193
+ end
194
+ end
195
+ ```
196
+ Visit the Navigable Wiki for more information on [Resolvers][resolvers].
143
197
 
144
198
  ## Contributing
145
199
 
146
200
  Bug reports and pull requests are welcome on GitHub at https://github.com/first-try-software/navigable-server. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/first-try-software/navigable-server/blob/master/CODE_OF_CONDUCT.md).
147
201
 
148
-
149
202
  ## License
150
203
 
151
204
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -157,3 +210,5 @@ Everyone interacting in the Navigable::Server project's codebases, issue tracker
157
210
  [navigable]: https://github.com/first-try-software/navigable
158
211
  [router]: https://github.com/first-try-software/navigable-router
159
212
  [server]: https://github.com/first-try-software/navigable-server
213
+ [resolvers]: https://github.com/first-try-software/navigable/wiki/Resolvers
214
+ [graphql]: https://github.com/first-try-software/navigable-graphql
@@ -6,15 +6,19 @@ require 'navigable'
6
6
  require 'rack'
7
7
  require 'rack/accept_media_types'
8
8
  require 'rack/abstract_format'
9
- require 'rack/bodyparser'
10
9
 
11
10
  require 'navigable/server'
12
11
  require 'navigable/server/version'
12
+ require 'navigable/server/parsers/json'
13
+ require 'navigable/server/parsers/null'
14
+ require 'navigable/server/parsers/factory'
13
15
  require 'navigable/server/request'
14
16
  require 'navigable/server/response'
15
17
  require 'navigable/server/rack_adapter'
16
18
  require 'navigable/server/router'
17
19
  require 'navigable/server/endpoint'
20
+ require 'navigable/server/endpoint_command'
21
+ require 'navigable/server/cors'
18
22
 
19
23
  module Navigable
20
24
  module Server
@@ -24,7 +28,7 @@ module Navigable
24
28
 
25
29
  def self.rack_app
26
30
  @server ||= Rack::Builder.new(Server.router) do
27
- use Rack::BodyParser, :parsers => BODY_PARSERS
31
+ use Navigable::Server::CORS::Middleware
28
32
  use Rack::AbstractFormat
29
33
  end
30
34
  end
@@ -0,0 +1,54 @@
1
+ module Navigable
2
+ module Server
3
+ class CORS
4
+ class << self
5
+ def config
6
+ yield(self)
7
+ end
8
+
9
+ def headers
10
+ @headers ||= {}
11
+ end
12
+
13
+ def headers=(headers)
14
+ raise ArgumentError.new("Expected headers to be a Hash, received a #{headers.class} instead") unless headers.is_a?(Hash)
15
+
16
+ @headers = headers
17
+ end
18
+ end
19
+
20
+ class Middleware
21
+ def initialize(app)
22
+ @app = app
23
+ end
24
+
25
+ def call(env)
26
+ return @app.call(env) unless cors_configured? && cors_request?(env)
27
+
28
+ cors_response
29
+ end
30
+
31
+ private
32
+
33
+ def cors_configured?
34
+ !Navigable::Server::CORS.headers.empty?
35
+ end
36
+
37
+ def cors_request?(env)
38
+ env['REQUEST_METHOD'] == 'OPTIONS'
39
+ end
40
+
41
+ def cors_response
42
+ [200, default_headers.merge(Navigable::Server::CORS.headers), []]
43
+ end
44
+
45
+ def default_headers
46
+ {
47
+ 'Content-Type' => 'text/plain',
48
+ 'Content-Length' => '0'
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -3,27 +3,79 @@
3
3
  module Navigable
4
4
  module Server
5
5
  module Endpoint
6
- EXECUTE_NOT_IMPLEMENTED_MESSAGE = 'Class must implement `execute` method.'
6
+ EXECUTE_NOT_IMPLEMENTED_MESSAGE = 'Endpoint classes must either call `executes` or implement an `execute` method.'
7
+ UNAUTHENTICATED = { status: 401, text: 'Unauthorized' }.freeze
8
+ UNAUTHORIZED = { status: 403, text: 'Forbidden' }.freeze
7
9
 
8
10
  def self.extended(base)
9
- base.instance_eval do
10
- def responds_to(verb, path)
11
- Navigable::Server.add_endpoint(verb: verb, path: path, endpoint_class: self)
12
- end
11
+ base.extend(ClassMethods)
12
+ base.include(InstanceMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ def responds_to(verb, path)
17
+ Navigable::Server.add_endpoint(verb: verb, path: path, endpoint_class: self)
18
+ end
19
+
20
+ def executes(command_key)
21
+ @command_key = command_key
22
+ end
23
+ end
24
+
25
+ module InstanceMethods
26
+ attr_reader :request
27
+
28
+ def inject(request: Request.new)
29
+ @request = request
13
30
  end
14
31
 
15
- base.class_eval do
16
- attr_reader :request
32
+ def execute
33
+ raise NotImplementedError.new(EXECUTE_NOT_IMPLEMENTED_MESSAGE) unless command_key
34
+
35
+ return unauthenticated unless authenticated?
36
+ return unauthorized unless authorized?
37
+
38
+ dispatch
39
+ end
40
+
41
+ private
42
+
43
+ def dispatch
44
+ Navigable::Dispatcher.dispatch(command_key, params: params, resolver: resolver)
45
+ end
17
46
 
18
- def inject(request: Request.new)
19
- @request = request
20
- end
47
+ def command_key
48
+ self.class.instance_variable_get(:@command_key)
49
+ end
50
+
51
+ def params
52
+ request.params
53
+ end
54
+
55
+ def preferred_media_type
56
+ request.headers[:preferred_media_type]
57
+ end
58
+
59
+ def resolver
60
+ Manufacturable.build_one(Resolver::TYPE, preferred_media_type) || Navigable::NullResolver.new
61
+ end
62
+
63
+ def unauthenticated
64
+ UNAUTHENTICATED
65
+ end
66
+
67
+ def unauthorized
68
+ UNAUTHORIZED
69
+ end
70
+
71
+ def authenticated?
72
+ true
73
+ end
21
74
 
22
- def execute
23
- raise NotImplementedError.new(EXECUTE_NOT_IMPLEMENTED_MESSAGE)
24
- end
75
+ def authorized?
76
+ true
25
77
  end
26
78
  end
27
79
  end
28
80
  end
29
- end
81
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Navigable
4
+ module Server
5
+ module EndpointCommand
6
+ def self.extended(base)
7
+ base.extend(Navigable::Command)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ def responds_to(verb, path)
13
+ @responds_to_proc ||= Proc.new do |endpoint_klass|
14
+ endpoint_klass.responds_to(verb, path)
15
+ end
16
+
17
+ setup_endpoint_command if ready_for_setup?
18
+ end
19
+
20
+ def corresponds_to(command_key)
21
+ @corresponds_to_proc ||= Proc.new do |endpoint_klass, command_klass|
22
+ endpoint_klass.executes(command_key)
23
+ super(command_key)
24
+ end
25
+
26
+ setup_endpoint_command if ready_for_setup?
27
+ end
28
+
29
+ def setup_endpoint_command
30
+ endpoint_klass = Class.new { extend Navigable::Server::Endpoint }
31
+ @responds_to_proc.call(endpoint_klass)
32
+ @corresponds_to_proc.call(endpoint_klass, self)
33
+ end
34
+
35
+ def ready_for_setup?
36
+ @responds_to_proc && @corresponds_to_proc
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Navigable
4
+ module Server
5
+ module Parsers
6
+ class Factory
7
+ def self.build(media_type:)
8
+ case media_type
9
+ when /application\/json/
10
+ Parsers::JSON
11
+ else
12
+ Parsers::Null
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Navigable
4
+ module Server
5
+ module Parsers
6
+ class JSON
7
+ def self.parse(text)
8
+ text ? ::JSON.parse(text) : {}
9
+ rescue ::JSON::ParserError
10
+ {}
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Navigable
4
+ module Server
5
+ module Parsers
6
+ class Null
7
+ def self.parse(*args); end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -38,7 +38,19 @@ module Navigable
38
38
  end
39
39
 
40
40
  def body_params
41
- symbolize_keys(env[PARSED_BODY] || {})
41
+ symbolize_keys(parsed_body || {})
42
+ end
43
+
44
+ def parsed_body
45
+ Parsers::Factory.build(media_type: media_type).parse(body)
46
+ end
47
+
48
+ def body
49
+ @body ||= rack_request.body.read.tap { rack_request.body.rewind }
50
+ end
51
+
52
+ def media_type
53
+ rack_request.media_type
42
54
  end
43
55
 
44
56
  def url_params
@@ -54,4 +66,4 @@ module Navigable
54
66
  end
55
67
  end
56
68
  end
57
- end
69
+ end
@@ -38,7 +38,7 @@ module Navigable
38
38
  end
39
39
 
40
40
  def content_length
41
- content.bytesize
41
+ content.bytesize.to_s
42
42
  end
43
43
 
44
44
  def content
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Navigable
4
4
  module Server
5
- VERSION = "0.4.0"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
@@ -23,13 +23,12 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_dependency 'navigable-router'
27
26
  spec.add_dependency 'json', '~> 2.3'
28
- spec.add_dependency 'navigable', '~> 1.0'
27
+ spec.add_dependency 'navigable', '~> 1.5'
28
+ spec.add_dependency 'navigable-router', '~>0.2'
29
29
  spec.add_dependency 'rack', '~> 2.2'
30
30
  spec.add_dependency 'rack-abstract-format', '~> 0.9.9'
31
31
  spec.add_dependency 'rack-accept-media-types', '~> 0.9'
32
- spec.add_dependency 'rack-bodyparser', '~> 1.0'
33
32
 
34
33
  spec.add_development_dependency "bundler", "~> 2.0"
35
34
  spec.add_development_dependency "rake", "~> 12.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: navigable-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alan Ridlehoover
@@ -9,50 +9,50 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-09-27 00:00:00.000000000 Z
12
+ date: 2021-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: navigable-router
15
+ name: json
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ">="
18
+ - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
20
+ version: '2.3'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - ">="
25
+ - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
27
+ version: '2.3'
28
28
  - !ruby/object:Gem::Dependency
29
- name: json
29
+ name: navigable
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '2.3'
34
+ version: '1.5'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '2.3'
41
+ version: '1.5'
42
42
  - !ruby/object:Gem::Dependency
43
- name: navigable
43
+ name: navigable-router
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '1.0'
48
+ version: '0.2'
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '1.0'
55
+ version: '0.2'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: rack
58
58
  requirement: !ruby/object:Gem::Requirement
@@ -95,20 +95,6 @@ dependencies:
95
95
  - - "~>"
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0.9'
98
- - !ruby/object:Gem::Dependency
99
- name: rack-bodyparser
100
- requirement: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - "~>"
103
- - !ruby/object:Gem::Version
104
- version: '1.0'
105
- type: :runtime
106
- prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- requirements:
109
- - - "~>"
110
- - !ruby/object:Gem::Version
111
- version: '1.0'
112
98
  - !ruby/object:Gem::Dependency
113
99
  name: bundler
114
100
  requirement: !ruby/object:Gem::Requirement
@@ -197,7 +183,12 @@ files:
197
183
  - bin/console
198
184
  - bin/setup
199
185
  - lib/navigable/server.rb
186
+ - lib/navigable/server/cors.rb
200
187
  - lib/navigable/server/endpoint.rb
188
+ - lib/navigable/server/endpoint_command.rb
189
+ - lib/navigable/server/parsers/factory.rb
190
+ - lib/navigable/server/parsers/json.rb
191
+ - lib/navigable/server/parsers/null.rb
201
192
  - lib/navigable/server/rack_adapter.rb
202
193
  - lib/navigable/server/request.rb
203
194
  - lib/navigable/server/response.rb