endpoint-flux2 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/gem-push.yml +42 -0
  4. data/.gitignore +36 -0
  5. data/CONTRIBUTING.md +18 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +49 -0
  8. data/README.md +634 -0
  9. data/Rakefile +1 -0
  10. data/_config.yml +1 -0
  11. data/circle.yml +14 -0
  12. data/endpoint_flux.gemspec +20 -0
  13. data/lib/endpoint_flux.rb +22 -0
  14. data/lib/endpoint_flux/class_loader.rb +58 -0
  15. data/lib/endpoint_flux/config.rb +85 -0
  16. data/lib/endpoint_flux/config/interceptor.rb +23 -0
  17. data/lib/endpoint_flux/config/middleware.rb +38 -0
  18. data/lib/endpoint_flux/config/rescue_from.rb +28 -0
  19. data/lib/endpoint_flux/endpoint.rb +81 -0
  20. data/lib/endpoint_flux/exceptions.rb +10 -0
  21. data/lib/endpoint_flux/exceptions/base.rb +21 -0
  22. data/lib/endpoint_flux/exceptions/forbidden.rb +12 -0
  23. data/lib/endpoint_flux/exceptions/not_found.rb +12 -0
  24. data/lib/endpoint_flux/exceptions/service_unavailable.rb +12 -0
  25. data/lib/endpoint_flux/exceptions/unauthorized.rb +12 -0
  26. data/lib/endpoint_flux/exceptions/validation.rb +13 -0
  27. data/lib/endpoint_flux/middlewares.rb +8 -0
  28. data/lib/endpoint_flux/middlewares/authenticator/skip.rb +11 -0
  29. data/lib/endpoint_flux/middlewares/authorizator/skip.rb +11 -0
  30. data/lib/endpoint_flux/middlewares/decorator/add_status.rb +12 -0
  31. data/lib/endpoint_flux/middlewares/decorator/skip.rb +11 -0
  32. data/lib/endpoint_flux/middlewares/policy/skip.rb +11 -0
  33. data/lib/endpoint_flux/middlewares/validator/empty.rb +12 -0
  34. data/lib/endpoint_flux/rails/concerns/endpoint_controller.rb +43 -0
  35. data/lib/endpoint_flux/railtie.rb +14 -0
  36. data/lib/endpoint_flux/request.rb +17 -0
  37. data/lib/endpoint_flux/response.rb +30 -0
  38. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/decorators/articles/base.rb +12 -0
  39. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/decorators/boards/base.rb +12 -0
  40. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/decorators/boards/show.rb +22 -0
  41. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/decorators/tasks/base.rb +12 -0
  42. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/articles/create.rb +27 -0
  43. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/articles/destroy.rb +23 -0
  44. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/articles/index.rb +26 -0
  45. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/boards/create.rb +24 -0
  46. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/boards/index.rb +21 -0
  47. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/boards/show.rb +23 -0
  48. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/tasks/create.rb +27 -0
  49. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/tasks/destroy.rb +23 -0
  50. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/tasks/index.rb +25 -0
  51. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/endpoints/tasks/update.rb +28 -0
  52. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/middlewares/decorator/paginate.rb +19 -0
  53. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/middlewares/decorator/representable.rb +24 -0
  54. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/middlewares/validator/inline.rb +17 -0
  55. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/validations/base.rb +21 -0
  56. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/validations/concern/error.rb +7 -0
  57. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/validations/predicates/base.rb +12 -0
  58. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/validations/predicates/bool.rb +20 -0
  59. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/validations/predicates/dates.rb +34 -0
  60. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/validations/predicates/decimal.rb +24 -0
  61. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/validations/predicates/email.rb +18 -0
  62. data/lib/endpoint_flux/tasks/endpoint_flux/generators/endpoint_flux/validations/predicates/password.rb +29 -0
  63. data/lib/endpoint_flux/tasks/endpoint_flux/generators/initializers/endpoint_flux.rb +41 -0
  64. data/lib/endpoint_flux/tasks/endpoint_flux/init.rake +12 -0
  65. data/lib/endpoint_flux/version.rb +3 -0
  66. data/spec/lib/class_loader_spec.rb +31 -0
  67. data/spec/lib/config/default_middlewares_spec.rb +21 -0
  68. data/spec/lib/config/endpoints_namespace_spec.rb +13 -0
  69. data/spec/lib/config/flow_spec.rb +8 -0
  70. data/spec/lib/config/interceptor_spec.rb +34 -0
  71. data/spec/lib/config/middleware_spec.rb +62 -0
  72. data/spec/lib/config/rescue_from_spec.rb +45 -0
  73. data/spec/lib/endpoint/flow_spec.rb +43 -0
  74. data/spec/lib/endpoint/middlewares_spec.rb +110 -0
  75. data/spec/lib/endpoint/perform_spec.rb +61 -0
  76. data/spec/lib/endpoint/rescue_from_spec.rb +61 -0
  77. data/spec/lib/endpoint_flux/rails/concerns/endpoint_controller_spec.rb +158 -0
  78. data/spec/lib/endpoint_flux/request_spec.rb +44 -0
  79. data/spec/lib/exceptions/forbidden_spec.rb +12 -0
  80. data/spec/lib/exceptions/not_found_spec.rb +12 -0
  81. data/spec/lib/exceptions/service_unavailable_spec.rb +12 -0
  82. data/spec/lib/exceptions/unauthorized_spec.rb +12 -0
  83. data/spec/lib/exceptions/validation_spec.rb +14 -0
  84. data/spec/lib/middlewares/authenticator/skip_spec.rb +5 -0
  85. data/spec/lib/middlewares/authorizator/skip_spec.rb +5 -0
  86. data/spec/lib/middlewares/decorator/add_status_spec.rb +17 -0
  87. data/spec/lib/middlewares/decorator/skip_spec.rb +5 -0
  88. data/spec/lib/middlewares/policy/skip_spec.rb +5 -0
  89. data/spec/lib/middlewares/shared_examples.rb +19 -0
  90. data/spec/lib/middlewares/validator/empty_spec.rb +15 -0
  91. data/spec/lib/response_spec.rb +131 -0
  92. data/spec/spec_helper.rb +56 -0
  93. metadata +203 -0
@@ -0,0 +1 @@
1
+
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-slate
@@ -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,20 @@
1
+ require_relative 'lib/endpoint_flux/version' # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'endpoint-flux2'
5
+ s.version = EndpointFlux::VERSION
6
+ s.summary = 'EndpointFlux!'
7
+ s.description = 'A simple way to organise API endpoints'
8
+ s.authors = ['Original creator Arturs Kreipans https://github.com/fragallia Forked by Pavel Kvasnikov https://github.com/pavelkvasnikov']
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/pavelkvasnikov/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
+ s.add_development_dependency 'codecov'
19
+
20
+ end
@@ -0,0 +1,22 @@
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
+ require "endpoint_flux/railtie" if defined?(Rails)
9
+
10
+ module EndpointFlux
11
+ module_function
12
+
13
+ def config(handler = nil)
14
+ @config = handler if handler
15
+
16
+ @config ||= EndpointFlux::Config.new
17
+ end
18
+
19
+ def clear
20
+ @config = nil
21
+ end
22
+ 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