nucleus-core 0.1.3 → 0.1.5
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 +4 -4
- data/README.md +12 -9
- data/lib/nucleus_core/extensions/utils.rb +88 -0
- data/lib/nucleus_core/policy.rb +1 -1
- data/lib/nucleus_core/responder.rb +22 -19
- data/lib/nucleus_core/response_adapter.rb +1 -1
- data/lib/nucleus_core/version.rb +1 -1
- data/lib/nucleus_core.rb +16 -38
- metadata +7 -8
- data/lib/nucleus_core/extensions/array.rb +0 -11
- data/lib/nucleus_core/extensions/rack.rb +0 -86
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16856ef31bb784d60c0245dc2b64688edffafedc629346e4b0aa5ad85c67f03b
|
4
|
+
data.tar.gz: 6132356105d16688ba22fcb6ab4827727584660867ffd07b37f2c559d6a365c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d77daf39dac0313f1fae986f124394e3f1af56fb1c0a8324a2054398d9c43715df25513a3cc568150ae5eed87bdabd8def043f6542fccce443334b2ee5fdc8b
|
7
|
+
data.tar.gz: 7a233128785f6359cd01c351c6fc3633de18af631ea022aa42e73e978c37a59fecab0eaa770d482257ee6c4042182555f94a3ecc3058819df769b2ec2a12bc25
|
data/README.md
CHANGED
@@ -4,26 +4,28 @@
|
|
4
4
|
[](https://app.circleci.com/pipelines/github/dodgerogers/nucleus-core?branch=main)
|
5
5
|
[](https://codeclimate.com/github/dodgerogers/nucleus-core)
|
6
6
|
|
7
|
-
NucleusCore Core is a
|
7
|
+
NucleusCore Core is a set of components which express and orchestrate business logic separatley to the framework.
|
8
8
|
|
9
|
-
## This gem is still very much in development.
|
9
|
+
## This gem is still very much in development. See `nucleus-rails` for Rails usage.
|
10
10
|
|
11
11
|
Here are the classes NucleusCore exposes, they have preordained responsibilities, can be composed together, and tested simply in isolation from the framework.
|
12
12
|
|
13
|
-
- Policy (Authorization) - Can this user perform this process?
|
14
|
-
- Operation (Services) - Executes a single unit of business logic, or side effect (ScheduleAppointment, CancelOrder, UpdateAddress).
|
15
|
-
- Workflow (Service Orchestration) - Excecutes multiple units of work, and side effects (ApproveLoan, TakePayment, CancelFulfillments).
|
16
|
-
- View (Presentation) - A presentation object which can render to multiple formats.
|
17
|
-
- Repository (Data access) - Interacts with data sources to hide the implementation details to callers, and return Aggregates. Data sources could be an API, ActiveRecord, SQL, a local file, etc.
|
18
|
-
- Aggregate (Domain/business Object) - Maps data from the data source to an object the aplication defines, known as an anti corruption layer.
|
13
|
+
- **Policy (Authorization)** - Can this user perform this process?
|
14
|
+
- **Operation (Services)** - Executes a single unit of business logic, or side effect (ScheduleAppointment, CancelOrder, UpdateAddress).
|
15
|
+
- **Workflow (Service Orchestration)** - Excecutes multiple units of work, and side effects (ApproveLoan, TakePayment, CancelFulfillments).
|
16
|
+
- **View (Presentation)** - A presentation object which can render to multiple formats.
|
17
|
+
- **Repository (Data access)** - Interacts with data sources to hide the implementation details to callers, and return Aggregates. Data sources could be an API, ActiveRecord, SQL, a local file, etc.
|
18
|
+
- **Aggregate (Domain/business Object)** - Maps data from the data source to an object the aplication defines, known as an anti corruption layer.
|
19
19
|
|
20
20
|
Below is an example using NucleusCore Core with Rails:
|
21
21
|
|
22
22
|
```ruby
|
23
23
|
# controllers/payments_controller.rb
|
24
24
|
class PaymentsController < ApplicationController
|
25
|
+
include NucleusCore::Responder
|
26
|
+
|
25
27
|
def create
|
26
|
-
|
28
|
+
handle_response do
|
27
29
|
policy.enforce!(:can_write?)
|
28
30
|
|
29
31
|
context, _process = HandleCheckoutWorkflow.call(invoice_params)
|
@@ -104,6 +106,7 @@ class ShoppingCartRepository < NucleusCore::Repository
|
|
104
106
|
end
|
105
107
|
end
|
106
108
|
|
109
|
+
# app/models/shopping_cart.rb
|
107
110
|
class ShoppingCart < ActiveRecord::Base
|
108
111
|
# ...
|
109
112
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# Rack::Utils patch for status code
|
2
|
+
class Utils
|
3
|
+
HTTP_STATUS_CODES = {
|
4
|
+
100 => "Continue",
|
5
|
+
101 => "Switching Protocols",
|
6
|
+
102 => "Processing",
|
7
|
+
103 => "Early Hints",
|
8
|
+
200 => "OK",
|
9
|
+
201 => "Created",
|
10
|
+
202 => "Accepted",
|
11
|
+
203 => "Non-Authoritative Information",
|
12
|
+
204 => "No Content",
|
13
|
+
205 => "Reset Content",
|
14
|
+
206 => "Partial Content",
|
15
|
+
207 => "Multi-Status",
|
16
|
+
208 => "Already Reported",
|
17
|
+
226 => "IM Used",
|
18
|
+
300 => "Multiple Choices",
|
19
|
+
301 => "Moved Permanently",
|
20
|
+
302 => "Found",
|
21
|
+
303 => "See Other",
|
22
|
+
304 => "Not Modified",
|
23
|
+
305 => "Use Proxy",
|
24
|
+
306 => "(Unused)",
|
25
|
+
307 => "Temporary Redirect",
|
26
|
+
308 => "Permanent Redirect",
|
27
|
+
400 => "Bad Request",
|
28
|
+
401 => "Unauthorized",
|
29
|
+
402 => "Payment Required",
|
30
|
+
403 => "Forbidden",
|
31
|
+
404 => "Not Found",
|
32
|
+
405 => "Method Not Allowed",
|
33
|
+
406 => "Not Acceptable",
|
34
|
+
407 => "Proxy Authentication Required",
|
35
|
+
408 => "Request Timeout",
|
36
|
+
409 => "Conflict",
|
37
|
+
410 => "Gone",
|
38
|
+
411 => "Length Required",
|
39
|
+
412 => "Precondition Failed",
|
40
|
+
413 => "Payload Too Large",
|
41
|
+
414 => "URI Too Long",
|
42
|
+
415 => "Unsupported Media Type",
|
43
|
+
416 => "Range Not Satisfiable",
|
44
|
+
417 => "Expectation Failed",
|
45
|
+
421 => "Misdirected Request",
|
46
|
+
422 => "Unprocessable Entity",
|
47
|
+
423 => "Locked",
|
48
|
+
424 => "Failed Dependency",
|
49
|
+
425 => "Too Early",
|
50
|
+
426 => "Upgrade Required",
|
51
|
+
428 => "Precondition Required",
|
52
|
+
429 => "Too Many Requests",
|
53
|
+
431 => "Request Header Fields Too Large",
|
54
|
+
451 => "Unavailable for Legal Reasons",
|
55
|
+
500 => "Internal Server Error",
|
56
|
+
501 => "Not Implemented",
|
57
|
+
502 => "Bad Gateway",
|
58
|
+
503 => "Service Unavailable",
|
59
|
+
504 => "Gateway Timeout",
|
60
|
+
505 => "HTTP Version Not Supported",
|
61
|
+
506 => "Variant Also Negotiates",
|
62
|
+
507 => "Insufficient Storage",
|
63
|
+
508 => "Loop Detected",
|
64
|
+
509 => "Bandwidth Limit Exceeded",
|
65
|
+
510 => "Not Extended",
|
66
|
+
511 => "Network Authentication Required"
|
67
|
+
}.freeze
|
68
|
+
|
69
|
+
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map do |code, message|
|
70
|
+
[message.downcase.gsub(/\s|-|'/, "_").to_sym, code]
|
71
|
+
end.flatten]
|
72
|
+
|
73
|
+
def self.status_code(status)
|
74
|
+
if status.is_a?(Symbol)
|
75
|
+
return SYMBOL_TO_STATUS_CODE.fetch(status) do
|
76
|
+
raise ArgumentError, "Unrecognized status code #{status.inspect}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
status.to_i
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.wrap(object)
|
84
|
+
return [] if object.nil?
|
85
|
+
|
86
|
+
object.is_a?(Array) ? object : [object]
|
87
|
+
end
|
88
|
+
end
|
data/lib/nucleus_core/policy.rb
CHANGED
@@ -11,7 +11,7 @@ module NucleusCore
|
|
11
11
|
policy_methods.each do |policy_method_and_args|
|
12
12
|
next if send(*policy_method_and_args)
|
13
13
|
|
14
|
-
name =
|
14
|
+
name = Utils.wrap(policy_method_and_args).first
|
15
15
|
message = "You do not have access to: #{name}"
|
16
16
|
|
17
17
|
raise NucleusCore::NotAuthorized, message
|
@@ -2,13 +2,22 @@ require "set"
|
|
2
2
|
|
3
3
|
module NucleusCore
|
4
4
|
module Responder
|
5
|
+
attr_reader :request_format, :response_adapter
|
6
|
+
|
7
|
+
def init_responder(request_format: nil, response_adapter: nil)
|
8
|
+
set_request_format(request_format)
|
9
|
+
set_response_adapter(response_adapter)
|
10
|
+
end
|
11
|
+
|
5
12
|
def set_request_format(request=nil)
|
6
|
-
@request_format = request&.
|
13
|
+
@request_format = request&.to_sym || NucleusCore.configuration.default_response_format
|
7
14
|
end
|
8
15
|
|
9
|
-
|
10
|
-
|
16
|
+
# rubocop:disable Naming/AccessorMethodName
|
17
|
+
def set_response_adapter(response_adapter)
|
18
|
+
@response_adapter = response_adapter || NucleusCore.configuration.response_adapter
|
11
19
|
end
|
20
|
+
# rubocop:enable Naming/AccessorMethodName
|
12
21
|
|
13
22
|
# rubocop:disable Lint/RescueException:
|
14
23
|
def handle_response(&block)
|
@@ -70,7 +79,7 @@ module NucleusCore
|
|
70
79
|
def render_response(entity)
|
71
80
|
render_headers(entity.headers)
|
72
81
|
|
73
|
-
|
82
|
+
method_name = {
|
74
83
|
NucleusCore::JsonResponse => :render_json,
|
75
84
|
NucleusCore::XmlResponse => :render_xml,
|
76
85
|
NucleusCore::PdfResponse => :render_pdf,
|
@@ -79,7 +88,7 @@ module NucleusCore
|
|
79
88
|
NucleusCore::NoResponse => :render_nothing
|
80
89
|
}.fetch(entity.class, nil)
|
81
90
|
|
82
|
-
response_adapter&.send(
|
91
|
+
response_adapter&.send(method_name, entity)
|
83
92
|
end
|
84
93
|
|
85
94
|
def render_headers(headers={})
|
@@ -92,18 +101,16 @@ module NucleusCore
|
|
92
101
|
|
93
102
|
# rubocop:disable Lint/DuplicateBranch
|
94
103
|
def exception_to_status(exception)
|
95
|
-
config = exception_map
|
96
|
-
|
97
104
|
case exception
|
98
|
-
when NucleusCore::NotFound, *
|
105
|
+
when NucleusCore::NotFound, *exceptions.not_found
|
99
106
|
:not_found
|
100
|
-
when NucleusCore::BadRequest, *
|
107
|
+
when NucleusCore::BadRequest, *exceptions.bad_request
|
101
108
|
:bad_request
|
102
|
-
when NucleusCore::NotAuthorized, *
|
109
|
+
when NucleusCore::NotAuthorized, *exceptions.forbidden
|
103
110
|
:forbidden
|
104
|
-
when NucleusCore::Unprocessable, *
|
111
|
+
when NucleusCore::Unprocessable, *exceptions.unprocessable
|
105
112
|
:unprocessable_entity
|
106
|
-
when NucleusCore::BaseException, *
|
113
|
+
when NucleusCore::BaseException, *exceptions.server_error
|
107
114
|
:internal_server_error
|
108
115
|
else
|
109
116
|
:internal_server_error
|
@@ -116,15 +123,11 @@ module NucleusCore
|
|
116
123
|
end
|
117
124
|
|
118
125
|
def logger(object, log_level=:info)
|
119
|
-
NucleusCore.configuration
|
120
|
-
end
|
121
|
-
|
122
|
-
def exception_map
|
123
|
-
NucleusCore.configuration.exceptions_map
|
126
|
+
NucleusCore.configuration&.logger&.send(log_level, object)
|
124
127
|
end
|
125
128
|
|
126
|
-
def
|
127
|
-
NucleusCore.configuration.
|
129
|
+
def exceptions
|
130
|
+
NucleusCore.configuration.exceptions
|
128
131
|
end
|
129
132
|
end
|
130
133
|
end
|
@@ -17,7 +17,7 @@ class NucleusCore::ResponseAdapter < NucleusCore::BasicObject
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def status_code(status=nil)
|
20
|
-
status =
|
20
|
+
status = Utils.status_code(status)
|
21
21
|
default_status = 200
|
22
22
|
|
23
23
|
status.zero? ? default_status : status
|
data/lib/nucleus_core/version.rb
CHANGED
data/lib/nucleus_core.rb
CHANGED
@@ -24,21 +24,21 @@ module NucleusCore
|
|
24
24
|
class BadRequest < BaseException; end
|
25
25
|
|
26
26
|
class Configuration
|
27
|
-
|
28
|
-
|
27
|
+
attr_accessor :response_adapter, :default_response_format, :logger
|
28
|
+
attr_reader :exceptions
|
29
|
+
|
30
|
+
RESPONSE_ADAPTER_METHODS = %i[
|
31
|
+
render_json render_xml render_text render_pdf render_csv render_nothing set_header
|
32
|
+
].freeze
|
29
33
|
|
30
34
|
def initialize
|
31
35
|
@logger = nil
|
32
|
-
@
|
33
|
-
@
|
34
|
-
end
|
35
|
-
|
36
|
-
def exceptions_map=(args={})
|
37
|
-
@exceptions_map = format_exceptions(args)
|
36
|
+
@exceptions = format_exceptions
|
37
|
+
@default_response_format = :json
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
41
|
-
@
|
40
|
+
def exceptions=(args={})
|
41
|
+
@exceptions = format_exceptions(args)
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -51,35 +51,13 @@ module NucleusCore
|
|
51
51
|
|
52
52
|
def format_exceptions(exceptions={})
|
53
53
|
exception_defaults = ERROR_STATUSES.reduce({}) { |acc, ex| acc.merge(ex => nil) }
|
54
|
+
exceptions = (exceptions || exception_defaults)
|
55
|
+
.slice(*exception_defaults.keys)
|
56
|
+
.reduce({}) do |acc, (key, value)|
|
57
|
+
acc.merge(key => Utils.wrap(value))
|
58
|
+
end
|
54
59
|
|
55
|
-
objectify(
|
56
|
-
(exceptions || exception_defaults)
|
57
|
-
.slice(*exception_defaults.keys)
|
58
|
-
.reduce({}) do |acc, (key, value)|
|
59
|
-
acc.merge(key => Array.wrap(value))
|
60
|
-
end
|
61
|
-
)
|
62
|
-
end
|
63
|
-
|
64
|
-
def format_adapter(adapter={})
|
65
|
-
adapter.tap do |a|
|
66
|
-
verify_adapter!(a)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
ADAPTER_METHODS = %i[
|
71
|
-
render_json render_xml render_text render_pdf render_csv render_nothing set_header
|
72
|
-
].freeze
|
73
|
-
|
74
|
-
def verify_adapter!(adapter)
|
75
|
-
# current_adapter_methods = Set[*(adapter.methods - Object.methods)]
|
76
|
-
# required_adapter_methods = ADAPTER_METHODS.to_set
|
77
|
-
|
78
|
-
# return if current_adapter_methods == required_adapter_methods
|
79
|
-
|
80
|
-
# missing = current_adapter_methods.subtract(required_adapter_methods)
|
81
|
-
|
82
|
-
# raise ArgumentError, "responder.adapter must implement: #{missing}"
|
60
|
+
objectify(exceptions)
|
83
61
|
end
|
84
62
|
end
|
85
63
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nucleus-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- dodgerogers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02-
|
11
|
+
date: 2023-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -72,28 +72,28 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - '='
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 1.
|
75
|
+
version: 1.44.1
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - '='
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 1.
|
82
|
+
version: 1.44.1
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rubocop-minitest
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - '='
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
89
|
+
version: 0.27.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - '='
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
96
|
+
version: 0.27.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rubocop-packaging
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -151,8 +151,7 @@ files:
|
|
151
151
|
- lib/nucleus_core/aggregate.rb
|
152
152
|
- lib/nucleus_core/basic_object.rb
|
153
153
|
- lib/nucleus_core/cli.rb
|
154
|
-
- lib/nucleus_core/extensions/
|
155
|
-
- lib/nucleus_core/extensions/rack.rb
|
154
|
+
- lib/nucleus_core/extensions/utils.rb
|
156
155
|
- lib/nucleus_core/operation.rb
|
157
156
|
- lib/nucleus_core/policy.rb
|
158
157
|
- lib/nucleus_core/repository.rb
|
@@ -1,86 +0,0 @@
|
|
1
|
-
# Rack::Utils patch for status code
|
2
|
-
module NucleusCore
|
3
|
-
module Rack
|
4
|
-
class Utils
|
5
|
-
HTTP_STATUS_CODES = {
|
6
|
-
100 => "Continue",
|
7
|
-
101 => "Switching Protocols",
|
8
|
-
102 => "Processing",
|
9
|
-
103 => "Early Hints",
|
10
|
-
200 => "OK",
|
11
|
-
201 => "Created",
|
12
|
-
202 => "Accepted",
|
13
|
-
203 => "Non-Authoritative Information",
|
14
|
-
204 => "No Content",
|
15
|
-
205 => "Reset Content",
|
16
|
-
206 => "Partial Content",
|
17
|
-
207 => "Multi-Status",
|
18
|
-
208 => "Already Reported",
|
19
|
-
226 => "IM Used",
|
20
|
-
300 => "Multiple Choices",
|
21
|
-
301 => "Moved Permanently",
|
22
|
-
302 => "Found",
|
23
|
-
303 => "See Other",
|
24
|
-
304 => "Not Modified",
|
25
|
-
305 => "Use Proxy",
|
26
|
-
306 => "(Unused)",
|
27
|
-
307 => "Temporary Redirect",
|
28
|
-
308 => "Permanent Redirect",
|
29
|
-
400 => "Bad Request",
|
30
|
-
401 => "Unauthorized",
|
31
|
-
402 => "Payment Required",
|
32
|
-
403 => "Forbidden",
|
33
|
-
404 => "Not Found",
|
34
|
-
405 => "Method Not Allowed",
|
35
|
-
406 => "Not Acceptable",
|
36
|
-
407 => "Proxy Authentication Required",
|
37
|
-
408 => "Request Timeout",
|
38
|
-
409 => "Conflict",
|
39
|
-
410 => "Gone",
|
40
|
-
411 => "Length Required",
|
41
|
-
412 => "Precondition Failed",
|
42
|
-
413 => "Payload Too Large",
|
43
|
-
414 => "URI Too Long",
|
44
|
-
415 => "Unsupported Media Type",
|
45
|
-
416 => "Range Not Satisfiable",
|
46
|
-
417 => "Expectation Failed",
|
47
|
-
421 => "Misdirected Request",
|
48
|
-
422 => "Unprocessable Entity",
|
49
|
-
423 => "Locked",
|
50
|
-
424 => "Failed Dependency",
|
51
|
-
425 => "Too Early",
|
52
|
-
426 => "Upgrade Required",
|
53
|
-
428 => "Precondition Required",
|
54
|
-
429 => "Too Many Requests",
|
55
|
-
431 => "Request Header Fields Too Large",
|
56
|
-
451 => "Unavailable for Legal Reasons",
|
57
|
-
500 => "Internal Server Error",
|
58
|
-
501 => "Not Implemented",
|
59
|
-
502 => "Bad Gateway",
|
60
|
-
503 => "Service Unavailable",
|
61
|
-
504 => "Gateway Timeout",
|
62
|
-
505 => "HTTP Version Not Supported",
|
63
|
-
506 => "Variant Also Negotiates",
|
64
|
-
507 => "Insufficient Storage",
|
65
|
-
508 => "Loop Detected",
|
66
|
-
509 => "Bandwidth Limit Exceeded",
|
67
|
-
510 => "Not Extended",
|
68
|
-
511 => "Network Authentication Required"
|
69
|
-
}.freeze
|
70
|
-
|
71
|
-
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map do |code, message|
|
72
|
-
[message.downcase.gsub(/\s|-|'/, "_").to_sym, code]
|
73
|
-
end.flatten]
|
74
|
-
|
75
|
-
def self.status_code(status)
|
76
|
-
if status.is_a?(Symbol)
|
77
|
-
return SYMBOL_TO_STATUS_CODE.fetch(status) do
|
78
|
-
raise ArgumentError, "Unrecognized status code #{status.inspect}"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
status.to_i
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|