nucleus-core 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc1a3b1d1596531ada1d8537243b467070363b551133d8daa5e44503323a104f
4
- data.tar.gz: 95251212007f9bf1ee7d2f6df738c4389a885323d85bf2e861cd6260ca8c2be7
3
+ metadata.gz: 16856ef31bb784d60c0245dc2b64688edffafedc629346e4b0aa5ad85c67f03b
4
+ data.tar.gz: 6132356105d16688ba22fcb6ab4827727584660867ffd07b37f2c559d6a365c8
5
5
  SHA512:
6
- metadata.gz: 8325af67543e7de1f5519483a34b90adc0b98ca84a0610604d3e76592410da63fb82fd4eda685518490bc7652dd90ee13038d5f6703039c3e0bdbf03ffe3f59e
7
- data.tar.gz: a5e5d5d640253499f4f961aeabc135149522b4b59b2bd42f0fbddac089bc5b184ea4b65c46574f8769720b3cecf655eae8986f351bcb744607a31d815010a9fc
6
+ metadata.gz: 9d77daf39dac0313f1fae986f124394e3f1af56fb1c0a8324a2054398d9c43715df25513a3cc568150ae5eed87bdabd8def043f6542fccce443334b2ee5fdc8b
7
+ data.tar.gz: 7a233128785f6359cd01c351c6fc3633de18af631ea022aa42e73e978c37a59fecab0eaa770d482257ee6c4042182555f94a3ecc3058819df769b2ec2a12bc25
data/README.md CHANGED
@@ -4,26 +4,28 @@
4
4
  [![Circle](https://circleci.com/gh/dodgerogers/nucleus-core/tree/main.svg?style=shield)](https://app.circleci.com/pipelines/github/dodgerogers/nucleus-core?branch=main)
5
5
  [![Code Climate](https://codeclimate.com/github/dodgerogers/nucleus-core/badges/gpa.svg)](https://codeclimate.com/github/dodgerogers/nucleus-core)
6
6
 
7
- NucleusCore Core is a framework to express and orchestrate business logic in a way that is agnostic to the framework.
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. A `nucleus-rails` gem will handle the adaptation of NucleusCore::View objects to the rails rendering methods.
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
- NucleusCore::Responder.handle_response do
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
@@ -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 = Array.wrap(policy_method_and_args).first
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&.format&.to_sym || :json
13
+ @request_format = request&.to_sym || NucleusCore.configuration.default_response_format
7
14
  end
8
15
 
9
- def request_format
10
- @request_format ||= set_request_format
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
- render_method = {
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(render_method, entity)
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, *config.not_found
105
+ when NucleusCore::NotFound, *exceptions.not_found
99
106
  :not_found
100
- when NucleusCore::BadRequest, *config.bad_request
107
+ when NucleusCore::BadRequest, *exceptions.bad_request
101
108
  :bad_request
102
- when NucleusCore::NotAuthorized, *config.forbidden
109
+ when NucleusCore::NotAuthorized, *exceptions.forbidden
103
110
  :forbidden
104
- when NucleusCore::Unprocessable, *config.unprocessable
111
+ when NucleusCore::Unprocessable, *exceptions.unprocessable
105
112
  :unprocessable_entity
106
- when NucleusCore::BaseException, *config.server_error
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.logger&.send(log_level, object)
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 response_adapter
127
- NucleusCore.configuration.response_adapter
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 = NucleusCore::Rack::Utils.status_code(status)
20
+ status = Utils.status_code(status)
21
21
  default_status = 200
22
22
 
23
23
  status.zero? ? default_status : status
@@ -1,3 +1,3 @@
1
1
  module NucleusCore
2
- VERSION = "0.1.3".freeze
2
+ VERSION = "0.1.5".freeze
3
3
  end
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
- attr_reader :exceptions_map, :response_adapter
28
- attr_accessor :logger
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
- @response_adapter = nil
33
- @exceptions_map = nil
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 response_adapter=(adapter)
41
- @response_adapter = format_adapter(adapter)
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.3
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-01 00:00:00.000000000 Z
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.43.0
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.43.0
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.26.1
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.26.1
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/array.rb
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,11 +0,0 @@
1
- class Array
2
- def self.wrap(object)
3
- if object.nil?
4
- []
5
- elsif object.respond_to?(:to_ary)
6
- object.to_ary || [object]
7
- else
8
- [object]
9
- end
10
- end
11
- end
@@ -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