mayak 0.0.15 → 0.1.0

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: 8217e22d103f9b3a253973f90f330d3b69d98320919c85b28a9d05df42c9b901
4
- data.tar.gz: 89e2f9a86616f036129b0c31b09d864b811067febc4b31191a6d98ef1b332774
3
+ metadata.gz: eb1722eed3175033b058ab2e87589e45d31fb701f402f51978758663d0ad3c82
4
+ data.tar.gz: 3b2aa6f45f79aa848bd017e208f4568aef6ecdc9f3f80fb18eae8b5053fee551
5
5
  SHA512:
6
- metadata.gz: e44eddcb1be94600d1501ecfbac8690557c233410ebb5a470e669201a40d564aafe00ea358027fe78887b893dc3b7cc0fb1aaa6be8f02d9d75ea19ac64abed2c
7
- data.tar.gz: 2238e28cfd575b50f0a2fc6fb360485fd10c2fd754c441493f394bd289f3f38a5b54f13d952fbfa8ef09b85bbed9968f8245500ade2ae405bd5b12b93862e6cf
6
+ metadata.gz: 9ea0826e78b1cf81e74fae2df74d42f3842e19fa05c5b9bfcdd95955221efb8cb161bcd72dd8623e55defd8b1b1677745edea195b9bf4c5c644eadb575f47d92
7
+ data.tar.gz: d20145430d27b269422d1c18a81256704969535c7a9616ad92da9008a4fb3ca63a46c5d24b103473232ab5a3de513f91b455d77213a75ff06c54df93d91f4b1f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Даниил Бобер
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -9,11 +9,17 @@ Mayak is a library which aims to provide abstractions for well typed programming
9
9
  In order to use the library, add the following line to your `Gemfile`:
10
10
 
11
11
  ```ruby
12
- gem 'mayak'
12
+ gem "mayak"
13
13
  ```
14
14
  or install it via the following command:
15
15
  ```ruby
16
- gem install 'mayak'
16
+ gem install "mayak"
17
+ ```
18
+
19
+ If you are using tapioca, add following line into tapioca's `require.rb` before generating rbi's for the gem:
20
+
21
+ ```ruby
22
+ require "mayak"
17
23
  ```
18
24
 
19
25
  ### Documentation
@@ -34,6 +40,233 @@ Mayak consists from separate classes and interfaces as well as separate modules
34
40
 
35
41
  #### Miscellaneous
36
42
 
43
+ ##### Lazy
44
+
45
+ `Lazy` classs represents a value that evaluates only during it's access, and evaluates only once
46
+ during the first access. Basically, `Lazy` wraps a block of code (thunk) that returns a value (`Lazy` has single type parameter of a value), and executes only when the value accessed for the first time
47
+ and then stores it afterward.
48
+
49
+ In order to build `Lazy` a type parameter of value holded should be provided as well as a block that computes a value of the type.
50
+ Note that the block is not executed right away.
51
+
52
+ ```ruby
53
+ lazy1 = ::Mayak::Lazy[Integer].new { 1 }
54
+
55
+ buffer = []
56
+ lazy2 = ::Mayak::Lazy[Integer].new do
57
+ buffer << 1
58
+ 1
59
+ end
60
+ buffer
61
+ #> []
62
+ ```
63
+
64
+ To access the value call `#value`. If the value is not yet computed, provided block will be executed and its result will be stored. Further invokations
65
+ of this method won't execute the block again.
66
+
67
+ ```ruby
68
+ buffer = []
69
+ lazy = ::Mayak::Lazy[Integer].new do
70
+ buffer << 1
71
+ 1
72
+ end
73
+ buffer
74
+ #> []
75
+
76
+ # Will execute the block and return the computed value.
77
+ lazy.value
78
+ #> 1
79
+ buffer
80
+ #> [1]
81
+
82
+ # Will return the memoized value, but won't call the block again
83
+ lazy.value
84
+ #> 1
85
+ buffer
86
+ #> [1]
87
+ ```
88
+
89
+ `Lazy` can be used in situations, when we want to inject some dependency into some class or method, but it may not be used, and the computation or aacquisition of the dependency may be cosftul. In this cases, it's acquisitation may be wrapped in lazy.
90
+
91
+ In more imperative style
92
+ ```ruby
93
+ sig { params(env_variable: String, file_content: ::Mayak::Lazy[String], default: String).returns(String) }
94
+ def fetch_config(env_variable, file_content, default)
95
+ from_environment = ENV[env_variable]
96
+ if env.empty?
97
+ file_config = ::Core::Json.parse(file_content.value).success_or({})
98
+ from_file = file_config["configuration"]
99
+ if from_file.empty?
100
+ default
101
+ else
102
+ from_file
103
+ end
104
+ else
105
+ from_environment
106
+ end
107
+ end
108
+ ```
109
+
110
+ Using Mayak monads:
111
+ ```ruby
112
+ include ::Mayak::Monads::Maybe::Mixin
113
+
114
+ sig { params(env_variable: String, file_content: ::Mayak::Lazy[String], default: String).returns(String) }
115
+ def fetch_config(env_variable, file_content, default)
116
+ Maybe(ENV[env_variable])
117
+ .recover_with_maybe(::Core::Json.parse(file_content.value).to_maybe)
118
+ .flat_map { |json| Maybe(json["configuration"]) }
119
+ .value_or(default)
120
+ end
121
+ ```
122
+
123
+ This method receives name of environment variable, and file content as lazy value. The method
124
+ tries to read the environment variable, and if it's not present and reads the file content to find the configuration.
125
+ `Lazy` allows to incapsulate behaviour of reading from file, so it can be passed as dependency, method `#fetch_config` doesn't
126
+ know anything about reading from file, but because of usage of `lazy` we can postpone it's execution thus avoiding unnecessary work.
127
+
128
+ `Lazy` can be transformed via methods `#map` and `#flat_map`.
129
+
130
+ Method `#map` allows to transform value inside `Lazy` without triggering executing. Note that `#map` returns
131
+ a new instance without mutating previous `Lazy`.
132
+ ```ruby
133
+ int_lazy = ::Mayak::Lazy[Integer].new do
134
+ puts("On initialize")
135
+ 1
136
+ end
137
+ string_lazy = int_lazy.map do |int|
138
+ puts("On mapping")
139
+ int.to_s
140
+ end
141
+ int_lazy.value # 1
142
+ #> On initialize
143
+
144
+ string_lazy.value # "1"
145
+ #> On initialize
146
+ #> On mapping
147
+
148
+ sig { params(file_content: ::Mayak::Lazy[String]).returns(::Mayak::Lazy[Maybe[String]]) }
149
+ def file_content_config(file_content)
150
+ file_content.map do |file_content|
151
+ ::Core::Json
152
+ .parse(file_content.value)
153
+ .to_maybe
154
+ .flat_map { |json| Maybe(json["configuration"]) }
155
+ end
156
+ end
157
+ ```
158
+
159
+ Method `#flat_map` allows to chain lazy computations. It receives a block, that builds a new `Lazy` value from the value of original
160
+ `Lazy` and returns a new instance of `Lazy`.
161
+
162
+ ```ruby
163
+ sig { params(env_name: String).returns(::Mayak::Lazy[String]) }
164
+ def lazy_env(env_name)
165
+ ::Mayak::Lazy[String].new { ENV[env_name] }
166
+ end
167
+
168
+ env_variable_name = ::Mayak::Lazy[String].new { "VARIABLE" }
169
+ env_variable = env_variable_name.flat_map { |env_name| lazy_env(env_name) }
170
+ ```
171
+
172
+ This may be useful when want to perform a lazy computation based on result of some other lazy computation without enforcing the evaluation.
173
+
174
+ For example we have a file that contains list of file names. We can build a lazy computation that read all lines from this code.
175
+ ```ruby
176
+ sig { params(file_name: String).returns(::Mayak::Lazy[T::Array[String]]) }
177
+ def read_file_lines(file_name)
178
+ ::Mayak::Lazy[T::Array[String]].new { File.read(file_name).split }
179
+ end
180
+ ```
181
+
182
+ Let's we want to read all filenames from the root file, and then read the first file lazily. In this cases, the lazy computation can be chained via `#flat_map`:
183
+
184
+ ```ruby
185
+ sig { params(file_name: String).returns(::Mayak::Lazy[T::Array[String]]) }
186
+ def read_first_file(file_name)
187
+ read_file_lines(file_name).flat_map do |file_names|
188
+ Maybe(file_names.first)
189
+ .filter(&:empty?)
190
+ .map { |file| read_file_lines(file) }
191
+ .value_or(::Mayak::Lazy[T::Array[String]].new { [] })
192
+ end
193
+ end
194
+ ```
195
+
196
+ In order to combine two lazies of different types into a single one, method `#combine` can be used.
197
+ This method receives another lazy (it can be lazy of different type), and a block
198
+ and returns a lazy containing result of applying passed blocked to values calculated by lazies.
199
+
200
+ ```ruby
201
+ class ConfigFiles < T::Struct
202
+ const :database_config_file, ::File
203
+ const :server_config_file, ::File
204
+ end
205
+
206
+ sig { returns(::Mayak::Lazy[File]) }
207
+ def database_config_file
208
+ ::Mayak::Lazy[File].new { File.new(DATABASE_CONFIG_FILE_NAME, "r") }
209
+ end
210
+
211
+ sig { returns(::Mayak::Lazy[File]) }
212
+ def server_config_file
213
+ ::Mayak::Lazy[File].new { File.new(SERVER_CONFIG_FILE_NAME, "r") }
214
+ end
215
+
216
+ sig { returns(::Mayak::Lazy[ConfigFiles]) }
217
+ def config_files
218
+ database_config_file.combine(server_config_file) do |db_file, server_file|
219
+ ConfigFiles.new(
220
+ database_config_file: database_config_file,
221
+ server_config_file: server_file
222
+ )
223
+ end
224
+ end
225
+ ```
226
+
227
+ The same behaviour can be achieved with a method `.combine_two`:
228
+
229
+ ```ruby
230
+ sig { returns(::Mayak::Lazy[ConfigFiles]) }
231
+ def config_files
232
+ ::Mayak::Lazy.combine_two(database_config_file, server_config_file) do |db_file, server_file|
233
+ ConfigFiles.new(
234
+ database_config_file: database_config_file,
235
+ server_config_file: server_file
236
+ )
237
+ end
238
+ end
239
+ ```
240
+
241
+ There are also methods `.combine_three`, `.combine_four` upto `.combine_sevel` to combine multiple lazies of diffent types.
242
+
243
+ If you need to combined multiple lazies containing the same value, you can use `.combine_many`. It works
244
+ as `Array#reduce`: receives an array of lazies containing the same type, initial value of result type, and a block
245
+ receiving accumulator value of result type, and value of next lazy.
246
+
247
+ ```ruby
248
+ sig { returns(::Mayak::Lazy[Integer]) }
249
+ def lazy
250
+ ::Mayak::Lazy.combine_many(
251
+ [::Mayak::Lazy[Integer].new(1), ::Mayak::Lazy[Integer].new(2), ::Mayak::Lazy[Integer].new(3)],
252
+ 0
253
+ ) { |acc, value| acc + value }
254
+ end
255
+
256
+ lazy.value # 10
257
+ ```
258
+
259
+ If you need to transform array of lazies of some value into lazy of array of the value, you can use `.sequence` method.
260
+
261
+ ```ruby
262
+ sig { returns(::Mayak::Lazy[T::Array[Integer]]) }
263
+ def lazy
264
+ ::Mayak::Lazy.sequence([::Mayak::Lazy[Integer].new(1), ::Mayak::Lazy[Integer].new(2), ::Mayak::Lazy[Integer].new(3)])
265
+ end
266
+
267
+ lazy.value # [1, 2, 3]
268
+ ```
269
+
37
270
  ##### Function
38
271
  In some situations Sorbet can not infer a type of proc passed:
39
272
 
data/lib/mayak/lazy.rb ADDED
@@ -0,0 +1,307 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require "json"
5
+
6
+ module Mayak
7
+ class Lazy
8
+ extend T::Sig
9
+ extend T::Generic
10
+
11
+ Value = type_member
12
+
13
+ sig { params(blk: T.proc.returns(Value)).void }
14
+ def initialize(&blk)
15
+ @thunk = T.let(blk, T.proc.returns(Value))
16
+ @value = T.let(nil, T.nilable(Value))
17
+ @forced = T.let(false, T::Boolean)
18
+ end
19
+
20
+ sig { returns(Value) }
21
+ def value
22
+ if @forced
23
+ T.must(@value)
24
+ else
25
+ @forced = true
26
+ @value = @thunk.call
27
+ @value
28
+ end
29
+ end
30
+
31
+ sig {
32
+ type_parameters(
33
+ :NewValue
34
+ ).params(
35
+ blk: T.proc.params(arg0: Value).returns(T.type_parameter(:NewValue))
36
+ ).returns(
37
+ ::Mayak::Lazy[T.type_parameter(:NewValue)]
38
+ )
39
+ }
40
+ def map(&blk)
41
+ ::Mayak::Lazy.new { blk.call(value) }
42
+ end
43
+
44
+ sig {
45
+ type_parameters(
46
+ :NewValue
47
+ ).params(
48
+ blk: T.proc.params(arg0: Value).returns(::Mayak::Lazy[T.type_parameter(:NewValue)])
49
+ ).returns(
50
+ ::Mayak::Lazy[T.type_parameter(:NewValue)]
51
+ )
52
+ }
53
+ def flat_map(&blk)
54
+ ::Mayak::Lazy.new { blk.call(value).value }
55
+ end
56
+
57
+ sig {
58
+ type_parameters(
59
+ :AnotherValue,
60
+ :NewValue
61
+ ).params(
62
+ another: ::Mayak::Lazy[T.type_parameter(:AnotherValue)],
63
+ blk: T.proc.params(arg0: Value, arg1: T.type_parameter(:AnotherValue)).returns(T.type_parameter(:NewValue))
64
+ ).returns(
65
+ ::Mayak::Lazy[T.type_parameter(:NewValue)]
66
+ )
67
+ }
68
+ def combine(another, &blk)
69
+ ::Mayak::Lazy[T.type_parameter(:NewValue)].new do
70
+ blk.call(value, another.value)
71
+ end
72
+ end
73
+
74
+ sig {
75
+ type_parameters(
76
+ :Value
77
+ ).params(
78
+ lazies: T::Array[::Mayak::Lazy[T.type_parameter(:Value)]]
79
+ ).returns(
80
+ ::Mayak::Lazy[T::Array[T.type_parameter(:Value)]]
81
+ )
82
+ }
83
+ def self.sequence(lazies)
84
+ ::Mayak::Lazy[T::Array[T.type_parameter(:Value)]].new { lazies.map(&:value) }
85
+ end
86
+
87
+ sig {
88
+ type_parameters(
89
+ :FirstValue,
90
+ :SecondValue,
91
+ :ResultValue
92
+ ).params(
93
+ first: ::Mayak::Lazy[T.type_parameter(:FirstValue)],
94
+ second: ::Mayak::Lazy[T.type_parameter(:SecondValue)],
95
+ blk: T.proc.params(
96
+ arg0: T.type_parameter(:FirstValue),
97
+ arg1: T.type_parameter(:SecondValue)
98
+ ).returns(
99
+ T.type_parameter(:ResultValue)
100
+ )
101
+ ).returns(
102
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)]
103
+ )
104
+ }
105
+ def self.combine_two(first, second, &blk)
106
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)].new do
107
+ blk.call(first.value, second.value)
108
+ end
109
+ end
110
+
111
+ sig {
112
+ type_parameters(
113
+ :FirstValue,
114
+ :SecondValue,
115
+ :ThirdValue,
116
+ :ResultValue
117
+ ).params(
118
+ first: ::Mayak::Lazy[T.type_parameter(:FirstValue)],
119
+ second: ::Mayak::Lazy[T.type_parameter(:SecondValue)],
120
+ third: ::Mayak::Lazy[T.type_parameter(:ThirdValue)],
121
+ blk: T.proc.params(
122
+ arg0: T.type_parameter(:FirstValue),
123
+ arg1: T.type_parameter(:SecondValue),
124
+ arg2: T.type_parameter(:ThirdValue)
125
+ ).returns(
126
+ T.type_parameter(:ResultValue)
127
+ )
128
+ ).returns(
129
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)]
130
+ )
131
+ }
132
+ def self.combine_three(first, second, third, &blk)
133
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)].new do
134
+ blk.call(first.value, second.value, third.value)
135
+ end
136
+ end
137
+
138
+ sig {
139
+ type_parameters(
140
+ :FirstValue,
141
+ :SecondValue,
142
+ :ThirdValue,
143
+ :FourthValue,
144
+ :ResultValue
145
+ ).params(
146
+ first: ::Mayak::Lazy[T.type_parameter(:FirstValue)],
147
+ second: ::Mayak::Lazy[T.type_parameter(:SecondValue)],
148
+ third: ::Mayak::Lazy[T.type_parameter(:ThirdValue)],
149
+ fourth: ::Mayak::Lazy[T.type_parameter(:FourthValue)],
150
+ blk: T.proc.params(
151
+ arg0: T.type_parameter(:FirstValue),
152
+ arg1: T.type_parameter(:SecondValue),
153
+ arg2: T.type_parameter(:ThirdValue),
154
+ arg3: T.type_parameter(:FourthValue)
155
+ ).returns(
156
+ T.type_parameter(:ResultValue)
157
+ )
158
+ ).returns(
159
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)]
160
+ )
161
+ }
162
+ def self.combine_four(first, second, third, fourth, &blk)
163
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)].new do
164
+ blk.call(first.value, second.value, third.value, fourth.value)
165
+ end
166
+ end
167
+
168
+ sig {
169
+ type_parameters(
170
+ :FirstValue,
171
+ :SecondValue,
172
+ :ThirdValue,
173
+ :FourthValue,
174
+ :FifthValue,
175
+ :ResultValue
176
+ ).params(
177
+ first: ::Mayak::Lazy[T.type_parameter(:FirstValue)],
178
+ second: ::Mayak::Lazy[T.type_parameter(:SecondValue)],
179
+ third: ::Mayak::Lazy[T.type_parameter(:ThirdValue)],
180
+ fourth: ::Mayak::Lazy[T.type_parameter(:FourthValue)],
181
+ fifth: ::Mayak::Lazy[T.type_parameter(:FifthValue)],
182
+ blk: T.proc.params(
183
+ arg0: T.type_parameter(:FirstValue),
184
+ arg1: T.type_parameter(:SecondValue),
185
+ arg2: T.type_parameter(:ThirdValue),
186
+ arg3: T.type_parameter(:FourthValue),
187
+ arg4: T.type_parameter(:FifthValue)
188
+ ).returns(
189
+ T.type_parameter(:ResultValue)
190
+ )
191
+ ).returns(
192
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)]
193
+ )
194
+ }
195
+ def self.combine_five(first, second, third, fourth, fifth, &blk)
196
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)].new do
197
+ blk.call(first.value, second.value, third.value, fourth.value, fifth.value)
198
+ end
199
+ end
200
+
201
+ sig {
202
+ type_parameters(
203
+ :FirstValue,
204
+ :SecondValue,
205
+ :ThirdValue,
206
+ :FourthValue,
207
+ :FifthValue,
208
+ :SixthValue,
209
+ :ResultValue
210
+ ).params(
211
+ first: ::Mayak::Lazy[T.type_parameter(:FirstValue)],
212
+ second: ::Mayak::Lazy[T.type_parameter(:SecondValue)],
213
+ third: ::Mayak::Lazy[T.type_parameter(:ThirdValue)],
214
+ fourth: ::Mayak::Lazy[T.type_parameter(:FourthValue)],
215
+ fifth: ::Mayak::Lazy[T.type_parameter(:FifthValue)],
216
+ sixth: ::Mayak::Lazy[T.type_parameter(:SixthValue)],
217
+ blk: T.proc.params(
218
+ arg0: T.type_parameter(:FirstValue),
219
+ arg1: T.type_parameter(:SecondValue),
220
+ arg2: T.type_parameter(:ThirdValue),
221
+ arg3: T.type_parameter(:FourthValue),
222
+ arg4: T.type_parameter(:FifthValue),
223
+ arg5: T.type_parameter(:SixthValue)
224
+ ).returns(
225
+ T.type_parameter(:ResultValue)
226
+ )
227
+ ).returns(
228
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)]
229
+ )
230
+ }
231
+ def self.combine_six(first, second, third, fourth, fifth, sixth, &blk)
232
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)].new do
233
+ blk.call(first.value, second.value, third.value, fourth.value, fifth.value, sixth.value)
234
+ end
235
+ end
236
+
237
+ sig {
238
+ type_parameters(
239
+ :FirstValue,
240
+ :SecondValue,
241
+ :ThirdValue,
242
+ :FourthValue,
243
+ :FifthValue,
244
+ :SixthValue,
245
+ :SeventhValue,
246
+ :ResultValue
247
+ ).params(
248
+ first: ::Mayak::Lazy[T.type_parameter(:FirstValue)],
249
+ second: ::Mayak::Lazy[T.type_parameter(:SecondValue)],
250
+ third: ::Mayak::Lazy[T.type_parameter(:ThirdValue)],
251
+ fourth: ::Mayak::Lazy[T.type_parameter(:FourthValue)],
252
+ fifth: ::Mayak::Lazy[T.type_parameter(:FifthValue)],
253
+ sixth: ::Mayak::Lazy[T.type_parameter(:SixthValue)],
254
+ seventh: ::Mayak::Lazy[T.type_parameter(:SeventhValue)],
255
+ blk: T.proc.params(
256
+ arg0: T.type_parameter(:FirstValue),
257
+ arg1: T.type_parameter(:SecondValue),
258
+ arg2: T.type_parameter(:ThirdValue),
259
+ arg3: T.type_parameter(:FourthValue),
260
+ arg4: T.type_parameter(:FifthValue),
261
+ arg5: T.type_parameter(:SixthValue),
262
+ arg6: T.type_parameter(:SeventhValue),
263
+ ).returns(
264
+ T.type_parameter(:ResultValue)
265
+ )
266
+ ).returns(
267
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)]
268
+ )
269
+ }
270
+ def self.combine_seven(first, second, third, fourth, fifth, sixth, seventh, &blk)
271
+ ::Mayak::Lazy[T.type_parameter(:ResultValue)].new do
272
+ blk.call(first.value, second.value, third.value, fourth.value, fifth.value, sixth.value, seventh.value)
273
+ end
274
+ end
275
+
276
+ sig {
277
+ type_parameters(
278
+ :Value,
279
+ :Result
280
+ ).params(
281
+ lazies: T::Array[::Mayak::Lazy[T.type_parameter(:Value)]],
282
+ initial: T.type_parameter(:Result),
283
+ blk: T.proc.params(arg0: T.type_parameter(:Result), arg1: T.type_parameter(:Value)).returns(T.type_parameter(:Result))
284
+ ).returns(
285
+ ::Mayak::Lazy[T.type_parameter(:Result)]
286
+ )
287
+ }
288
+ def self.combine_many(lazies, initial, &blk)
289
+ ::Mayak::Lazy[T.type_parameter(:Result)].new do
290
+ lazies.reduce(initial) { |acc, element| blk.call(acc, element.value) }
291
+ end
292
+ end
293
+
294
+ sig {
295
+ type_parameters(
296
+ :Value
297
+ ).params(
298
+ lazies: T::Array[::Mayak::Lazy[T.type_parameter(:Value)]]
299
+ ).returns(
300
+ ::Mayak::Lazy[T::Array[T.type_parameter(:Value)]]
301
+ )
302
+ }
303
+ def self.sequence(lazies)
304
+ combine_many(lazies, []) { |acc, element| acc.concat([element]) }
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Mayak
5
+ module Validations
6
+ class Rule
7
+ extend T::Sig
8
+ extend T::Helpers
9
+ extend T::Generic
10
+
11
+ Value = type_member
12
+ Error = type_member
13
+
14
+ sig {
15
+ params(
16
+ blk: T.proc.params(arg0: Value).returns(::Mayak::ValidationResult[Error])
17
+ ).void
18
+ }
19
+ def initialize(&blk)
20
+ @blk = T.let(@blk, T.proc.params(arg0: Value).returns(::Mayak::ValidationResult[Error]))
21
+ end
22
+
23
+ sig { params(value: Value).returns(::Mayak::ValidationResult[Error]) }
24
+ def check(value)
25
+ @blk.call(value)
26
+ end
27
+
28
+ sig {
29
+ type_parameters(:NewError)
30
+ .params(blk: T.proc.params(arg0: Value).returns(T.type_parameter(:NewError)))
31
+ .returns(Rule[Value, T.type_parameter(:NewError)])
32
+ }
33
+ def error_from_value(&blk)
34
+ Rule[Value, T.type_parameter(:NewError)].new do |checked|
35
+ result = check(checked)
36
+
37
+ case result
38
+ when ::Mayak::ValidationResult::Valid
39
+ ::Mayak::ValidationResult::Valid[T.type_parameter(:NewError)].new
40
+ when ::Mayak::ValidationResult::Invalid
41
+ ::Mayak::ValidationResult::Invalid[T.type_parameter(:NewError)].new(errors: [blk.call(checked)])
42
+ else
43
+ T.absurd(result)
44
+ end
45
+ end
46
+ end
47
+
48
+ sig {
49
+ type_parameters(:NewError)
50
+ .params(new_error: T.type_parameter(:NewError))
51
+ .returns(Rule[Value, T.type_parameter(:NewError)])
52
+ }
53
+ def error(new_error)
54
+ error_from_value { |_| new_error }
55
+ end
56
+
57
+ sig {
58
+ params(another: Rule[Value, Error]).returns(Rule[Value, Error])
59
+ }
60
+ def any(another)
61
+ Rule[Value, Error].new do |checked|
62
+ first_result = check(checked)
63
+ case first_result
64
+ when ::Mayak::ValidationResult::Valid
65
+ another.check(checked)
66
+ when ::Mayak::ValidationResult::Invalid
67
+ first_result
68
+ else
69
+ T.absurd(first_result)
70
+ end
71
+ end
72
+ end
73
+
74
+ alias | any
75
+
76
+ sig {
77
+ params(another: Rule[Value, Error]).returns(Rule[Value, Error])
78
+ }
79
+ def both(another)
80
+ Rule[Value, Error].new do |checked|
81
+ first_result = check(checked)
82
+ case first_result
83
+ when ::Mayak::ValidationResult::Valid
84
+ another.check(checked)
85
+ when ::Mayak::ValidationResult::Invalid
86
+ second_result = another.check(checked)
87
+ case second_result
88
+ when ::Mayak::ValidationResult::Valid
89
+ first_result
90
+ when ::Mayak::ValidationResult::Invalid
91
+ ::Mayak::ValidationResult::Invalid.new(errors: first_result.errors + second_result.errors)
92
+ else
93
+ T.absurd(first_result)
94
+ end
95
+ else
96
+ T.absurd(first_result)
97
+ end
98
+ end
99
+ end
100
+
101
+ alias & both
102
+
103
+ sig { params(key: Symbol).returns(Rule[Value, [Symbol, Error]]) }
104
+ def with_key(key)
105
+ Rule[Value, [Symbol, Error]].new do |checked|
106
+ result = check(checked)
107
+ case result
108
+ when ::Mayak::ValidationResult::Valid
109
+ ::Mayak::ValidationResult::Valid[[Symbol, Error]].new
110
+ when ::Mayak::ValidationResult::Invalid
111
+ ::Mayak::ValidationResult::Invalid[[Symbol, Error]].new(
112
+ errors: result.errors.map { |error| [key, error] }
113
+ )
114
+ else
115
+ T.absurd(result)
116
+ end
117
+ end
118
+ end
119
+
120
+
121
+ sig {
122
+ params(
123
+ value: T.any(Float, Integer)
124
+ ).returns(Rule[T.any(Float, Integer), String])
125
+ }
126
+ def self.greater_than(value)
127
+ Rule[T.any(Float, Integer), String].new do |checked|
128
+ if checked > value
129
+ ::Mayak::ValidationResult::Valid.new
130
+ else
131
+ ::Mayak::ValidationResult::Invalid.new(
132
+ errors: ["Value #{checked} should be greater than the #{value}"]
133
+ )
134
+ end
135
+ end
136
+ end
137
+
138
+ sig {
139
+ params(
140
+ value: T.any(Float, Integer)
141
+ ).returns(Rule[T.any(Float, Integer), String])
142
+ }
143
+ def self.less_than(value)
144
+ Rule[T.any(Float, Integer), String].new do |checked|
145
+ if checked < value
146
+ ::Mayak::ValidationResult::Valid.new
147
+ else
148
+ ::Mayak::ValidationResult::Invalid.new(
149
+ errors: ["Value #{checked} should be less than the #{value}"]
150
+ )
151
+ end
152
+ end
153
+ end
154
+
155
+ sig {
156
+ params(
157
+ value: T.any(Float, Integer)
158
+ ).returns(Rule[T.any(Float, Integer), String])
159
+ }
160
+ def self.equal_to(value)
161
+ Rule[T.any(Float, Integer), String].new do |checked|
162
+ if checked == value
163
+ ::Mayak::ValidationResult::Valid.new
164
+ else
165
+ ::Mayak::ValidationResult::Invalid.new(
166
+ errors: ["Value #{checked} should equal to #{value}"]
167
+ )
168
+ end
169
+ end
170
+ end
171
+
172
+ sig {
173
+ params(
174
+ value: T.any(Float, Integer)
175
+ ).returns(Rule[T.any(Float, Integer), String])
176
+ }
177
+ def self.greater_than_or_equal_to(value)
178
+ (greater_than(value) | equal_to(value)).error_from_value do |checked|
179
+ "Value #{checked} should greater than or equal to #{value}"
180
+ end
181
+ end
182
+
183
+ sig {
184
+ params(
185
+ value: T.any(Float, Integer)
186
+ ).returns(Rule[T.any(Float, Integer), String])
187
+ }
188
+ def self.less_than_or_equal_to(value)
189
+ (less_than(value) | equal_to(value)).error_from_value do |checked|
190
+ "Value #{checked} should less than or equal to #{value}"
191
+ end
192
+ end
193
+
194
+ sig {
195
+ params(
196
+ value: T.any(Float, Integer)
197
+ ).returns(Rule[T.any(Float, Integer), String])
198
+ }
199
+ def self.positive(value)
200
+ greater_than(0)
201
+ end
202
+
203
+ sig {
204
+ params(
205
+ value: T.any(Float, Integer)
206
+ ).returns(Rule[T.any(Float, Integer), String])
207
+ }
208
+ def self.negative(value)
209
+ less_than(0)
210
+ end
211
+
212
+ sig {
213
+ returns(Rule[T.any(String, T::Array[T.anything], T::Hash[T.anything, T.anything], String), String])
214
+ }
215
+ def self.not_empty
216
+ Rule[T.any(String, Array, Hash, String), String].new do |checked|
217
+ if checked.empty?
218
+ ::Mayak::ValidationResult::Invalid.new(
219
+ errors: ["Value #{checked} should not be empty equal"]
220
+ )
221
+ else
222
+ ::Mayak::ValidationResult::Valid.new
223
+ end
224
+ end
225
+ end
226
+
227
+ sig {
228
+ type_parameters(:Value, :Error)
229
+ .params(rule: Rule[T.type_parameter(:Value), T.type_parameter(:Error)])
230
+ .returns(Rule[T.nilable(T.type_parameter(:Value)), T.type_parameter(:Error)])
231
+ }
232
+ def self.not_nil(rule)
233
+ Rule[T.nilable(T.type_parameter(:Value)), T.type_parameter(:Error)].new do |checked|
234
+ case checked
235
+ when NilClass
236
+ ::Mayak::ValidationResult::Invalid.new(errors: ["Should not be nil"])
237
+ else
238
+ rule.check(checked)
239
+ end
240
+ end
241
+ end
242
+
243
+ sig {
244
+ type_parameters(:Value, :Error)
245
+ .params(rule: Rule[T.type_parameter(:Value), [Symbol, T.type_parameter(:Error)]])
246
+ .returns(Rule[T.type_parameter(:Value), T::Hash[Symbol, T.type_parameter(:Error)]])
247
+ }
248
+ def self.with_keys_aggregated(rule)
249
+ Rule[T.type_parameter(:Value), T::Hash[Symbol, T.type_parameter(:Error)]].new do |checked|
250
+ result = rule.check(checked)
251
+ case result
252
+ when ::Mayak::ValidationResult::Valid
253
+ ::Mayak::ValidationResult::Valid[T::Hash[Symbol, T.type_parameter(:Error)]].new
254
+ when ::Mayak::ValidationResult::Invalid
255
+ accumulated = result.errors.reduce({}) do |acc, tuple|
256
+ key, error = tuple
257
+ if acc.key?(key)
258
+ acc[key] << error
259
+ else
260
+ acc[key] = [error]
261
+ end
262
+ end
263
+ ::Mayak::ValidationResult::Invalid[T::Hash[Symbol, T.type_parameter(:Error)]].new(
264
+ accumulated
265
+ )
266
+ else
267
+ T.absurd(result)
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Mayak
5
+ module Validations
6
+ class Contract
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ Value = type_member
11
+ Error = type_member
12
+
13
+ sig { returns(T::Set[Rule[Value, [Symbol, Error]]]) }
14
+ attr_reader :rule_set
15
+
16
+ sig { params(rule_set: T::Set[Rule[Value, [Symbol, Error]]]).void }
17
+ def initialize(rule_set = Set.new)
18
+ @rule_set = T.let(rule_set, T::Set[Rule[Value, [Symbol, Error]]])
19
+ end
20
+
21
+ sig {
22
+ type_parameters(:MappedValue)
23
+ .params(
24
+ rule: Rule[T.type_parameter(:MappedValue), Error],
25
+ key: Symbol,
26
+ blk: T.proc.params(arg0: Value).returns(T.type_parameter(:MappedValue))
27
+ ).returns(Contract[Value, Error])
28
+ }
29
+ def validate(rule, key:, &blk)
30
+ rule = Rule[Value, Error].new do |checked|
31
+ rule.check(blk.call(checked))
32
+ end
33
+ Contract.new(rule_set.add(rule.with_key(key)))
34
+ end
35
+
36
+ sig { params(value: Value).returns(::Mayak::ValidationResult[T::Hash[Symbol, Error]]) }
37
+ def check(value)
38
+ initial = T.let(
39
+ ::Mayak::ValidationResult::Valid[T::Hash[Symbol, Error]].new,
40
+ ::Mayak::ValidationResult[T::Hash[Symbol, Error]]
41
+ )
42
+ rule_set.reduce(initial) do |aggreated_result, rule|
43
+ current_result = Rule.with_keys_aggregated(rule).check(value)
44
+ case aggreated_result
45
+ when ::Mayak::ValidationResult::Valid
46
+ current_result
47
+ when ::Mayak::ValidationResult::Invalid
48
+ case current_result
49
+ when ::Mayak::ValidationResult::Valid
50
+ aggreated_result
51
+ when ::Mayak::ValidationResult::Invalid
52
+ ::Mayak::ValidationResult::Invalid[T::Hash[Symbol, Error]].new(errors: [ ])
53
+ else
54
+ T.absurd(current_result)
55
+ end
56
+ else
57
+ T.absurd(aggreated_result)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+
data/lib/mayak/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # typed: strict
3
3
 
4
4
  module Mayak
5
- VERSION = T.let("0.0.1", String)
5
+ VERSION = T.let("0.1.0", String)
6
6
  end
data/lib/mayak.rb CHANGED
@@ -16,6 +16,7 @@ require_relative 'mayak/weak_ref'
16
16
  require_relative 'mayak/decoder'
17
17
  require_relative 'mayak/encoder'
18
18
  require_relative 'mayak/hash_serializable'
19
+ require_relative 'mayak/lazy'
19
20
 
20
21
  require_relative 'mayak/caching/unbounded_cache'
21
22
  require_relative 'mayak/caching/lru_cache'
data/mayak.gemspec CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "mayak"
7
- spec.version = "0.0.15"
7
+ spec.version = "0.1.0"
8
8
  spec.summary = "Set of fully typed utility classes and interfaces integrated with Sorbet."
9
9
  spec.description = spec.summary
10
10
  spec.authors = ["Daniil Bober"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mayak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.15
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniil Bober
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-21 00:00:00.000000000 Z
11
+ date: 2024-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sorbet-runtime
@@ -100,6 +100,7 @@ executables: []
100
100
  extensions: []
101
101
  extra_rdoc_files: []
102
102
  files:
103
+ - LICENSE
103
104
  - README.md
104
105
  - lib/mayak.rb
105
106
  - lib/mayak/cache.rb
@@ -124,6 +125,7 @@ files:
124
125
  - lib/mayak/http/verb.rb
125
126
  - lib/mayak/json.rb
126
127
  - lib/mayak/json/encoder.rb
128
+ - lib/mayak/lazy.rb
127
129
  - lib/mayak/monads/README.md
128
130
  - lib/mayak/monads/maybe.rb
129
131
  - lib/mayak/monads/result.rb
@@ -133,6 +135,8 @@ files:
133
135
  - lib/mayak/predicates/rule.rb
134
136
  - lib/mayak/random.rb
135
137
  - lib/mayak/validation_result.rb
138
+ - lib/mayak/validations/rule.rb
139
+ - lib/mayak/validations/validation.rb
136
140
  - lib/mayak/version.rb
137
141
  - lib/mayak/weak_ref.rb
138
142
  - mayak.gemspec