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,27 @@
1
+ module Endpoints
2
+ module Tasks
3
+ module Create
4
+ include EndpointFlux::Endpoint
5
+
6
+ validator :inline do
7
+ required(:title).value(:str?)
8
+ required(:article_id).value(:number?)
9
+ end
10
+
11
+ process do |request, response|
12
+ task = Task.create!(
13
+ title: request.params[:title],
14
+ article_id: request.params[:article_id]
15
+ )
16
+
17
+ response.body[:task] = task
18
+
19
+ [request, response]
20
+ end
21
+
22
+ decorator :add_status, 201
23
+ decorator :representable,
24
+ decorator: 'Tasks::Base', wrapped_in: :task
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Endpoints
2
+ module Tasks
3
+ module Destroy
4
+ include EndpointFlux::Endpoint
5
+
6
+ validator :inline do
7
+ required(:id).value(:number?)
8
+ end
9
+
10
+ process do |request, response|
11
+ task = Task.find(request.params[:id]).destroy
12
+
13
+ response.body[:task] = task
14
+
15
+ [request, response]
16
+ end
17
+
18
+ decorator :add_status, 202
19
+ decorator :representable,
20
+ decorator: 'Tasks::Base', wrapped_in: :task
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module Endpoints
2
+ module Tasks
3
+ module Index
4
+ include EndpointFlux::Endpoint
5
+
6
+ authorizator :skip
7
+
8
+ validator :inline do
9
+ required(:article_id).value(:number?)
10
+ end
11
+
12
+ process do |request, response|
13
+
14
+ response.body[:tasks] = Task.where(article_id: request.params[:article_id])
15
+
16
+ [request, response]
17
+ end
18
+
19
+ decorator :add_status, 200
20
+ decorator :representable, decorator: 'Tasks::Base',
21
+ collection?: true,
22
+ wrapped_in: :tasks
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ module Endpoints
2
+ module Tasks
3
+ module Update
4
+ include EndpointFlux::Endpoint
5
+
6
+ validator :inline do
7
+ required(:id).value(:number?)
8
+ required(:title).value(:str?)
9
+ required(:article_id).value(:number?)
10
+ end
11
+
12
+ process do |request, response|
13
+ Task.find(request.params[:id]).update(
14
+ title: request.params[:title],
15
+ article_id: request.params[:article_id]
16
+ )
17
+
18
+ response.body[:task] = Task.find(request.params[:id])
19
+
20
+ [request, response]
21
+ end
22
+
23
+ decorator :add_status, 201
24
+ decorator :representable,
25
+ decorator: 'Tasks::Base', wrapped_in: :task
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ module Middlewares
2
+ module Decorator
3
+ module Paginate
4
+ def self.perform(request, response, options)
5
+ page, per_page = request.params.values_at(:page, :per_page)
6
+ resources = response.body[options[:wrapped_in]]
7
+
8
+ if per_page != 'all' && resources
9
+ resources = resources.page(page).per(per_page)
10
+
11
+ response.body[options[:wrapped_in]] = resources
12
+ response.body[:pagination] = { total_pages: resources.total_pages }
13
+ end
14
+
15
+ [request, response]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ module Middlewares
2
+ module Decorator
3
+ module Representable
4
+ def self.perform(request, response, options)
5
+ resource_name = options[:wrapped_in] || options[:decorator]
6
+ resource = response.body[resource_name]
7
+
8
+ response.body[resource_name] = decorate(resource, options) if resource
9
+
10
+ [request, response]
11
+ end
12
+
13
+ def self.decorate(object, options)
14
+ decorator = "::Decorators::#{options[:decorator].to_s.camelize}".constantize
15
+
16
+ if options[:collection?]
17
+ decorator.for_collection.new(object.to_a).to_hash
18
+ else
19
+ decorator.new(object).to_hash
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Middlewares
2
+ module Validator
3
+ module Inline
4
+ def self.perform(request, response, _options, &block)
5
+ raise 'InlineValidator requires block with validations' unless block_given?
6
+
7
+ validation = ::Dry::Validation.Schema(::Validations::Base, &block).call(request.params)
8
+ unless validation.success?
9
+ raise EndpointFlux::Exceptions::Validation, validation.messages
10
+ end
11
+ request.params = validation.output
12
+
13
+ [request, response]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ require 'dry-validation'
2
+ require 'dry-types'
3
+
4
+ module Validations
5
+ module Types
6
+ include Dry::Types.module
7
+ end
8
+
9
+ class Base < Dry::Validation::Schema
10
+ configure do
11
+ predicates(Validations::Predicates::Base)
12
+
13
+ config.input_processor = :sanitizer
14
+ config.messages_file = 'config/locales/en/validation-errors.yml'
15
+ end
16
+ define! do
17
+ optional(:page).value(:number?)
18
+ optional(:per_page) { number? | eql?('all') }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Validations
2
+ module Error
3
+ def raise_validation_error(errors)
4
+ raise EndpointFlux::Exceptions::Validation, errors
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ module Validations
2
+ module Predicates
3
+ module Base
4
+ include Dry::Logic::Predicates
5
+ include Validations::Predicates::Password
6
+ include Validations::Predicates::Email
7
+ include Validations::Predicates::Bool
8
+ include Validations::Predicates::Dates
9
+ include Validations::Predicates::Decimal
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ module Validations
2
+ module Predicates
3
+ module Bool
4
+ module Methods
5
+ def bool?(value)
6
+ ![true, false, 'true', 'false', '0', '1'].find do |item|
7
+ value == item
8
+ end.nil?
9
+ end
10
+ end
11
+
12
+ extend Methods
13
+
14
+ def self.included(other)
15
+ super
16
+ other.extend(Methods)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ module Validations
2
+ module Predicates
3
+ module Dates
4
+ module Methods
5
+ def in_past?(value)
6
+ value && ::Rich::Date.safe_parse(value).try(:past?)
7
+ end
8
+
9
+ def in_future?(value)
10
+ !self[:in_past?].call(value)
11
+ end
12
+
13
+ def earlier_then?(v1, v2)
14
+ v1 && v2 && ::Rich::Date.safe_parse(v1) > ::Rich::Date.safe_parse(v2)
15
+ end
16
+
17
+ def on_or_later_then?(v1, v2)
18
+ !self[:earlier_then?].call(v1, v2)
19
+ end
20
+
21
+ def parsed_as_date?(value)
22
+ value.nil? || !Validations::Types::Form::DateTime[value].is_a?(String)
23
+ end
24
+ end
25
+
26
+ extend Methods
27
+
28
+ def self.included(other)
29
+ super
30
+ other.extend(Methods)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ require 'bigdecimal'
2
+
3
+ module Validations
4
+ module Predicates
5
+ module Decimal
6
+ module Methods
7
+ def decimal?(input)
8
+ begin
9
+ true if BigDecimal(input)
10
+ rescue ArgumentError, TypeError
11
+ false
12
+ end
13
+ end
14
+ end
15
+
16
+ extend Methods
17
+
18
+ def self.included(other)
19
+ super
20
+ other.extend(Methods)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module Validations
2
+ module Predicates
3
+ module Email
4
+ module Methods
5
+ def email?(value)
6
+ !%r{\A((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))}i.match(value).nil? # rubocop:disable LineLength
7
+ end
8
+ end
9
+
10
+ extend Methods
11
+
12
+ def self.included(other)
13
+ super
14
+ other.extend(Methods)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ module Validations
2
+ module Predicates
3
+ module Password
4
+ module Methods
5
+ def password?(value)
6
+ rules = [
7
+ %r{[A-Z]}, # at least 1 uppercase character (A-Z)
8
+ %r{[a-z]}, # at least 1 lowercase character (a-z)
9
+ %r{\d}, # at least 1 digit (0-9)
10
+ %r{\W} # at least 1 special character (punctuation)
11
+ ]
12
+
13
+ matches = rules.inject(0) do |n, rule|
14
+ value.match(rule) ? n + 1 : n
15
+ end
16
+
17
+ matches >= 3
18
+ end
19
+ end
20
+
21
+ extend Methods
22
+
23
+ def self.included(other)
24
+ super
25
+ other.extend(Methods)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ require 'endpoint_flux'
2
+
3
+ EndpointFlux.config.middlewares_namespaces << 'middlewares'
4
+
5
+ EndpointFlux.config.default_middlewares :authenticator, :skip
6
+ EndpointFlux.config.default_middlewares :authorizator, :skip
7
+ EndpointFlux.config.default_middlewares :validator, :empty
8
+ EndpointFlux.config.default_middlewares :policy, :skip
9
+ EndpointFlux.config.default_middlewares :decorator, :skip
10
+
11
+
12
+ EndpointFlux::Endpoint.class_eval do
13
+ define_method(:raise_validation_error) do |errors|
14
+ raise EndpointFlux::Exceptions::Validation, errors
15
+ end
16
+ end
17
+
18
+ rescue_from_exceptions = [
19
+ EndpointFlux::Exceptions::Validation,
20
+ EndpointFlux::Exceptions::Forbidden,
21
+ EndpointFlux::Exceptions::Unauthorized,
22
+ EndpointFlux::Exceptions::NotFound,
23
+ EndpointFlux::Exceptions::ServiceUnavailable
24
+ ]
25
+
26
+ EndpointFlux.config.rescue_from(rescue_from_exceptions) do |_, attrs, exception|
27
+ attrs[1].body.merge!(exception.to_hash)
28
+ attrs
29
+ end
30
+
31
+ EndpointFlux::Request.class_eval do
32
+ attr_accessor :current_user_params
33
+
34
+ define_method(:current_user) do
35
+ @current_user ||= begin
36
+ if current_user_params && current_user_params['id']
37
+ User.find_by(id: current_user_params['id'])
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,12 @@
1
+ namespace :endpoint_flux do
2
+ desc 'Initialize project with endpoint_flux'
3
+ task :init => :environment do
4
+ require 'fileutils'
5
+ puts 'Creating files...'
6
+ path = File.expand_path(__dir__)
7
+ FileUtils.cp_r "#{path}/generators/endpoint_flux", 'app/endpoint_flux'
8
+ FileUtils.cp "#{path}/generators/initializers/endpoint_flux.rb", 'config/initializers/endpoint_flux.rb'
9
+ puts 'Finished'
10
+ end
11
+ end
12
+
@@ -0,0 +1,3 @@
1
+ module EndpointFlux
2
+ VERSION = '1.1.6'.freeze
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe EndpointFlux::ClassLoader do
4
+ describe '#string_to_class_name' do
5
+ it 'converts string to class name' do
6
+ expect(subject.string_to_class_name('hey/bey/cey')).to eq('Hey::Bey::Cey')
7
+ end
8
+ end
9
+
10
+ describe '#load_class!' do
11
+ context 'when wrong params given' do
12
+ it 'fails if the class is not existing' do
13
+ expect { subject.load_class!('hey/bey') }
14
+ .to raise_error('The [hey/bey] should be a string representing a class')
15
+ end
16
+
17
+ it 'fails if the class name is invalid' do
18
+ expect { subject.load_class!('111/bey') }
19
+ .to raise_error('The [111/bey] should be a string representing a class')
20
+ end
21
+ end
22
+
23
+ context 'when valid params given' do
24
+ let(:sample_class) { stub_const 'SampleClass::SecondLevel', Class.new }
25
+
26
+ it 'returns the class back' do
27
+ expect(subject.load_class!(sample_class)).to be(sample_class)
28
+ end
29
+ end
30
+ end
31
+ end