mojones 0.1.0
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/lib/generators/mojones/service_generator.rb +15 -0
- data/lib/mojones/base.rb +131 -0
- data/lib/mojones/matcher.rb +98 -0
- data/lib/mojones/version.rb +5 -0
- data/lib/mojones.rb +8 -0
- metadata +63 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f0e3e1098a30a3cb361430de99a97ede30f422ed06c4778d20f140bd313c7a3d
|
|
4
|
+
data.tar.gz: addc8a5acded5a911a9fd214a1983aeac71258a3d55e66f34e59f2ad7b18afba
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1705669f6ef6e3084bbe785385ebe343a8217825532255e0b48338eff104f6a2d1ba4e77e7dbd70666e43648030626833ffad5b45cc48e2c039a10f04bfdef91
|
|
7
|
+
data.tar.gz: 9907b26c7a0af8ccb7233a7f8c6830ae89ac4a806f963c21c41d88a652effa073b505769bacb480ab97448b950911a15b5c85612d77623184681fe375cc00bf0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Mojones
|
|
6
|
+
module Generators
|
|
7
|
+
class ServiceGenerator < Rails::Generators::NamedBase
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
def create_service_file
|
|
11
|
+
template "service.rb.tt", File.join("app/services", class_path, "#{file_name}.rb")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/mojones/base.rb
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mojones
|
|
4
|
+
class Base
|
|
5
|
+
include Dry::Monads[:result, :do]
|
|
6
|
+
|
|
7
|
+
module Errors
|
|
8
|
+
class NoHandlerMatched < StandardError
|
|
9
|
+
def initialize(value)
|
|
10
|
+
super
|
|
11
|
+
@value = value
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def message = "No handler matched for #{@value.inspect}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class ServiceReturnedNonResult < StandardError
|
|
18
|
+
def initialize(service, value)
|
|
19
|
+
super(value)
|
|
20
|
+
@service_name = service.class.inspect.sub(/^Mojones::/, "")
|
|
21
|
+
@value = value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def message
|
|
25
|
+
"Service #{@service_name} returned non-Result value (#{@value.inspect} : #{@value.class})"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class ReturnedValue
|
|
31
|
+
def initialize(value)
|
|
32
|
+
@value = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def value!
|
|
36
|
+
@value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fmap
|
|
40
|
+
yield @value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def success?
|
|
44
|
+
@value.success?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def failure?
|
|
48
|
+
@value.failure?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def original
|
|
52
|
+
@value.either(:itself.to_proc, :itself.to_proc)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def no_matches!
|
|
56
|
+
raise Errors::NoHandlerMatched, @value
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class InitializerReturnedValue < ReturnedValue
|
|
61
|
+
def success? = true
|
|
62
|
+
def failure? = true
|
|
63
|
+
def original = @value
|
|
64
|
+
|
|
65
|
+
def assert_call_returned_result_monad_or_raised!
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class CallReturnedValue < ReturnedValue
|
|
71
|
+
def initialize(service, value)
|
|
72
|
+
super(value)
|
|
73
|
+
@service = service
|
|
74
|
+
@value = value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def assert_call_returned_result_monad_or_raised!
|
|
78
|
+
return self if @value.is_a?(Dry::Monads::Result)
|
|
79
|
+
|
|
80
|
+
raise Errors::ServiceReturnedNonResult.new(@service, @value)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class RaisedError
|
|
85
|
+
def initialize(error)
|
|
86
|
+
@error = error
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def success? = false
|
|
90
|
+
def failure? = true
|
|
91
|
+
|
|
92
|
+
def value!
|
|
93
|
+
raise @error
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def original
|
|
97
|
+
@error
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def fmap
|
|
101
|
+
self
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def no_matches!
|
|
105
|
+
raise @error
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def assert_call_returned_result_monad_or_raised!
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.call(*args, **kwargs, &)
|
|
114
|
+
result = (
|
|
115
|
+
begin
|
|
116
|
+
InitializerReturnedValue.new(new(*args, **kwargs))
|
|
117
|
+
rescue StandardError => e
|
|
118
|
+
RaisedError.new(e)
|
|
119
|
+
end
|
|
120
|
+
).fmap do |service_object|
|
|
121
|
+
CallReturnedValue.new(service_object, service_object.call)
|
|
122
|
+
rescue StandardError => e
|
|
123
|
+
RaisedError.new(e)
|
|
124
|
+
end.assert_call_returned_result_monad_or_raised!
|
|
125
|
+
|
|
126
|
+
return result.value! unless block_given?
|
|
127
|
+
|
|
128
|
+
Matcher.new(result, self, &).result
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
module Mojones
|
|
6
|
+
class Matcher
|
|
7
|
+
class Match
|
|
8
|
+
attr_reader :matched, :result
|
|
9
|
+
|
|
10
|
+
def initialize(matched:, result:)
|
|
11
|
+
@matched = matched
|
|
12
|
+
@result = result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Success < self; end
|
|
16
|
+
class Failure < self; end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(service_result, service)
|
|
20
|
+
@service = service
|
|
21
|
+
@service_result = service_result
|
|
22
|
+
@matches = []
|
|
23
|
+
yield(self) if block_given?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def result
|
|
27
|
+
return @service_result.no_matches! if @matches.empty?
|
|
28
|
+
|
|
29
|
+
@matches.last.result.tap do |chosen|
|
|
30
|
+
if @matches.size > 1
|
|
31
|
+
logger.debug <<~WARNING
|
|
32
|
+
#{service.name} matched multiple handlers; returning last result (#{chosen})
|
|
33
|
+
WARNING
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def success(*match_values, &block)
|
|
39
|
+
return unless @service_result.success?
|
|
40
|
+
|
|
41
|
+
value = @service_result.original
|
|
42
|
+
|
|
43
|
+
return unless match_values.empty? || match_values.any? { _1 === value } # rubocop:disable Style/CaseEquality
|
|
44
|
+
|
|
45
|
+
@matches << Match::Success.new(
|
|
46
|
+
matched: value,
|
|
47
|
+
result: block.call(value)
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def failure(*match_errors, &block)
|
|
52
|
+
return unless @service_result.failure?
|
|
53
|
+
|
|
54
|
+
error = @service_result.original
|
|
55
|
+
|
|
56
|
+
return unless match_errors.empty? || match_errors.any? { _1 === error } # rubocop:disable Style/CaseEquality
|
|
57
|
+
|
|
58
|
+
translated = translate_error(error)
|
|
59
|
+
|
|
60
|
+
@matches << Match::Failure.new(
|
|
61
|
+
matched: error,
|
|
62
|
+
result: block.arity == 2 ? block.call(error, translated) : block.call(translated)
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
attr_reader :service
|
|
69
|
+
|
|
70
|
+
def logger
|
|
71
|
+
@logger ||= defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def service_name_lookup
|
|
75
|
+
@service_name_lookup ||= service.name.gsub("::", ".").underscore
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def translate_error(error)
|
|
79
|
+
return error.to_s unless defined?(I18n)
|
|
80
|
+
|
|
81
|
+
key =
|
|
82
|
+
case error
|
|
83
|
+
when Symbol
|
|
84
|
+
error
|
|
85
|
+
when StandardError
|
|
86
|
+
error.class.name.gsub("::", ".").underscore
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
default =
|
|
90
|
+
case error
|
|
91
|
+
when Symbol then error.to_s.humanize
|
|
92
|
+
when StandardError then error.message
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
I18n.t("#{service_name_lookup}.#{key}", default: default)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/mojones.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mojones
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Phil Brockwell
|
|
8
|
+
- Habib Alamin
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: dry-monads
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.3'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.3'
|
|
27
|
+
description: A lightweight framework for Ruby service objects using Dry::Monads with
|
|
28
|
+
enforced result types and matching.
|
|
29
|
+
email:
|
|
30
|
+
- phil@trueflux.agency
|
|
31
|
+
- habib@trueflux.agency
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- lib/generators/mojones/service_generator.rb
|
|
37
|
+
- lib/mojones.rb
|
|
38
|
+
- lib/mojones/base.rb
|
|
39
|
+
- lib/mojones/matcher.rb
|
|
40
|
+
- lib/mojones/version.rb
|
|
41
|
+
homepage: https://trueflux.agency
|
|
42
|
+
licenses:
|
|
43
|
+
- MIT
|
|
44
|
+
metadata:
|
|
45
|
+
rubygems_mfa_required: 'true'
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.2'
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements: []
|
|
60
|
+
rubygems_version: 3.6.7
|
|
61
|
+
specification_version: 4
|
|
62
|
+
summary: Service objects with monadic result handling
|
|
63
|
+
test_files: []
|