mayak 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|