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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +36 -0
  3. data/CONTRIBUTING.md +18 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +37 -0
  6. data/README.md +607 -0
  7. data/circle.yml +14 -0
  8. data/endpoint_flux.gemspec +18 -0
  9. data/lib/endpoint_flux.rb +20 -0
  10. data/lib/endpoint_flux/class_loader.rb +58 -0
  11. data/lib/endpoint_flux/config.rb +85 -0
  12. data/lib/endpoint_flux/config/interceptor.rb +23 -0
  13. data/lib/endpoint_flux/config/middleware.rb +38 -0
  14. data/lib/endpoint_flux/config/rescue_from.rb +28 -0
  15. data/lib/endpoint_flux/endpoint.rb +81 -0
  16. data/lib/endpoint_flux/exceptions.rb +10 -0
  17. data/lib/endpoint_flux/exceptions/base.rb +21 -0
  18. data/lib/endpoint_flux/exceptions/forbidden.rb +12 -0
  19. data/lib/endpoint_flux/exceptions/not_found.rb +12 -0
  20. data/lib/endpoint_flux/exceptions/service_unavailable.rb +12 -0
  21. data/lib/endpoint_flux/exceptions/unauthorized.rb +12 -0
  22. data/lib/endpoint_flux/exceptions/validation.rb +13 -0
  23. data/lib/endpoint_flux/middlewares.rb +8 -0
  24. data/lib/endpoint_flux/middlewares/authenticator/skip.rb +11 -0
  25. data/lib/endpoint_flux/middlewares/authorizator/skip.rb +11 -0
  26. data/lib/endpoint_flux/middlewares/decorator/add_status.rb +12 -0
  27. data/lib/endpoint_flux/middlewares/decorator/skip.rb +11 -0
  28. data/lib/endpoint_flux/middlewares/policy/skip.rb +11 -0
  29. data/lib/endpoint_flux/middlewares/validator/empty.rb +12 -0
  30. data/lib/endpoint_flux/rails/concerns/endpoint_controller.rb +43 -0
  31. data/lib/endpoint_flux/request.rb +17 -0
  32. data/lib/endpoint_flux/response.rb +30 -0
  33. data/lib/endpoint_flux/version.rb +3 -0
  34. data/spec/lib/class_loader_spec.rb +31 -0
  35. data/spec/lib/config/default_middlewares_spec.rb +21 -0
  36. data/spec/lib/config/endpoints_namespace_spec.rb +13 -0
  37. data/spec/lib/config/flow_spec.rb +8 -0
  38. data/spec/lib/config/interceptor_spec.rb +34 -0
  39. data/spec/lib/config/middleware_spec.rb +62 -0
  40. data/spec/lib/config/rescue_from_spec.rb +45 -0
  41. data/spec/lib/endpoint/flow_spec.rb +43 -0
  42. data/spec/lib/endpoint/middlewares_spec.rb +110 -0
  43. data/spec/lib/endpoint/perform_spec.rb +61 -0
  44. data/spec/lib/endpoint/rescue_from_spec.rb +61 -0
  45. data/spec/lib/endpoint_flux/rails/concerns/endpoint_controller_spec.rb +75 -0
  46. data/spec/lib/endpoint_flux/request_spec.rb +44 -0
  47. data/spec/lib/exceptions/forbidden_spec.rb +12 -0
  48. data/spec/lib/exceptions/not_found_spec.rb +12 -0
  49. data/spec/lib/exceptions/service_unavailable_spec.rb +12 -0
  50. data/spec/lib/exceptions/unauthorized_spec.rb +12 -0
  51. data/spec/lib/exceptions/validation_spec.rb +14 -0
  52. data/spec/lib/middlewares/authenticator/skip_spec.rb +5 -0
  53. data/spec/lib/middlewares/authorizator/skip_spec.rb +5 -0
  54. data/spec/lib/middlewares/decorator/add_status_spec.rb +17 -0
  55. data/spec/lib/middlewares/decorator/skip_spec.rb +5 -0
  56. data/spec/lib/middlewares/policy/skip_spec.rb +5 -0
  57. data/spec/lib/middlewares/shared_examples.rb +19 -0
  58. data/spec/lib/middlewares/validator/empty_spec.rb +15 -0
  59. data/spec/lib/response_spec.rb +131 -0
  60. data/spec/spec_helper.rb +52 -0
  61. metadata +157 -0
@@ -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
@@ -0,0 +1,12 @@
1
+ module EndpointFlux
2
+ module Exceptions
3
+ class Forbidden < EndpointFlux::Exceptions::Base
4
+ def to_hash
5
+ {
6
+ status: 403,
7
+ message: 'Forbidden'
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module EndpointFlux
2
+ module Exceptions
3
+ class NotFound < EndpointFlux::Exceptions::Base
4
+ def to_hash
5
+ {
6
+ status: 404,
7
+ message: 'not found'
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module EndpointFlux
2
+ module Exceptions
3
+ class ServiceUnavailable < EndpointFlux::Exceptions::Base
4
+ def to_hash
5
+ {
6
+ status: 503,
7
+ message: 'service unavailable'
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end