mayak 0.0.2
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/README.md +33 -0
- data/lib/mayak/cache.rb +35 -0
- data/lib/mayak/caching/README.md +100 -0
- data/lib/mayak/caching/lru_cache.rb +76 -0
- data/lib/mayak/caching/unbounded_cache.rb +51 -0
- data/lib/mayak/collections/README.md +62 -0
- data/lib/mayak/collections/priority_queue.rb +155 -0
- data/lib/mayak/collections/queue.rb +73 -0
- data/lib/mayak/function.rb +46 -0
- data/lib/mayak/http/README.md +105 -0
- data/lib/mayak/http/client.rb +17 -0
- data/lib/mayak/http/codec.rb +23 -0
- data/lib/mayak/http/decoder.rb +43 -0
- data/lib/mayak/http/encoder.rb +55 -0
- data/lib/mayak/http/request.rb +110 -0
- data/lib/mayak/http/response.rb +29 -0
- data/lib/mayak/http/verb.rb +20 -0
- data/lib/mayak/json.rb +33 -0
- data/lib/mayak/monads/README.md +1409 -0
- data/lib/mayak/monads/maybe.rb +449 -0
- data/lib/mayak/monads/result.rb +574 -0
- data/lib/mayak/monads/try.rb +619 -0
- data/lib/mayak/numeric.rb +44 -0
- data/lib/mayak/predicates/rule.rb +74 -0
- data/lib/mayak/random.rb +15 -0
- data/lib/mayak/version.rb +6 -0
- data/lib/mayak/weak_ref.rb +37 -0
- data/lib/mayak.rb +35 -0
- data/mayak.gemspec +21 -0
- metadata +127 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
# Http
|
2
|
+
|
3
|
+
This module contains abstraction for HTTP interactions. It provides data classes that models HTTP requests and response as well interfaces for http client and codecs.
|
4
|
+
|
5
|
+
#### Verb
|
6
|
+
|
7
|
+
Enumaration that encodes an HTTP verb.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
get_verb = Mayak::Http::Verb::Get
|
11
|
+
post_verb = Mayak::Http::Verb::Post
|
12
|
+
head_verb = Mayak::Http::Verb::Head
|
13
|
+
put_verb = Mayak::Http::Verb::Put
|
14
|
+
patch_verb = Mayak::Http::Verb::Patch
|
15
|
+
delete_verb = Mayak::Http::Verb::Delete
|
16
|
+
connect_verb = Mayak::Http::Verb::Connect
|
17
|
+
options_verb = Mayak::Http::Verb::Options
|
18
|
+
trace_verb = Mayak::Http::Verb::Trace
|
19
|
+
```
|
20
|
+
|
21
|
+
#### Request
|
22
|
+
|
23
|
+
`Mayak::Http::Request` is a datastructure that encodes an HTTP request.
|
24
|
+
```ruby
|
25
|
+
# to build an HTTP request verb and URI should be provided, as well as optional headers hash and body
|
26
|
+
request = Mayak::Http::Request.new(
|
27
|
+
verb: Mayak::Http::Verb::Put,
|
28
|
+
url: URI.parse("https://www.foobar.com/users/update"),
|
29
|
+
headers: { "content-type" => "application/json" },
|
30
|
+
body: """{ id: 100, name: "Daniil" })"""
|
31
|
+
)
|
32
|
+
|
33
|
+
# to build request helper constructor methods can be used
|
34
|
+
get_request = Mayak::Http::Request.get(url: URI.parse("https://www.foobar.com"))
|
35
|
+
```
|
36
|
+
|
37
|
+
#### Response
|
38
|
+
|
39
|
+
`Mayak::Http::Response` is a datastructure that encodes an HTTP request. It contains status, and optional headers and body:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
response = Mayak::Http::Response.new(
|
43
|
+
status: 200
|
44
|
+
headers: { "content-type" => "application/json" },
|
45
|
+
body: """{ id: 100, name: "Daniil" }"""
|
46
|
+
)
|
47
|
+
```
|
48
|
+
|
49
|
+
#### Client
|
50
|
+
|
51
|
+
Interface that encodes an HTTP client. The interface is very simple and consists from one method that receives a request and returns a `Try` monad containg response:
|
52
|
+
```ruby
|
53
|
+
module Mayak
|
54
|
+
module Http
|
55
|
+
module Client
|
56
|
+
extend T::Sig
|
57
|
+
extend T::Generic
|
58
|
+
|
59
|
+
interface!
|
60
|
+
|
61
|
+
sig { abstract.params(request: Http::Request).returns(Mayak::Monads::Try[Mayak::Http::Response]) }
|
62
|
+
def send_request(request)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
You can implement this interface using a specific library:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class FaradayClient
|
73
|
+
extend T::Sig
|
74
|
+
|
75
|
+
include Mayak::Http::Client
|
76
|
+
|
77
|
+
include Mayak::Monads::Try::Mixin
|
78
|
+
|
79
|
+
sig { params(config_blk: T.nilable(T.proc.params(connection: Faraday::Connection).void)).void }
|
80
|
+
def initialize(&config_blk)
|
81
|
+
@faraday_instance = T.let(
|
82
|
+
config_blk.nil? ? Faraday.new : Faraday.new(&@config_blk),
|
83
|
+
Faraday
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
sig { override.params(request: Mayak::Http::Request).returns(Mayak::Monads::Try[Mayak::Http::Response]) }
|
88
|
+
def send_request(request)
|
89
|
+
Try do
|
90
|
+
faraday_response = @faraday_instance.run_request(
|
91
|
+
verb.serialize.downcase.to_sym,
|
92
|
+
request.url,
|
93
|
+
request.body,
|
94
|
+
request.headers
|
95
|
+
)
|
96
|
+
|
97
|
+
Mayak::Http::Response.new(
|
98
|
+
status: T.must(faraday_response.status.to_i),
|
99
|
+
headers: faraday_response.headers || {},
|
100
|
+
body: faraday_response.body || ""
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module Mayak
|
5
|
+
module Http
|
6
|
+
module Client
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Generic
|
9
|
+
|
10
|
+
interface!
|
11
|
+
|
12
|
+
sig { abstract.params(request: Http::Request).returns(Mayak::Monads::Try[Mayak::Http::Response]) }
|
13
|
+
def send_request(request)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'encoder'
|
5
|
+
require_relative 'decoder'
|
6
|
+
|
7
|
+
module Mayak
|
8
|
+
module Http
|
9
|
+
module Codec
|
10
|
+
extend T::Sig
|
11
|
+
extend T::Generic
|
12
|
+
extend T::Helpers
|
13
|
+
|
14
|
+
interface!
|
15
|
+
|
16
|
+
include Mayak::Http::Encoder
|
17
|
+
include Mayak::Http::Decoder
|
18
|
+
|
19
|
+
RequestEntity = type_member
|
20
|
+
ResponseEntity = type_member
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Mayak
|
5
|
+
module Http
|
6
|
+
module Decoder
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Generic
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
interface!
|
12
|
+
|
13
|
+
ResponseEntity = type_member
|
14
|
+
|
15
|
+
sig {
|
16
|
+
abstract
|
17
|
+
.params(response: Mayak::Http::Response)
|
18
|
+
.returns(Mayak::Monads::Try[ResponseEntity])
|
19
|
+
}
|
20
|
+
def decode(response)
|
21
|
+
end
|
22
|
+
|
23
|
+
class IdentityDecoder
|
24
|
+
extend T::Sig
|
25
|
+
extend T::Generic
|
26
|
+
extend T::Helpers
|
27
|
+
|
28
|
+
include ::Mayak::Http::Decoder
|
29
|
+
|
30
|
+
ResponseEntity = type_member { { fixed: ::Mayak::Http::Response } }
|
31
|
+
|
32
|
+
sig {
|
33
|
+
override
|
34
|
+
.params(response: Mayak::Http::Response)
|
35
|
+
.returns(Mayak::Monads::Try[ResponseEntity])
|
36
|
+
}
|
37
|
+
def decode(response)
|
38
|
+
Mayak::Monads::Try::Success[ResponseEntity].new(response)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Mayak
|
5
|
+
module Http
|
6
|
+
module Encoder
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Generic
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
interface!
|
12
|
+
|
13
|
+
RequestEntity = type_member
|
14
|
+
|
15
|
+
sig { abstract.params(entity: RequestEntity).returns(Mayak::Http::Request) }
|
16
|
+
def encode(entity)
|
17
|
+
end
|
18
|
+
|
19
|
+
class IdentityEncoder
|
20
|
+
extend T::Sig
|
21
|
+
extend T::Generic
|
22
|
+
extend T::Helpers
|
23
|
+
|
24
|
+
include ::Mayak::Http::Encoder
|
25
|
+
|
26
|
+
RequestEntity = type_member { { fixed: ::Mayak::Http::Request } }
|
27
|
+
|
28
|
+
sig { override.params(entity: RequestEntity).returns(Mayak::Http::Request) }
|
29
|
+
def encode(entity)
|
30
|
+
entity
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class FromFunction
|
35
|
+
extend T::Sig
|
36
|
+
extend T::Generic
|
37
|
+
extend T::Helpers
|
38
|
+
|
39
|
+
include ::Mayak::Http::Encoder
|
40
|
+
|
41
|
+
RequestEntity = type_member
|
42
|
+
|
43
|
+
sig { params(function: Mayak::Function[RequestEntity, Mayak::Http::Request]).void }
|
44
|
+
def initialize(function)
|
45
|
+
@function = T.let(function, Mayak::Function[RequestEntity, Mayak::Http::Request])
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { override.params(entity: RequestEntity).returns(Mayak::Http::Request) }
|
49
|
+
def encode(entity)
|
50
|
+
@function.call(entity)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require 'uri'
|
5
|
+
require_relative 'verb'
|
6
|
+
|
7
|
+
module Mayak
|
8
|
+
module Http
|
9
|
+
class Request < T::Struct
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
const :verb, Mayak::Http::Verb
|
13
|
+
const :url, URI
|
14
|
+
const :headers, T::Hash[String, String], default: {}
|
15
|
+
const :body, T.nilable(String)
|
16
|
+
|
17
|
+
CONTENT_TYPE_HEADER = T.let("Content-Type", String)
|
18
|
+
|
19
|
+
sig(:final) { params(other: Mayak::Http::Request).returns(T::Boolean) }
|
20
|
+
def ==(other)
|
21
|
+
verb == other.verb && url.to_s == other.url.to_s && headers && other.headers && body == other.body
|
22
|
+
end
|
23
|
+
|
24
|
+
sig(:final) { params(other: Mayak::Http::Request).returns(T::Boolean) }
|
25
|
+
def eql?(other)
|
26
|
+
self == other
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { returns(Integer) }
|
30
|
+
def hash
|
31
|
+
[verb, url.to_s, headers, body].hash
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { returns(T.nilable(String)) }
|
35
|
+
def content_type
|
36
|
+
headers[CONTENT_TYPE_HEADER]
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { params(type: String).returns(Mayak::Http::Request) }
|
40
|
+
def with_content_type(type)
|
41
|
+
Mayak::Http::Request.new(
|
42
|
+
verb: verb,
|
43
|
+
url: url,
|
44
|
+
headers: headers.merge(CONTENT_TYPE_HEADER => type),
|
45
|
+
body: body
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(url: URI, headers: T::Hash[String, String], body: T.nilable(String)).returns(Mayak::Http::Request) }
|
50
|
+
def self.get(url:, headers: {}, body: nil)
|
51
|
+
Mayak::Http::Request.new(
|
52
|
+
verb: Mayak::Http::Verb::Get,
|
53
|
+
url: url,
|
54
|
+
headers: headers,
|
55
|
+
body: body
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(url: URI, headers: T::Hash[String, String], body: T.nilable(String)).returns(Mayak::Http::Request) }
|
60
|
+
def self.head(url:, headers: {}, body: nil)
|
61
|
+
Mayak::Http::Request.new(
|
62
|
+
verb: Mayak::Http::Verb::Head,
|
63
|
+
url: url,
|
64
|
+
headers: headers,
|
65
|
+
body: body
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { params(url: URI, headers: T::Hash[String, String], body: T.nilable(String)).returns(Mayak::Http::Request) }
|
70
|
+
def self.post(url:, headers: {}, body: nil)
|
71
|
+
Mayak::Http::Request.new(
|
72
|
+
verb: Mayak::Http::Verb::Post,
|
73
|
+
url: url,
|
74
|
+
headers: headers,
|
75
|
+
body: body
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
sig { params(url: URI, headers: T::Hash[String, String], body: T.nilable(String)).returns(Mayak::Http::Request) }
|
80
|
+
def self.put(url:, headers: {}, body: nil)
|
81
|
+
Mayak::Http::Request.new(
|
82
|
+
verb: Mayak::Http::Verb::Put,
|
83
|
+
url: url,
|
84
|
+
headers: headers,
|
85
|
+
body: body
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { params(url: URI, headers: T::Hash[String, String], body: T.nilable(String)).returns(Mayak::Http::Request) }
|
90
|
+
def self.patch(url:, headers: {}, body: nil)
|
91
|
+
Mayak::Http::Request.new(
|
92
|
+
verb: Mayak::Http::Verb::Patch,
|
93
|
+
url: url,
|
94
|
+
headers: headers,
|
95
|
+
body: body
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { params(url: URI, headers: T::Hash[String, String], body: T.nilable(String)).returns(Mayak::Http::Request) }
|
100
|
+
def self.delete(url:, headers: {}, body: nil)
|
101
|
+
Mayak::Http::Request.new(
|
102
|
+
verb: Mayak::Http::Verb::Delete,
|
103
|
+
url: url,
|
104
|
+
headers: headers,
|
105
|
+
body: body
|
106
|
+
)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require "set"
|
5
|
+
|
6
|
+
module Mayak
|
7
|
+
module Http
|
8
|
+
class Response < T::Struct
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
SuccessfulStatuses = T.let(::Set.new(200..299).freeze, T::Set[Integer])
|
12
|
+
StatusesWithoutBody = T.let(::Set.new([204, 304]).freeze, T::Set[Integer])
|
13
|
+
|
14
|
+
const :status, Integer
|
15
|
+
const :headers, T::Hash[String, String], default: {}
|
16
|
+
const :body, String, default: ""
|
17
|
+
|
18
|
+
sig { returns(T::Boolean) }
|
19
|
+
def success?
|
20
|
+
SuccessfulStatuses.include?(status)
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { returns(T::Boolean) }
|
24
|
+
def failure?
|
25
|
+
!success?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module Mayak
|
5
|
+
module Http
|
6
|
+
class Verb < T::Enum
|
7
|
+
enums do
|
8
|
+
Get = new("GET")
|
9
|
+
Post = new("POST")
|
10
|
+
Head = new("HEAD")
|
11
|
+
Put = new("PUT")
|
12
|
+
Patch = new("PATCH")
|
13
|
+
Delete = new("DELETE")
|
14
|
+
Connect = new("CONNECT")
|
15
|
+
Options = new("OPTIONS")
|
16
|
+
Trace = new("TRACE")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/mayak/json.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module Mayak
|
5
|
+
module Json
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
JsonType = T.type_alias {
|
9
|
+
T.any(
|
10
|
+
T::Array[T.untyped],
|
11
|
+
T::Hash[T.untyped, T.untyped],
|
12
|
+
String,
|
13
|
+
Integer,
|
14
|
+
Float
|
15
|
+
)
|
16
|
+
}
|
17
|
+
|
18
|
+
class ParsingError < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(string: String).returns(Mayak::Monads::Try[JsonType]) }
|
22
|
+
def self.parse(string)
|
23
|
+
Mayak::Monads::Try::Success.new(JSON.parse(string))
|
24
|
+
rescue JSON::ParserError => e
|
25
|
+
Mayak::Monads::Try::Failure.new(ParsingError.new(e.message))
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { params(string: String).returns(JsonType) }
|
29
|
+
def self.parse_unsafe!(string)
|
30
|
+
JSON.parse(string)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|