hollow 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9c801b879198a619db7318ae1e087400770a028c
4
+ data.tar.gz: fc4610a928d38fb161f944cbb5fec161450b6c16
5
+ SHA512:
6
+ metadata.gz: a9962e37390da57a4b0e46b773ab572d2c4cdfc1f416f7fc7e391cac7e0f6eaf740c23878664f8fd77558ddb99d0cb0999f4160083abc9c2300f598ca0429a5c
7
+ data.tar.gz: 50be0715f27254029af1a380a71176a2b06fd521f3c7ebe7c2b77d6d4ff358235301c86efbe5e8ffef250c52f29a95409fabf9ac0cc3f25c61065b378e083eca
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Tom Simmons
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,38 @@
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.
@@ -0,0 +1,17 @@
1
+ require_relative "hollow/application"
2
+ require_relative "hollow/exceptions"
3
+ require_relative "hollow/resource"
4
+ require_relative "hollow/version"
5
+ require_relative "hollow/resource/chains"
6
+ require_relative "hollow/resource/stateful"
7
+ require_relative "hollow/resource/stateless"
8
+
9
+ 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
+ end
@@ -0,0 +1,63 @@
1
+ require 'require_all'
2
+
3
+ module Hollow
4
+
5
+ class Application
6
+
7
+ attr_reader :settings
8
+
9
+ def initialize(settings = {})
10
+ @settings = Hollow::DEFAULT_SETTINGS.merge(settings)
11
+ @settings[:resource_methods].map! { |m| m.to_sym }
12
+ @settings[:autorequire][:directories].each do |dir|
13
+ require_all "#{@settings[:autorequire][:root]}/#{dir}"
14
+ end
15
+ end
16
+
17
+ def handle_request(resource: nil, method: nil, data: {})
18
+ begin
19
+ resource_class = Application::get_resource(resource.to_sym)
20
+ handler = resource_class.get_instance
21
+ method = method.to_sym.downcase
22
+ rescue NoMethodError, NameError
23
+ fail Hollow::ResourceException,
24
+ "The resource #{resource} does not exist."
25
+ end
26
+
27
+ if @settings[:resource_methods].include?(method) &&
28
+ handler.respond_to?(method)
29
+ invoke_chain(resource_class, data, :before, method)
30
+ response = handler.public_send(method, data)
31
+ invoke_chain(resource_class, data, :after, method)
32
+ return response
33
+ else
34
+ fail Hollow::ResourceMethodException,
35
+ "The %s resource does not respond to %s requests" % [ resource,
36
+ method ]
37
+ end
38
+ end
39
+
40
+ private
41
+ def Application::get_resource(resource)
42
+ if Module.const_defined?(resource)
43
+ it = Object.const_get(resource)
44
+
45
+ if it.is_a?(Class) &&
46
+ it.class != Hollow::Resource &&
47
+ it.is_a?(Hollow::Resource)
48
+ return it
49
+ else
50
+ fail Hollow::ResourceException,
51
+ "The requested resource (\"#{resource}\") does not exist."
52
+ end
53
+ end
54
+ end
55
+
56
+ def invoke_chain(resource, request, chain, method)
57
+ chains = resource.class_variable_get(:@@chains)
58
+ links = (chains[chain][:all] || []) + (chains[chain][method] || [])
59
+ links.each { |chain_link| chain_link.call(request) }
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,7 @@
1
+ module Hollow
2
+
3
+ class HollowException < StandardError ; end
4
+ class ResourceException < HollowException ; end
5
+ class ResourceMethodException < ResourceException ; end
6
+
7
+ end
@@ -0,0 +1,14 @@
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
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,14 @@
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
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,3 @@
1
+ module Hollow
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hollow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Simmons
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: require_all
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.7'
83
+ description: "\n Hollow is a drop-in component for building RESTful services that
84
+ bridges\n from any routing solution (like Sinatra) to your back-end. You flesh
85
+ out\n your service with Resource classes, you pick a Router to forward some\n
86
+ \ traffic to Hollow, and it works. GET /HelloWorld becomes HelloWorld.get().\n
87
+ \ "
88
+ email:
89
+ - tomasimmons@gmail.com
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - LICENSE.txt
95
+ - README.md
96
+ - lib/hollow.rb
97
+ - lib/hollow/application.rb
98
+ - lib/hollow/exceptions.rb
99
+ - lib/hollow/resource.rb
100
+ - lib/hollow/resource/chains.rb
101
+ - lib/hollow/resource/chains/chains.rb
102
+ - lib/hollow/resource/resource.rb
103
+ - lib/hollow/resource/stateful.rb
104
+ - lib/hollow/resource/stateful/stateful.rb
105
+ - lib/hollow/resource/stateless.rb
106
+ - lib/hollow/resource/stateless/stateless.rb
107
+ - lib/hollow/version.rb
108
+ homepage: https://www.github.com/tomboyo/hollow
109
+ licenses:
110
+ - MIT
111
+ metadata:
112
+ allowed_push_host: https://rubygems.org
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.6.12
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Bridge the gap from routing to Resource.
133
+ test_files: []