flowy 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.
@@ -0,0 +1,63 @@
1
+ module Flowy
2
+ # Tag module + factory namespace. Both Flowy::Success and Flowy::Failure
3
+ # include this module so `result.is_a?(Flowy::Result)` matches either.
4
+ # The instance interface is defined on Success and Failure — see the README.
5
+ module Result
6
+ def self._deep_merge(a, b)
7
+ a.merge(b) do |_, va, vb|
8
+ if va.is_a?(Hash) && vb.is_a?(Hash)
9
+ _deep_merge(va, vb)
10
+ else
11
+ vb
12
+ end
13
+ end
14
+ end
15
+
16
+ def self._collect_results(enumerable)
17
+ enumerable.map do |item|
18
+ result = yield item
19
+ unless result.is_a?(Flowy::Result)
20
+ raise TypeError,
21
+ "all_success/any_success block must return a Flowy::Result, got #{result.class}"
22
+ end
23
+ result
24
+ end
25
+ end
26
+
27
+ def self.success(data: {}, warnings: [])
28
+ Flowy::Success.new(data: data, warnings: warnings)
29
+ end
30
+
31
+ def self.failure(error_code:, error_data: {}, error_title: nil, error_description: nil, parent_failure: nil)
32
+ Flowy::Failure.new(
33
+ error_code: error_code,
34
+ error_data: error_data,
35
+ error_title: error_title,
36
+ error_description: error_description,
37
+ parent_failure: parent_failure
38
+ )
39
+ end
40
+
41
+ # `rescue:` is captured via **opts because `rescue` is a Ruby reserved word
42
+ # and cannot be referenced as a bare local variable inside a method body.
43
+ def self.wrap(error_code: :wrapped_error, error_title: nil, **opts)
44
+ unknown = opts.keys - [:rescue]
45
+ raise ArgumentError, "unknown keyword: #{unknown.first}" if unknown.any?
46
+
47
+ rescued_classes = Array(opts.fetch(:rescue, [StandardError]))
48
+
49
+ value = yield
50
+
51
+ return value if value.is_a?(Flowy::Result)
52
+
53
+ Flowy::Success.new(data: { value: value })
54
+ rescue *rescued_classes => e
55
+ Flowy::Failure.new(
56
+ error_code: error_code,
57
+ error_data: { error_class: e.class.name, message: e.message },
58
+ error_title: error_title,
59
+ error_description: e.message
60
+ )
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,74 @@
1
+ module Flowy
2
+ class Success
3
+ include Flowy::Result
4
+
5
+ attr_reader :data, :warnings
6
+
7
+ def initialize(data: {}, warnings: [])
8
+ @data = data
9
+ @warnings = warnings
10
+ end
11
+
12
+ def +(other)
13
+ self.class.new(data: Flowy::Result._deep_merge(data, other.data))
14
+ end
15
+
16
+ def to_hash
17
+ {
18
+ success: true,
19
+ data: data,
20
+ warnings: warnings
21
+ }
22
+ end
23
+
24
+ def success?
25
+ true
26
+ end
27
+
28
+ def failure?
29
+ false
30
+ end
31
+
32
+ def on_success
33
+ yield self
34
+ self
35
+ end
36
+
37
+ def on_failure
38
+ self
39
+ end
40
+
41
+ def and_then
42
+ result = yield self
43
+ unless result.is_a?(Flowy::Success) || result.is_a?(Flowy::Failure)
44
+ raise TypeError, "and_then block must return a Flowy::Success or Flowy::Failure, got #{result.class}"
45
+ end
46
+
47
+ result
48
+ end
49
+
50
+ def or_else
51
+ self
52
+ end
53
+
54
+ def map_failure(**)
55
+ self
56
+ end
57
+
58
+ def raise!
59
+ self
60
+ end
61
+
62
+ def tap
63
+ yield self
64
+ self
65
+ end
66
+
67
+ def merge_data(extra = nil)
68
+ extra = block_given? ? yield(data) : extra
69
+ raise ArgumentError, 'merge_data requires a Hash' unless extra.is_a?(Hash)
70
+
71
+ self.class.new(data: Flowy::Result._deep_merge(data, extra), warnings: warnings)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module Flowy
2
+ VERSION = '0.1.0'
3
+ end
data/lib/flowy.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'flowy/version'
2
+ require 'flowy/result'
3
+ require 'flowy/success'
4
+ require 'flowy/failure'
5
+ require 'flowy/error'
6
+ require 'flowy/concern'
7
+ require 'flowy/enumerable'
8
+ require 'flowy/pipeline'
9
+
10
+ module Flowy
11
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flowy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Leanbit
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-05-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.13'
27
+ description: Flowy provides Success/Failure result objects and a composable step-based
28
+ concern for clean, functional-style service objects in Ruby.
29
+ email:
30
+ - support@leanbit.eu
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - LICENSE.txt
36
+ - README.md
37
+ - lib/flowy.rb
38
+ - lib/flowy/concern.rb
39
+ - lib/flowy/concern/step_runner.rb
40
+ - lib/flowy/enumerable.rb
41
+ - lib/flowy/error.rb
42
+ - lib/flowy/failure.rb
43
+ - lib/flowy/pipeline.rb
44
+ - lib/flowy/result.rb
45
+ - lib/flowy/success.rb
46
+ - lib/flowy/version.rb
47
+ homepage: https://github.com/leanbit/flowy
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '3.2'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.4.20
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Lightweight Railway Oriented Programming for Ruby
70
+ test_files: []