hollow 0.1.0 → 0.1.1

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
  SHA1:
3
- metadata.gz: 9c801b879198a619db7318ae1e087400770a028c
4
- data.tar.gz: fc4610a928d38fb161f944cbb5fec161450b6c16
3
+ metadata.gz: 913d652bacfc36cf0de16de872e48834aff704b1
4
+ data.tar.gz: 779d456eafe4c34533a0435f97ea976b7167bcb7
5
5
  SHA512:
6
- metadata.gz: a9962e37390da57a4b0e46b773ab572d2c4cdfc1f416f7fc7e391cac7e0f6eaf740c23878664f8fd77558ddb99d0cb0999f4160083abc9c2300f598ca0429a5c
7
- data.tar.gz: 50be0715f27254029af1a380a71176a2b06fd521f3c7ebe7c2b77d6d4ff358235301c86efbe5e8ffef250c52f29a95409fabf9ac0cc3f25c61065b378e083eca
6
+ metadata.gz: 4cedfa66c2db9f2c03e68efa1021c90d7d24f836d65e9b6183b6f866160ccb147b5c0cd67034c4a4c43d06eb0f1b26e939d31d2f14dcf04a7aa56980c9809f55
7
+ data.tar.gz: 241ef0f0b0446aa28dd21266d9f42b9fd3da6324293c1346bb70da5f6f5ad65a3690b640b28ded8594348afcabcce36bce81584f1e06d6c47f8f569ab28bd97a
data/README.md CHANGED
@@ -1,38 +1,49 @@
1
- Hollow is a simple REST server skeleton built atop Sinatra. It provides a dynamic routing configuration and simple a resource scheme for creating API servers that respond to requests of the form `HTTP_METHOD www.site.com/resource/?id`, where `resource` is an API resource capable of responding to requests and `id` is an optional parameter that uniquely identifies resource records.
2
-
3
- An application built on Hollow primarily relies on a collection of classes in the `/resources` folder (the location and name of which is arbitrary and configurable), each of which implement a *resource interface* that registers them within the application as being publicly available to service requests. Resources define methods named after HTTP method types, such as :get and :post, which are invoked by the application to respond to corresponding requests. For instance, a request to `GET /MyResource/5` triggers the application to invoke the :get method of the MyResource class. However, Hollow applications will discriminate when doing so, rejecting requests that name non-existent resources or name classes that do not extend the `ResourceInterface` module. If a requested resource is valid, Hollow applications will only invoke the HTTP-named methods of the resource, and then only if they are public, so that you may attach server-private functionality to your resource files. As implementors of the `ResourceInterface` contract, all resource classes come with some basic functionality provided by the module. Until you override the default functionality of a HTTP-named method of a resource, it will respond to requests with the following message:
4
-
5
- { "error": "The resource you requested can not respond to this request (see OPTIONS)" }
6
-
7
- Do note that this message encourages your user to send an OPTIONS request to the resource to learn how to use the API, so make sure you override the :options method. Incidentally, it... also generates that message.
8
-
9
- To launch your application, run `launch.rb`.
10
-
11
- Config.yml
12
- ----------
13
- This is the configuration file for your server. Hollow allows you to configure which HTTP methods your resources are allowed to respond to by adjusting the items in the development.resource_methods array. If a method is not listed in this array, requests utilizing this method will garner a response prompting the user to send an OPTIONS request. The location of resources and other necessary system files may be added to the development.autorequire.directories array to further configure your system. Note that *any* folder may act as the "resources" folder, or many folders can act in this capacity. Thus, this is your means of deciding where your resources are (which are indicated to the system by extending the `ResourceInterface`, see below). The development environment configuration is the default from which test and production inherit, so changes made here will cascade to the other environments, as well. The provided config.yml has an overriding setup for the test environment, allowing different files to be included in the system for testing purposes.
14
-
15
- # Config.yml snippet showing the default development settings
16
- development: &common_settings
17
- autorequire:
18
- directories: [/resource]
19
- resource_methods: [get, post, put, patch, delete, options]
20
-
21
- ResourceInterface
22
- -----------------
23
- This module is the interface which all resources in your system must implement. It provides no functionality other than defaulting every Resource::HTTP_METHOD call to raise a ResourceMethodInvalidEception, which is caught by Hollow and returned to the user as the informative message we say earlier. Any class which implements ResourceInterface via extending the module is considered a resource by Hollow and will have its public HTTP-named methods made available to service requests. Private and protected methods will not be available, and nor will any methods whose names do not match HTTP request methods (as configured in Config.yml). Any class which does not implement ResourceInterface will not be publicly accessible. Note that it is the act of extending `ResourceInterface` which makes a class a resource, *not* the virtue of being in a particular folder.
24
-
25
- Hollow Exceptions
26
- ------------------
27
- Hollow comes with three custom StdError classes: `ApplicationException`, `ResourceInvalidException`, and `ResourceMethodInvalidException`. The former is raised for generic user-error related issues, such as bad data payloads with invalid or missing parameters. There are meant for you to raise to send informative messages to the user, such as 'Missing ID' or 'A record with id 10 was not found', since Hollow will rescue all such exceptions and package their message in a standardized error payload. ResourceInvalidExceptions and ResourceMethodInvalidExceptions are raised and rescued by Hollow in order to standardize API messages reporting that a requested resource was not found or that it can not service a given request (because your Resource does not override the ResourceInterface for the given HTTP name).
28
-
29
- Examples
30
- --------
31
- The `test/resource` folder contains two classes, Access and NoAccess. The former is a valid resource in the application and can be accessed via API calls to `GET /Access/id?`, where id may be any value or none at all. The static method Access::get will be invoked and the result parsed by Hollow into JSON, which will respond to the request. NoAccess does NOT implement ResourceInterface, and so even though it is in the resources folder and included in the system, it will not be available under `METHOD /NoAccess/id?' for any METHOD or any id.
32
-
33
- Gotchas
34
- -------
35
- - Resource names are case-sensitive in the API. A request to `/MyResource/` is different from a request to `/myResource/`.
36
- - Resources should return data as it should appear within the `data` structure of the response payload. Hollow will standardize the output of your resources so you don't need to do so yourself.
37
- - Error messages should always be sent to the user via ApplicationException.new, not by returning a string within a resource. Hollow will rescue these exceptions and package their messages into the `error` structure of the response payload.
38
- - Internal server errors are reported via payloads with `internal_error` keys. The message sent back is entirely banal, but if your UI only listens for `data` and `error` but not `internal_error`, it may miss these responses and silently fail.
1
+ # Hollow
2
+
3
+ > Hollow is a drop-in component for building RESTful services that bridges from any routing solution (like Sinatra) to your back-end. You flesh out your service with Resource classes, you pick a Router to forward some traffic to Hollow, and it works. GET /HelloWorld becomes HelloWorld.get().
4
+
5
+ With Hollow, any class which includes `Hollow::Resource::Stateless` or `Stateful` is a REST resource capable of servicing certain kinds of requests. Let's say we want to respond to POST requests with a greeting like, "Hello, Tomboyo!":
6
+
7
+ ```ruby
8
+ class HelloWorld
9
+ include Hollow::Resource::Stateless
10
+ def post(request)
11
+ if (request['name'] && !request['name'].empty?)
12
+ "Hello, #{request['name']}!"
13
+ else
14
+ "Hello, whoever you are!"
15
+ end
16
+ end
17
+ end
18
+ ```
19
+
20
+ We save that to `./resources/HelloWorld.rb`. Now we want to expose it to the world, so we make a `./server.rb` script. Using Hollow, we first create an `Application` object:
21
+
22
+ ```ruby
23
+ my_app = Hollow::Application.new(
24
+ autorequire: {
25
+ root: "#{File.dirname __FILE__}",
26
+ directories: ['resources']
27
+ },
28
+ resource_methods: %i(post)
29
+ )
30
+ ```
31
+ The application instance is configured to look for resources in `./resources`, which is where `HelloWorld` is defined. We've also decided that we only want to handle POST requests for now. Finally, we simply pipe HTTP traffic to the application’s `handle_request` method. Using Sinatra:
32
+
33
+ ```ruby
34
+ get '/:resource' do |resource|
35
+ my_app.handle_request(
36
+ resource: resource,
37
+ method: request.request_method,
38
+ data: request.params
39
+ )
40
+ ```
41
+ Start the server and `curl 127.0.0.1:4567/HelloWorld -d "name=Tomboyo"` to be given an enthusiastic greeting (that is, send a post request with your name). If we want to create any more functionality, we just create new classes where Hollow can find them. That's it!
42
+
43
+ # What about other classes and methods?
44
+
45
+ Only classes, and only classes which include `Hollow::Resource::Stateless` or `Hollow::Resource::Stateful` can service requests. Only resource methods matching the symbols configured via the `resource_methods: %i(post)` parameter during application instantiation can be invoked, as well; other methods are hidden. That means if you only make a `HelloWorld` resource and your application only has `post` configured, your application only invokes `HelloWorld`'s `post`. Nothing else, ever.
46
+
47
+ # Is this only for REST?
48
+
49
+ Hollow was designed for REST, but it's not limited in that respect. `Application` can be configured with nonstandard request methods (making resource.myRequestMethodHere a legal request handler), so really `Application` is a mapping from binary identifiers (HelloWorld, post) to methods (HelloWorld post(resource)).
@@ -1,17 +1,11 @@
1
+ require_relative "hollow/hollow_exception"
1
2
  require_relative "hollow/application"
2
- require_relative "hollow/exceptions"
3
3
  require_relative "hollow/resource"
4
4
  require_relative "hollow/version"
5
5
  require_relative "hollow/resource/chains"
6
6
  require_relative "hollow/resource/stateful"
7
7
  require_relative "hollow/resource/stateless"
8
8
 
9
+ # Root namespace for Hollow
9
10
  module Hollow
10
- DEFAULT_SETTINGS = {
11
- autorequire: {
12
- root: "#{File.dirname __FILE__}/../..",
13
- directories: []
14
- },
15
- resource_methods: ["get", "post", "put", "patch", "delete", "options"]
16
- }
17
11
  end
@@ -2,26 +2,111 @@ require 'require_all'
2
2
 
3
3
  module Hollow
4
4
 
5
+ # A RESTful service provider framework which provides dynamic access to REST
6
+ # resource objects, allowing routers to service reqeusts without any knowledge
7
+ # of the types that exist in business code.
8
+ #
9
+ # - Resource classes compose the service provider aspect of Hollow and
10
+ # encapsulate all or part of a system's business logic.
11
+ # - Any class can be registered as a resource by including one of the
12
+ # Stateless[Resource/Stateless] or Stateful[Resource/Stateful] modules.
13
+ # - The service provider API is defined dynamically by application instance
14
+ # configuration (see settings[Application#settings-instance_method]).
15
+ # Different application instances can use the same underlying resources, but
16
+ # they will filter access to those resources differently. Requests through one
17
+ # application instance may succeed while requests through another may raise
18
+ # access exceptions.
19
+ # - Application instances provide service access to resources via the
20
+ # handle_request[#handle_request] method.
5
21
  class Application
6
22
 
23
+ # Indicates an illegal resource name.
24
+ class ResourceException < Hollow::HollowException
25
+ # The illegal resource name.
26
+ attr_reader :resource_name
27
+
28
+ private
29
+ def initialize(resource_name)
30
+ super("The resource #{resource_name} does not exist.")
31
+ @resource_name = resource_name
32
+ end
33
+ end
34
+
35
+ # Indicates an illegal resource method name.
36
+ class ResourceMethodException < Hollow::HollowException
37
+ # The legal resource name provided for the request.
38
+ attr_reader :resource_name
39
+
40
+ # The illegal method name.
41
+ attr_reader :method_name
42
+
43
+ private
44
+ def initialize(resource_name, method_name)
45
+ super("The %s resource does not respond to %s requests" % [
46
+ resource_name,
47
+ method_name
48
+ ])
49
+
50
+ @resource_name = resource_name
51
+ @method_name = method_name
52
+ end
53
+ end
54
+
55
+ # Default application settings.
56
+ DEFAULT_SETTINGS = {
57
+ autorequire: {
58
+ root: "#{File.dirname __FILE__}/../..",
59
+ directories: []
60
+ },
61
+ resource_methods: ["get", "post", "put", "patch", "delete", "options"]
62
+ }
63
+
64
+ # @return [Hash] the application settings
7
65
  attr_reader :settings
8
66
 
67
+ # Create a new application instance.
68
+ # @param [Hash] settings application settings. See {DEFAULT_SETTINGS} for
69
+ # defaults.
70
+ # @option settings [Array<Symbol>, Array<String>] :resource_methods
71
+ # Resource method names which may be invoked on resource instances by
72
+ # this application. This effectively defines the service provider API.
73
+ # @option settings [String] :autorequire[:root]
74
+ # location in the file system relative to which other filesystem locations
75
+ # are evaluated.
76
+ # @option settings [Array<String>] :autorequire[:directories]
77
+ # file system locations where resource classes may be found; all classes
78
+ # in these directories will be required immediately. These locations are
79
+ # relative to `:autorequire[:root]`.
9
80
  def initialize(settings = {})
10
- @settings = Hollow::DEFAULT_SETTINGS.merge(settings)
81
+ @settings = DEFAULT_SETTINGS.merge(settings)
11
82
  @settings[:resource_methods].map! { |m| m.to_sym }
12
83
  @settings[:autorequire][:directories].each do |dir|
13
84
  require_all "#{@settings[:autorequire][:root]}/#{dir}"
14
85
  end
15
86
  end
16
87
 
88
+ # Attempt to invoke a resource method with the given data.
89
+ #
90
+ # @param [String, Symbol] resource
91
+ # The case-sensitive name of a desired resource.
92
+ # @param [String, Symbol] method
93
+ # The case-sensitive resource method name to invoke.
94
+ # @param [Hash] data
95
+ # Any data which the resource may or may not use to handle the reqeust.
96
+ # @raise [ResourceException]
97
+ # If the indicated resource is not defined, is not a Class, is
98
+ # {Hollow::Resource} itself, or is not a type of {Hollow::Resource}.
99
+ # @raise [ResourceMethodException]
100
+ # If the indicated resource exists and:
101
+ # 1. The indicated method is not accessible or defined, or
102
+ # 2. The method name is not included in `settings[:resource_methods]`.
17
103
  def handle_request(resource: nil, method: nil, data: {})
18
104
  begin
19
105
  resource_class = Application::get_resource(resource.to_sym)
20
106
  handler = resource_class.get_instance
21
107
  method = method.to_sym.downcase
22
108
  rescue NoMethodError, NameError
23
- fail Hollow::ResourceException,
24
- "The resource #{resource} does not exist."
109
+ fail ResourceException.new resource
25
110
  end
26
111
 
27
112
  if @settings[:resource_methods].include?(method) &&
@@ -31,9 +116,7 @@ module Hollow
31
116
  invoke_chain(resource_class, data, :after, method)
32
117
  return response
33
118
  else
34
- fail Hollow::ResourceMethodException,
35
- "The %s resource does not respond to %s requests" % [ resource,
36
- method ]
119
+ fail ResourceMethodException.new resource, method
37
120
  end
38
121
  end
39
122
 
@@ -47,8 +130,7 @@ module Hollow
47
130
  it.is_a?(Hollow::Resource)
48
131
  return it
49
132
  else
50
- fail Hollow::ResourceException,
51
- "The requested resource (\"#{resource}\") does not exist."
133
+ fail ResourceException.new resource
52
134
  end
53
135
  end
54
136
  end
@@ -0,0 +1,7 @@
1
+ module Hollow
2
+
3
+ # Indicate any Hollow-specific exception which has no dedicated sublass of
4
+ # HollowException.
5
+ class HollowException < StandardError ; end
6
+
7
+ end
@@ -1,8 +1,12 @@
1
1
  module Hollow
2
2
 
3
- # A mixin that marks a class as a Resource.
3
+ # A mixin that marks a class as a resource. Only instances of Resource can be
4
+ # accessed by {Hollow::Application#handle_request}. This module should not be
5
+ # used directly; prefer {Hollow::Resource::Stateless} or
6
+ # {Hollow::Resource::Stateful} instead.
4
7
  module Resource
5
8
 
9
+ private
6
10
  def self.extended(base)
7
11
  base.class_variable_set(:@@chains, {
8
12
  before: {},
@@ -1,13 +1,29 @@
1
1
  module Hollow
2
2
  module Resource
3
3
 
4
+ # Indicates that a {Hollow::Resource} class uses chains to perform logic
5
+ # before or after each request.
4
6
  module Chains
5
- def self.included(base)
6
7
 
7
- def base.chain(chain, method, behavior)
8
- (self.class_variable_get(:@@chains)[chain][method] ||= []) <<
9
- behavior
10
- end
8
+ # Register a function to invoke before or after a given resource method
9
+ # (or any resource method) is invoked.
10
+ #
11
+ # Behaviors are invoked in the order in which they are defined within
12
+ # their respective chains, the exception being that behaviors associated
13
+ # with `:all` methods are invoked before more specific methods.
14
+ #
15
+ # @param [Symbol] chain
16
+ # Which chain to register the designated behavior with. Must be one of
17
+ # either `:before` or `:after`.
18
+ # @param [Symbol] method
19
+ # Which request method triggers the behavior. If `:all` is provded,
20
+ # any request method will invoke the behavior.
21
+ # @param [Proc, Lambda] behavior
22
+ # Behavior which accepts one argument (the data accompanying the
23
+ # current request).
24
+ def chain(chain, method, behavior)
25
+ (self.class_variable_get(:@@chains)[chain][method] ||= []) <<
26
+ behavior
11
27
  end
12
28
  end
13
29
 
@@ -1,8 +1,13 @@
1
1
  module Hollow
2
2
  module Resource
3
3
 
4
- # A resource which handles each request with a new instance
4
+ # Marks a class as a {Hollow::Resource} that may be used by
5
+ # {Hollow::Application} instances to handle requests. Each time
6
+ # {Hollow::Application#handle_request} delegates to a Stateful resource, a
7
+ # new instance of the resource is created to service the request.
8
+ # @see Hollow::Resource::Stateless
5
9
  module Stateful
10
+ private
6
11
  def self.included(base)
7
12
  unless base.is_a?(Hollow::Resource)
8
13
  base.extend(Hollow::Resource)
@@ -1,8 +1,13 @@
1
1
  module Hollow
2
2
  module Resource
3
3
 
4
- # A resource that handles all requests with the same instance
4
+ # Marks a class as a {Hollow::Resource} that may be used by
5
+ # {Hollow::Application} instances to handle requests. Including this module
6
+ # creates a singleton instance of the class which will service all requests
7
+ # from all Application instances.
8
+ # @see Hollow::Resource::Stateful
5
9
  module Stateless
10
+ private
6
11
  def self.included(base)
7
12
  unless base.is_a?(Hollow::Resource)
8
13
  base.extend(Hollow::Resource)
@@ -1,3 +1,4 @@
1
1
  module Hollow
2
- VERSION = "0.1.0"
2
+ # Hollow version number
3
+ VERSION = "0.1.1"
3
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hollow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Simmons
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-26 00:00:00.000000000 Z
11
+ date: 2017-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: require_all
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  description: "\n Hollow is a drop-in component for building RESTful services that
84
98
  bridges\n from any routing solution (like Sinatra) to your back-end. You flesh
85
99
  out\n your service with Resource classes, you pick a Router to forward some\n
@@ -95,21 +109,18 @@ files:
95
109
  - README.md
96
110
  - lib/hollow.rb
97
111
  - lib/hollow/application.rb
98
- - lib/hollow/exceptions.rb
112
+ - lib/hollow/hollow_exception.rb
99
113
  - lib/hollow/resource.rb
100
114
  - lib/hollow/resource/chains.rb
101
- - lib/hollow/resource/chains/chains.rb
102
- - lib/hollow/resource/resource.rb
103
115
  - lib/hollow/resource/stateful.rb
104
- - lib/hollow/resource/stateful/stateful.rb
105
116
  - lib/hollow/resource/stateless.rb
106
- - lib/hollow/resource/stateless/stateless.rb
107
117
  - lib/hollow/version.rb
108
118
  homepage: https://www.github.com/tomboyo/hollow
109
119
  licenses:
110
120
  - MIT
111
121
  metadata:
112
122
  allowed_push_host: https://rubygems.org
123
+ yard.run: yard
113
124
  post_install_message:
114
125
  rdoc_options: []
115
126
  require_paths:
@@ -1,7 +0,0 @@
1
- module Hollow
2
-
3
- class HollowException < StandardError ; end
4
- class ResourceException < HollowException ; end
5
- class ResourceMethodException < ResourceException ; end
6
-
7
- end
@@ -1,15 +0,0 @@
1
- module Hollow
2
- module Resource
3
-
4
- module Chains
5
- def self.included(base)
6
-
7
- def base.chain(chain, method, behavior)
8
- (self.class_variable_get(:@@chains)[chain][method] ||= []) <<
9
- behavior
10
- end
11
- end
12
- end
13
-
14
- end
15
- end
@@ -1,14 +0,0 @@
1
- module Hollow
2
-
3
- # A mixin that marks a class as a Resource.
4
- module Resource
5
-
6
- def self.extended(base)
7
- base.class_variable_set(:@@chains, {
8
- before: {},
9
- after: {}
10
- })
11
- end
12
-
13
- end
14
- end
@@ -1,18 +0,0 @@
1
- module Hollow
2
- module Resource
3
-
4
- # A resource which handles each request with a new instance
5
- module Stateful
6
- def self.included(base)
7
- unless base.is_a?(Hollow::Resource)
8
- base.extend(Hollow::Resource)
9
- end
10
-
11
- def base.get_instance
12
- self.new
13
- end
14
- end
15
- end
16
-
17
- end
18
- end
@@ -1,19 +0,0 @@
1
- module Hollow
2
- module Resource
3
-
4
- # A resource that handles all requests with the same instance
5
- module Stateless
6
- def self.included(base)
7
- unless base.is_a?(Hollow::Resource)
8
- base.extend(Hollow::Resource)
9
- end
10
-
11
- base.class_variable_set(:@@instance, base.new)
12
- def base.get_instance
13
- self.class_variable_get(:@@instance)
14
- end
15
- end
16
- end
17
-
18
- end
19
- end