endpoint-flux 1.1.4
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 +7 -0
- data/.gitignore +36 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +37 -0
- data/README.md +607 -0
- data/circle.yml +14 -0
- data/endpoint_flux.gemspec +18 -0
- data/lib/endpoint_flux.rb +20 -0
- data/lib/endpoint_flux/class_loader.rb +58 -0
- data/lib/endpoint_flux/config.rb +85 -0
- data/lib/endpoint_flux/config/interceptor.rb +23 -0
- data/lib/endpoint_flux/config/middleware.rb +38 -0
- data/lib/endpoint_flux/config/rescue_from.rb +28 -0
- data/lib/endpoint_flux/endpoint.rb +81 -0
- data/lib/endpoint_flux/exceptions.rb +10 -0
- data/lib/endpoint_flux/exceptions/base.rb +21 -0
- data/lib/endpoint_flux/exceptions/forbidden.rb +12 -0
- data/lib/endpoint_flux/exceptions/not_found.rb +12 -0
- data/lib/endpoint_flux/exceptions/service_unavailable.rb +12 -0
- data/lib/endpoint_flux/exceptions/unauthorized.rb +12 -0
- data/lib/endpoint_flux/exceptions/validation.rb +13 -0
- data/lib/endpoint_flux/middlewares.rb +8 -0
- data/lib/endpoint_flux/middlewares/authenticator/skip.rb +11 -0
- data/lib/endpoint_flux/middlewares/authorizator/skip.rb +11 -0
- data/lib/endpoint_flux/middlewares/decorator/add_status.rb +12 -0
- data/lib/endpoint_flux/middlewares/decorator/skip.rb +11 -0
- data/lib/endpoint_flux/middlewares/policy/skip.rb +11 -0
- data/lib/endpoint_flux/middlewares/validator/empty.rb +12 -0
- data/lib/endpoint_flux/rails/concerns/endpoint_controller.rb +43 -0
- data/lib/endpoint_flux/request.rb +17 -0
- data/lib/endpoint_flux/response.rb +30 -0
- data/lib/endpoint_flux/version.rb +3 -0
- data/spec/lib/class_loader_spec.rb +31 -0
- data/spec/lib/config/default_middlewares_spec.rb +21 -0
- data/spec/lib/config/endpoints_namespace_spec.rb +13 -0
- data/spec/lib/config/flow_spec.rb +8 -0
- data/spec/lib/config/interceptor_spec.rb +34 -0
- data/spec/lib/config/middleware_spec.rb +62 -0
- data/spec/lib/config/rescue_from_spec.rb +45 -0
- data/spec/lib/endpoint/flow_spec.rb +43 -0
- data/spec/lib/endpoint/middlewares_spec.rb +110 -0
- data/spec/lib/endpoint/perform_spec.rb +61 -0
- data/spec/lib/endpoint/rescue_from_spec.rb +61 -0
- data/spec/lib/endpoint_flux/rails/concerns/endpoint_controller_spec.rb +75 -0
- data/spec/lib/endpoint_flux/request_spec.rb +44 -0
- data/spec/lib/exceptions/forbidden_spec.rb +12 -0
- data/spec/lib/exceptions/not_found_spec.rb +12 -0
- data/spec/lib/exceptions/service_unavailable_spec.rb +12 -0
- data/spec/lib/exceptions/unauthorized_spec.rb +12 -0
- data/spec/lib/exceptions/validation_spec.rb +14 -0
- data/spec/lib/middlewares/authenticator/skip_spec.rb +5 -0
- data/spec/lib/middlewares/authorizator/skip_spec.rb +5 -0
- data/spec/lib/middlewares/decorator/add_status_spec.rb +17 -0
- data/spec/lib/middlewares/decorator/skip_spec.rb +5 -0
- data/spec/lib/middlewares/policy/skip_spec.rb +5 -0
- data/spec/lib/middlewares/shared_examples.rb +19 -0
- data/spec/lib/middlewares/validator/empty_spec.rb +15 -0
- data/spec/lib/response_spec.rb +131 -0
- data/spec/spec_helper.rb +52 -0
- metadata +157 -0
data/circle.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
version: 2
|
2
|
+
jobs:
|
3
|
+
build:
|
4
|
+
docker:
|
5
|
+
- image: circleci/ruby:2.5.3-node
|
6
|
+
steps:
|
7
|
+
- checkout
|
8
|
+
- run: gem install bundle
|
9
|
+
- run:
|
10
|
+
name: Bundle Install
|
11
|
+
command: bundle check || bundle install
|
12
|
+
- run:
|
13
|
+
name: Run Specs
|
14
|
+
command: bundle exec rspec --format progress
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'lib/endpoint_flux/version' # frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'endpoint-flux'
|
5
|
+
s.version = EndpointFlux::VERSION
|
6
|
+
s.summary = 'EndpointFlux!'
|
7
|
+
s.description = 'A simple way to organise API endpoints'
|
8
|
+
s.authors = ['Arturs Kreipans https://github.com/fragallia']
|
9
|
+
s.files = `git ls-files`.split($\)
|
10
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
11
|
+
s.require_paths = ['lib']
|
12
|
+
s.homepage = 'https://github.com/resolving/endpoint-flux'
|
13
|
+
s.license = 'MIT'
|
14
|
+
s.required_ruby_version = '>= 2.5.3'
|
15
|
+
|
16
|
+
s.add_development_dependency 'byebug', '>= 9.0'
|
17
|
+
s.add_development_dependency 'rspec', '>= 3.5.0'
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'endpoint_flux/class_loader'
|
2
|
+
|
3
|
+
require 'endpoint_flux/config'
|
4
|
+
require 'endpoint_flux/endpoint'
|
5
|
+
require 'endpoint_flux/exceptions'
|
6
|
+
require 'endpoint_flux/middlewares'
|
7
|
+
|
8
|
+
module EndpointFlux
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def config(handler = nil)
|
12
|
+
@config = handler if handler
|
13
|
+
|
14
|
+
@config ||= EndpointFlux::Config.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear
|
18
|
+
@config = nil
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module EndpointFlux
|
2
|
+
module ClassLoader
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def load_class(klass_name)
|
6
|
+
return klass_name if klass_name.is_a?(Class)
|
7
|
+
|
8
|
+
constantize('::' + string_to_class_name(klass_name.to_s))
|
9
|
+
rescue NameError
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_class!(klass_name)
|
14
|
+
load_class(klass_name) ||
|
15
|
+
raise("The [#{klass_name}] should be a string representing a class")
|
16
|
+
end
|
17
|
+
|
18
|
+
def string_to_class_name(klass_name)
|
19
|
+
klass_name
|
20
|
+
.sub(%r{^[a-z\d]*}) { $&.capitalize }
|
21
|
+
.gsub(%r{(?:_|(\/))([a-z\d]*)}) do
|
22
|
+
"#{Regexp.last_match[1]}#{Regexp.last_match[2].capitalize}"
|
23
|
+
end
|
24
|
+
.gsub('/', '::')
|
25
|
+
end
|
26
|
+
|
27
|
+
def constantize(camel_cased_word)
|
28
|
+
names = camel_cased_word.split("::".freeze)
|
29
|
+
|
30
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
31
|
+
Object.const_get(camel_cased_word) if names.empty?
|
32
|
+
|
33
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
34
|
+
names.shift if names.size > 1 && names.first.empty?
|
35
|
+
|
36
|
+
names.inject(Object) do |constant, name|
|
37
|
+
if constant == Object
|
38
|
+
constant.const_get(name)
|
39
|
+
else
|
40
|
+
candidate = constant.const_get(name)
|
41
|
+
next candidate if constant.const_defined?(name, false)
|
42
|
+
next candidate unless Object.const_defined?(name)
|
43
|
+
|
44
|
+
# Go down the ancestors to check if it is owned directly. The check
|
45
|
+
# stops when we reach Object or the end of ancestors tree.
|
46
|
+
constant = constant.ancestors.inject(constant) do |const, ancestor|
|
47
|
+
break const if ancestor == Object
|
48
|
+
break ancestor if ancestor.const_defined?(name, false)
|
49
|
+
const
|
50
|
+
end
|
51
|
+
|
52
|
+
# owner is in Object, so raise
|
53
|
+
constant.const_get(name, false)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module EndpointFlux
|
2
|
+
class Config
|
3
|
+
require 'endpoint_flux/config/interceptor'
|
4
|
+
require 'endpoint_flux/config/middleware'
|
5
|
+
require 'endpoint_flux/config/rescue_from'
|
6
|
+
|
7
|
+
attr_accessor :middlewares_namespaces
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@flow = %i[authenticator authorizator validator policy process decorator]
|
11
|
+
@default_middlewares = {}
|
12
|
+
@rescue_from = EndpointFlux::Config::RescueFrom.new
|
13
|
+
@endpoints_namespace = 'endpoints'
|
14
|
+
@middlewares_namespaces = ['endpoint_flux/middlewares']
|
15
|
+
end
|
16
|
+
|
17
|
+
def interceptor(&block)
|
18
|
+
@interceptor ||= EndpointFlux::Config::Interceptor.new
|
19
|
+
@interceptor.add(&block) if block_given?
|
20
|
+
@interceptor
|
21
|
+
end
|
22
|
+
|
23
|
+
def flow(new_flow = nil)
|
24
|
+
@flow = new_flow if new_flow
|
25
|
+
|
26
|
+
@flow
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_middlewares(name = nil, klass_name = nil)
|
30
|
+
if name
|
31
|
+
@default_middlewares[name] ||= []
|
32
|
+
|
33
|
+
if klass_name
|
34
|
+
klass = fetch_middleware_class!(name.to_s, klass_name.to_s)
|
35
|
+
@default_middlewares[name] << EndpointFlux::Config::Middleware.new(klass)
|
36
|
+
end
|
37
|
+
|
38
|
+
return @default_middlewares[name]
|
39
|
+
end
|
40
|
+
|
41
|
+
@default_middlewares
|
42
|
+
end
|
43
|
+
|
44
|
+
def rescue_from(klass_names = nil, &block)
|
45
|
+
if klass_names
|
46
|
+
if klass_names.respond_to?(:to_ary)
|
47
|
+
klass_names.to_ary || [klass_names]
|
48
|
+
else
|
49
|
+
[klass_names]
|
50
|
+
end.each do |klass_name|
|
51
|
+
klass = EndpointFlux::ClassLoader.load_class!(klass_name)
|
52
|
+
@rescue_from.add(klass, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@rescue_from
|
57
|
+
end
|
58
|
+
|
59
|
+
def endpoints_namespace(name = nil)
|
60
|
+
@endpoints_namespace = name if name
|
61
|
+
|
62
|
+
@endpoints_namespace
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch_middleware_class!(name, klass_name)
|
66
|
+
klass = fetch_middleware_class(name, klass_name)
|
67
|
+
|
68
|
+
raise "The [#{name}][#{klass_name}] should be a string representing a class" unless klass
|
69
|
+
|
70
|
+
klass
|
71
|
+
end
|
72
|
+
|
73
|
+
def fetch_middleware_class(name, klass_name)
|
74
|
+
middlewares_namespaces.each do |namespace|
|
75
|
+
klass = EndpointFlux::ClassLoader.load_class(
|
76
|
+
[namespace, name.to_s, klass_name.to_s].compact.join('/')
|
77
|
+
)
|
78
|
+
|
79
|
+
return klass if klass
|
80
|
+
end
|
81
|
+
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EndpointFlux
|
2
|
+
class Config
|
3
|
+
class Interceptor
|
4
|
+
attr_accessor :handlers
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@handlers = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(attrs)
|
11
|
+
handlers.each { |handler| handler.call(attrs) }
|
12
|
+
|
13
|
+
attrs
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(&block)
|
17
|
+
raise 'Block not given' unless block_given?
|
18
|
+
|
19
|
+
handlers << block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module EndpointFlux
|
2
|
+
class Config
|
3
|
+
class Middleware
|
4
|
+
attr_accessor :klass, :options, :handler
|
5
|
+
|
6
|
+
def initialize(klass = nil, options = {}, &block)
|
7
|
+
if klass
|
8
|
+
unless klass.methods.include?(:perform)
|
9
|
+
raise "The [#{klass}] class should define perform class method"
|
10
|
+
end
|
11
|
+
else
|
12
|
+
raise 'You must provide block or existing klass' unless block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
@klass = klass
|
16
|
+
@options = options
|
17
|
+
@handler = block
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(attrs)
|
21
|
+
if klass
|
22
|
+
klass.perform(*attrs, options, &handler)
|
23
|
+
elsif handler
|
24
|
+
handler.call(*attrs, options)
|
25
|
+
else
|
26
|
+
raise "Unknown middleware type [#{inspect}]"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
return true if klass && klass == other.klass
|
32
|
+
return true if handler && handler == other.handler
|
33
|
+
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module EndpointFlux
|
2
|
+
class Config
|
3
|
+
class RescueFrom
|
4
|
+
attr_accessor :handlers, :exceptions
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@handlers = {}
|
8
|
+
@exceptions = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(name, attrs, e)
|
12
|
+
raise 'No handler given' unless exceptions.include?(e.class)
|
13
|
+
|
14
|
+
handlers[e.class.name].call(name, attrs, e)
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(klass, &block)
|
18
|
+
raise 'Block not given' unless block_given?
|
19
|
+
|
20
|
+
handlers[klass.to_s] = block
|
21
|
+
|
22
|
+
exceptions << klass unless exceptions.include?(klass)
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'endpoint_flux/config'
|
2
|
+
require 'endpoint_flux/request'
|
3
|
+
require 'endpoint_flux/response'
|
4
|
+
|
5
|
+
module EndpointFlux
|
6
|
+
module Endpoint
|
7
|
+
def self.included(includer)
|
8
|
+
includer.extend self
|
9
|
+
includer.class_eval %( @middlewares ||= {} )
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform(request)
|
13
|
+
attrs = [request, EndpointFlux::Response.new]
|
14
|
+
attrs = EndpointFlux.config.interceptor.run(attrs)
|
15
|
+
|
16
|
+
request.endpoint = to_s
|
17
|
+
|
18
|
+
flow.inject(attrs) do |res, middleware_name|
|
19
|
+
fetch_middleware(middleware_name).inject(res) do |middleware_res, middleware|
|
20
|
+
middleware.run(middleware_res)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
rescue *EndpointFlux.config.rescue_from.exceptions => e
|
24
|
+
EndpointFlux.config.rescue_from.run(name, attrs, e)
|
25
|
+
end
|
26
|
+
|
27
|
+
def flow(array = nil)
|
28
|
+
@flow = array.map(&:to_sym) if array
|
29
|
+
@flow || EndpointFlux.config.flow
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(method_id, *attrs, &block)
|
33
|
+
method_sym = method_id.to_sym
|
34
|
+
if flow.include?(method_sym)
|
35
|
+
configure_middleware(method_sym, attrs, &block)
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def respond_to_missing?(method_name)
|
42
|
+
flow.include?(method_name.to_sym)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def configure_middleware(middleware_name, attrs, &block)
|
48
|
+
@middlewares[middleware_name] ||= []
|
49
|
+
|
50
|
+
middleware = build_middleware(middleware_name, *attrs, &block)
|
51
|
+
@middlewares[middleware_name] << middleware if middleware
|
52
|
+
|
53
|
+
@middlewares[middleware_name]
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch_middleware(name)
|
57
|
+
@middlewares[name] ||
|
58
|
+
EndpointFlux.config.default_middlewares[name] ||
|
59
|
+
raise("No middleware registred for [#{name.inspect}]")
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_middleware(middleware_name, *options, &block)
|
63
|
+
attrs = []
|
64
|
+
unless options.empty?
|
65
|
+
klass = EndpointFlux.config.fetch_middleware_class(middleware_name, options.first)
|
66
|
+
|
67
|
+
if klass
|
68
|
+
attrs << klass
|
69
|
+
attrs << options[1..-1]
|
70
|
+
else
|
71
|
+
attrs << nil
|
72
|
+
attrs << options
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
return if attrs.empty? && !block_given?
|
77
|
+
|
78
|
+
EndpointFlux::Config::Middleware.new(*attrs.flatten, &block)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module EndpointFlux
|
2
|
+
module Exceptions
|
3
|
+
require 'endpoint_flux/exceptions/base'
|
4
|
+
require 'endpoint_flux/exceptions/forbidden'
|
5
|
+
require 'endpoint_flux/exceptions/not_found'
|
6
|
+
require 'endpoint_flux/exceptions/service_unavailable'
|
7
|
+
require 'endpoint_flux/exceptions/unauthorized'
|
8
|
+
require 'endpoint_flux/exceptions/validation'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module EndpointFlux
|
2
|
+
module Exceptions
|
3
|
+
class Base < StandardError
|
4
|
+
attr_accessor :messages
|
5
|
+
|
6
|
+
def initialize(messages = nil)
|
7
|
+
@messages = messages
|
8
|
+
|
9
|
+
super("#{self.class.name}: [ #{messages.inspect}]")
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_hash
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"#{self.class.name}: [ #{to_hash.inspect} ]"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|