clear_logic 0.1.1
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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +86 -0
- data/LICENSE.txt +21 -0
- data/README.md +482 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/clear_logic-0.1.0.gem +0 -0
- data/clear_logic.gemspec +48 -0
- data/lib/clear_logic.rb +26 -0
- data/lib/clear_logic/context/builder.rb +53 -0
- data/lib/clear_logic/errors/catched_error.rb +11 -0
- data/lib/clear_logic/errors/failure_error.rb +11 -0
- data/lib/clear_logic/logger/adapter.rb +36 -0
- data/lib/clear_logic/logger/default.rb +24 -0
- data/lib/clear_logic/matcher.rb +53 -0
- data/lib/clear_logic/result.rb +54 -0
- data/lib/clear_logic/service.rb +55 -0
- data/lib/clear_logic/step_adapters/stride.rb +58 -0
- data/lib/clear_logic/types.rb +28 -0
- data/lib/clear_logic/version.rb +5 -0
- metadata +223 -0
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'clear_logic'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
Binary file
|
data/clear_logic.gemspec
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'clear_logic/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'clear_logic'
|
|
9
|
+
spec.version = ClearLogic::VERSION
|
|
10
|
+
spec.authors = ['bezrukavyi']
|
|
11
|
+
spec.email = ['yaroslav.bezrukavyi@gmail.com']
|
|
12
|
+
|
|
13
|
+
spec.summary = 'Clear result'
|
|
14
|
+
spec.description = 'Clear result'
|
|
15
|
+
spec.homepage = 'https://github.com/bezrukavyi/clear_logic'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
|
|
18
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
19
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
20
|
+
if spec.respond_to?(:metadata)
|
|
21
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
22
|
+
else
|
|
23
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
|
24
|
+
'public gem pushes.'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Specify which files should be added to the gem when it is released.
|
|
28
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
29
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
30
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
31
|
+
end
|
|
32
|
+
spec.bindir = 'exe'
|
|
33
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
34
|
+
spec.require_paths = ['lib']
|
|
35
|
+
|
|
36
|
+
spec.add_dependency 'dry-matcher', '>= 0.7'
|
|
37
|
+
spec.add_dependency 'dry-monads', '>= 0.3.1'
|
|
38
|
+
spec.add_dependency 'dry-transaction', '>= 0.12.0'
|
|
39
|
+
spec.add_dependency 'dry-initializer', '>= 2.5.0'
|
|
40
|
+
spec.add_dependency 'dry-types', '>= 1.0.0'
|
|
41
|
+
spec.add_dependency 'dry-inflector'
|
|
42
|
+
|
|
43
|
+
spec.add_development_dependency 'bundler', '~> 1.17'
|
|
44
|
+
spec.add_development_dependency 'pry'
|
|
45
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
46
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
47
|
+
spec.add_development_dependency 'ffaker'
|
|
48
|
+
end
|
data/lib/clear_logic.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'pry'
|
|
6
|
+
|
|
7
|
+
require 'dry-types'
|
|
8
|
+
require 'dry-initializer'
|
|
9
|
+
require 'dry-transaction'
|
|
10
|
+
require 'dry-matcher'
|
|
11
|
+
require 'dry-monads'
|
|
12
|
+
|
|
13
|
+
require 'clear_logic/logger/default'
|
|
14
|
+
require 'clear_logic/logger/adapter'
|
|
15
|
+
require 'clear_logic/errors/failure_error'
|
|
16
|
+
require 'clear_logic/errors/catched_error'
|
|
17
|
+
require 'clear_logic/version'
|
|
18
|
+
require 'clear_logic/types'
|
|
19
|
+
require 'clear_logic/result'
|
|
20
|
+
require 'clear_logic/step_adapters/stride'
|
|
21
|
+
require 'clear_logic/matcher'
|
|
22
|
+
require 'clear_logic/context/builder'
|
|
23
|
+
require 'clear_logic/service'
|
|
24
|
+
|
|
25
|
+
module ClearLogic
|
|
26
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClearLogic
|
|
4
|
+
class ContextBuilder
|
|
5
|
+
def self.call
|
|
6
|
+
Class.new do
|
|
7
|
+
extend ::Dry::Initializer
|
|
8
|
+
|
|
9
|
+
attr_reader :args
|
|
10
|
+
attr_accessor :catched_error, :failure_error, :service, :exit_success, :step
|
|
11
|
+
|
|
12
|
+
def initialize(*args)
|
|
13
|
+
@args = args
|
|
14
|
+
super(*args)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def [](key)
|
|
18
|
+
@additional_opts ||= {}
|
|
19
|
+
@additional_opts[key]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def []=(key, value)
|
|
23
|
+
@additional_opts ||= {}
|
|
24
|
+
@additional_opts[key] = value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def exit_success?
|
|
28
|
+
exit_success == true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def catched_error?
|
|
32
|
+
!catched_error.nil?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def failure_error?
|
|
36
|
+
!failure_error.nil?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_h
|
|
40
|
+
{
|
|
41
|
+
catched_error: catched_error,
|
|
42
|
+
failure_error: failure_error,
|
|
43
|
+
service: service.class,
|
|
44
|
+
exit_success: exit_success,
|
|
45
|
+
step: step,
|
|
46
|
+
options: @additional_opts,
|
|
47
|
+
args: args
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClearLogic
|
|
4
|
+
module Logger
|
|
5
|
+
class Adapter
|
|
6
|
+
attr_reader :service_class, :logger_class, :log_path
|
|
7
|
+
|
|
8
|
+
def initialize(service_class)
|
|
9
|
+
@service_class = service_class
|
|
10
|
+
@logger_class = service_class.logger_class
|
|
11
|
+
@log_path = service_class.logger_options[:log_path] || default_log_path
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def logger
|
|
15
|
+
@logger ||= create_logger
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def create_logger
|
|
21
|
+
system('mkdir', '-p', path) unless Dir.exist?(path)
|
|
22
|
+
|
|
23
|
+
logger_class.new(log_path)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def path
|
|
27
|
+
File.dirname(log_path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def default_log_path
|
|
31
|
+
file_name = Dry::Inflector.new.underscore(service_class.name.gsub('::', '/'))
|
|
32
|
+
File.join(ENV['BUNDLE_GEMFILE'], "log/#{file_name}.log").gsub!('Gemfile/', '')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ClearLogic
|
|
2
|
+
module Logger
|
|
3
|
+
class Default < ::Logger
|
|
4
|
+
DATE_FORMAT = '%y-%m-%d %H:%M:%S.%3N '.freeze
|
|
5
|
+
FORMAT = "[%s#%d#%d] %5s -- %s: %s\n".freeze
|
|
6
|
+
|
|
7
|
+
def format_message(severity, time, progname, context)
|
|
8
|
+
thread_id = Thread.current.object_id % 100_000
|
|
9
|
+
|
|
10
|
+
format(FORMAT, format_datetime(time), Process.pid, thread_id, severity, progname, pretty_view(context))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def format_datetime(time)
|
|
16
|
+
time.strftime(DATE_FORMAT)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def pretty_view(context)
|
|
20
|
+
JSON.pretty_generate(context.to_h)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClearLogic
|
|
4
|
+
class Matcher
|
|
5
|
+
CASES = %i[success failure].freeze
|
|
6
|
+
|
|
7
|
+
def self.call(*args)
|
|
8
|
+
new.matcher.call(*args) { |on| yield(on) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def matcher
|
|
12
|
+
dry_cases = CASES.each_with_object({}) do |one_case, case_list|
|
|
13
|
+
case_list[one_case] = send("#{one_case}_case")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Dry::Matcher.new(dry_cases)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def success_case
|
|
22
|
+
Dry::Matcher::Case.new(
|
|
23
|
+
match: ->(result) { result.success? },
|
|
24
|
+
resolve: ->(result) { result }
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def failure_case
|
|
29
|
+
Dry::Matcher::Case.new(
|
|
30
|
+
match: ->(result, *patterns) { patterns.any? ? case_patterns(result, patterns) : result.failure? },
|
|
31
|
+
resolve: ->(result) { result }
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def case_patterns(result, patterns)
|
|
36
|
+
patterns.any? do |pattern|
|
|
37
|
+
return false unless respond_to?("#{pattern}_pattern", private: true)
|
|
38
|
+
|
|
39
|
+
send("#{pattern}_pattern", result)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
ClearLogic::Result::DEFAULT_ERRORS.each do |error_type|
|
|
44
|
+
define_method "#{error_type}_pattern" do |result|
|
|
45
|
+
return false unless result.failure?
|
|
46
|
+
|
|
47
|
+
result.context.respond_to?(:failure_error) &&
|
|
48
|
+
!result.context.failure_error.nil? &&
|
|
49
|
+
result.context.failure_error.status == error_type
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
Dry::Monads::Result::Success.class_eval do
|
|
3
|
+
alias context success
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
Dry::Monads::Result::Failure.class_eval do
|
|
7
|
+
alias context failure
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClearLogic
|
|
11
|
+
module Result
|
|
12
|
+
DEFAULT_ERRORS = %i[
|
|
13
|
+
unauthorized
|
|
14
|
+
forbidden
|
|
15
|
+
not_found
|
|
16
|
+
invalid
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def self.included(base)
|
|
22
|
+
base.extend(ClassMethdos)
|
|
23
|
+
|
|
24
|
+
base.errors(*DEFAULT_ERRORS)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ClassMethdos
|
|
28
|
+
def errors(*errors_methods)
|
|
29
|
+
errors_methods.each do |error_type|
|
|
30
|
+
define_method(error_type) do |context|
|
|
31
|
+
context.failure_error ||= ClearLogic::FailureError.new(error_type)
|
|
32
|
+
|
|
33
|
+
failure(context)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private error_type
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def success(context)
|
|
42
|
+
Dry::Monads::Result::Success.new(context)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def exit_success(context)
|
|
46
|
+
context.exit_success = true
|
|
47
|
+
success(context)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def failure(context)
|
|
51
|
+
Dry::Monads::Result::Failure.new(context)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClearLogic
|
|
4
|
+
class Service
|
|
5
|
+
include Dry::Transaction
|
|
6
|
+
include ClearLogic::Result
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :context_class, :logger_instance, :logger_options,
|
|
10
|
+
:logger_class
|
|
11
|
+
|
|
12
|
+
def call(*args)
|
|
13
|
+
new.call(args)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build_context
|
|
17
|
+
self.context_class = ClearLogic::ContextBuilder.call
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def context(name, type = nil, **options)
|
|
21
|
+
context_class.class_eval do
|
|
22
|
+
method = options.delete(:as) || :option
|
|
23
|
+
send(method, name, type, **options)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def logger(logger_class, log_all: false, log_path: nil)
|
|
28
|
+
self.logger_options = { log_all: log_all, log_path: log_path }
|
|
29
|
+
self.logger_class = logger_class
|
|
30
|
+
self.logger_instance = ClearLogic::Logger::Adapter.new(self).logger
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def inherited(base)
|
|
34
|
+
base.class_eval do
|
|
35
|
+
attr_reader :context
|
|
36
|
+
|
|
37
|
+
build_context
|
|
38
|
+
|
|
39
|
+
logger ClearLogic::Logger::Default
|
|
40
|
+
|
|
41
|
+
step :initialize_context
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def initialize_context(args)
|
|
46
|
+
@context = self.class.context_class.new(*args.flatten)
|
|
47
|
+
context.service = self
|
|
48
|
+
|
|
49
|
+
success(context)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry/transaction/errors'
|
|
4
|
+
|
|
5
|
+
module ClearLogic
|
|
6
|
+
module StepAdapters
|
|
7
|
+
class Stride
|
|
8
|
+
include Dry::Monads::Result::Mixin
|
|
9
|
+
include ClearLogic::Result
|
|
10
|
+
|
|
11
|
+
attr_reader :operation, :options, :args, :context
|
|
12
|
+
|
|
13
|
+
def call(operation, options, args)
|
|
14
|
+
@operation = operation
|
|
15
|
+
@options = options
|
|
16
|
+
@args = args
|
|
17
|
+
@context = args.flatten.first
|
|
18
|
+
|
|
19
|
+
options[:rescue] ||= {}
|
|
20
|
+
|
|
21
|
+
context.step = options[:step_name]
|
|
22
|
+
|
|
23
|
+
return success(context) if context.exit_success?
|
|
24
|
+
|
|
25
|
+
result = operation.call(context)
|
|
26
|
+
|
|
27
|
+
log_result
|
|
28
|
+
|
|
29
|
+
return result if result.success?
|
|
30
|
+
|
|
31
|
+
failure_method
|
|
32
|
+
rescue *Array(options[:rescue].keys) => e
|
|
33
|
+
catch_error(e)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def catch_error(error)
|
|
37
|
+
context.catched_error = error
|
|
38
|
+
|
|
39
|
+
log_result
|
|
40
|
+
|
|
41
|
+
rescue_method = options[:rescue][error.class]
|
|
42
|
+
rescue_method ? context.service.send(rescue_method, context) : failure(context)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def log_result
|
|
46
|
+
return unless options[:log] || context.service.class.logger_options[:log_all]
|
|
47
|
+
|
|
48
|
+
context.service.class.logger_instance.info(context)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def failure_method
|
|
52
|
+
options[:failure] ? context.service.send(options[:failure], context) : failure(context)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Dry::Transaction::StepAdapters.register(:stride, ClearLogic::StepAdapters::Stride.new)
|