sofia 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/.byebug_history +6 -0
- data/.rubocop.yml +179 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +21 -0
- data/Rakefile +16 -0
- data/lib/sofia/adapter/net_http.rb +67 -0
- data/lib/sofia/adapter.rb +20 -0
- data/lib/sofia/client.rb +55 -0
- data/lib/sofia/error/argument_error.rb +8 -0
- data/lib/sofia/error/connection_failed.rb +8 -0
- data/lib/sofia/error/invalid_json.rb +8 -0
- data/lib/sofia/error/ssl_error.rb +8 -0
- data/lib/sofia/error/timeout_error.rb +8 -0
- data/lib/sofia/error.rb +14 -0
- data/lib/sofia/helper.rb +9 -0
- data/lib/sofia/request.rb +56 -0
- data/lib/sofia/response.rb +57 -0
- data/lib/sofia/sorbet_types.rb +19 -0
- data/lib/sofia/types/adapter.rb +39 -0
- data/lib/sofia/types/base_url.rb +33 -0
- data/lib/sofia/types/body.rb +60 -0
- data/lib/sofia/types/headers.rb +56 -0
- data/lib/sofia/types/params.rb +56 -0
- data/lib/sofia/types/path.rb +28 -0
- data/lib/sofia/types.rb +13 -0
- data/lib/sofia/version.rb +5 -0
- data/lib/sofia.rb +25 -0
- data/sig/sofia.rbs +4 -0
- data/sorbet/config +6 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activesupport.rbi +495 -0
- data/sorbet/rbi/annotations/minitest.rbi +120 -0
- data/sorbet/rbi/annotations/mocha.rbi +34 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/dsl/.gitattributes +1 -0
- data/sorbet/rbi/dsl/active_support/callbacks.rbi +21 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/activesupport@8.1.2.rbi +20360 -0
- data/sorbet/rbi/gems/ast@2.4.3.rbi +585 -0
- data/sorbet/rbi/gems/base64@0.3.0.rbi +545 -0
- data/sorbet/rbi/gems/benchmark@0.4.1.rbi +619 -0
- data/sorbet/rbi/gems/bigdecimal@4.0.1.rbi +239 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.3.6.rbi +11674 -0
- data/sorbet/rbi/gems/connection_pool@3.0.2.rbi +9 -0
- data/sorbet/rbi/gems/date@3.4.1.rbi +75 -0
- data/sorbet/rbi/gems/drb@2.2.3.rbi +1661 -0
- data/sorbet/rbi/gems/erb@5.0.2.rbi +878 -0
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
- data/sorbet/rbi/gems/factory_bot@6.5.6.rbi +2748 -0
- data/sorbet/rbi/gems/i18n@1.14.8.rbi +2359 -0
- data/sorbet/rbi/gems/io-console@0.8.1.rbi +9 -0
- data/sorbet/rbi/gems/json@2.12.2.rbi +2051 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi +9 -0
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +86 -0
- data/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
- data/sorbet/rbi/gems/minitest@5.25.5.rbi +1547 -0
- data/sorbet/rbi/gems/mocha@3.0.1.rbi +28 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
- data/sorbet/rbi/gems/openssl@4.0.0.rbi +1908 -0
- data/sorbet/rbi/gems/parallel@1.27.0.rbi +291 -0
- data/sorbet/rbi/gems/parser@3.3.8.0.rbi +5535 -0
- data/sorbet/rbi/gems/pp@0.6.2.rbi +368 -0
- data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +477 -0
- data/sorbet/rbi/gems/prism@1.5.1.rbi +42049 -0
- data/sorbet/rbi/gems/psych@5.2.6.rbi +2469 -0
- data/sorbet/rbi/gems/racc@1.8.1.rbi +160 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
- data/sorbet/rbi/gems/rake@13.2.1.rbi +3033 -0
- data/sorbet/rbi/gems/rbi@0.3.3.rbi +6742 -0
- data/sorbet/rbi/gems/rbs@3.9.5.rbi +6978 -0
- data/sorbet/rbi/gems/rdoc@6.14.2.rbi +12688 -0
- data/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +3795 -0
- data/sorbet/rbi/gems/reline@0.6.2.rbi +2441 -0
- data/sorbet/rbi/gems/rexml@3.4.1.rbi +5240 -0
- data/sorbet/rbi/gems/rubocop-ast@1.45.1.rbi +7721 -0
- data/sorbet/rbi/gems/rubocop@1.76.1.rbi +62517 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
- data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +9 -0
- data/sorbet/rbi/gems/securerandom@0.4.1.rbi +75 -0
- data/sorbet/rbi/gems/spoom@1.6.3.rbi +6985 -0
- data/sorbet/rbi/gems/stringio@3.1.7.rbi +9 -0
- data/sorbet/rbi/gems/tapioca@0.16.11.rbi +3628 -0
- data/sorbet/rbi/gems/thor@1.4.0.rbi +4399 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5918 -0
- data/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
- data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +251 -0
- data/sorbet/rbi/gems/uri@1.1.1.rbi +2405 -0
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
- data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
- data/sorbet/rbi/shims/byebug.rbi +6 -0
- data/sorbet/rbi/shims/client.rbi +22 -0
- data/sorbet/rbi/todo.rbi +7 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- metadata +137 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
class Response
|
|
6
|
+
|
|
7
|
+
#: Integer
|
|
8
|
+
attr_reader :status
|
|
9
|
+
|
|
10
|
+
#: Hash[String, String]
|
|
11
|
+
attr_reader :headers
|
|
12
|
+
|
|
13
|
+
#: String?
|
|
14
|
+
attr_reader :raw_body
|
|
15
|
+
|
|
16
|
+
#: Sofia::Request
|
|
17
|
+
attr_reader :request
|
|
18
|
+
|
|
19
|
+
#: (status: Integer, headers: Hash[String, String], raw_body: String?, request: Sofia::Request) -> void
|
|
20
|
+
def initialize(status:, headers:, raw_body:, request:)
|
|
21
|
+
@status = status
|
|
22
|
+
@headers = headers
|
|
23
|
+
@raw_body = raw_body
|
|
24
|
+
@request = request
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#: -> bool
|
|
28
|
+
def success?
|
|
29
|
+
(200..299).include?(@status)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: -> bool
|
|
33
|
+
def failure?
|
|
34
|
+
!success?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#: -> JSONValue?
|
|
38
|
+
def body
|
|
39
|
+
body = raw_body
|
|
40
|
+
return unless body
|
|
41
|
+
|
|
42
|
+
JSON.parse(body)
|
|
43
|
+
rescue JSON::ParserError
|
|
44
|
+
raise Sofia::Error::InvalidJSON
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#: -> bool
|
|
48
|
+
def client_error?
|
|
49
|
+
(400..499).include?(@status)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#: -> bool
|
|
53
|
+
def server_error?
|
|
54
|
+
(500..599).include?(@status)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
# JSONValue represents any valid JSON data
|
|
6
|
+
JSONValue = T.type_alias do
|
|
7
|
+
T.any(
|
|
8
|
+
String,
|
|
9
|
+
Integer,
|
|
10
|
+
Float,
|
|
11
|
+
TrueClass,
|
|
12
|
+
FalseClass,
|
|
13
|
+
NilClass,
|
|
14
|
+
T::Array[T.untyped],
|
|
15
|
+
T::Hash[String, T.untyped],
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
module Types
|
|
6
|
+
class Adapter
|
|
7
|
+
|
|
8
|
+
#: singleton(Sofia::Adapter::Base)
|
|
9
|
+
attr_reader :klass
|
|
10
|
+
|
|
11
|
+
#: (?untyped adapter) -> void
|
|
12
|
+
def initialize(adapter = :net_http)
|
|
13
|
+
name, klass = validate_and_set(adapter || :net_http)
|
|
14
|
+
@name = T.let(name, Symbol)
|
|
15
|
+
@klass = T.let(klass, T.class_of(Sofia::Adapter::Base))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#: -> Symbol
|
|
19
|
+
def to_sym
|
|
20
|
+
@name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
#: (untyped) -> [Symbol, singleton(Sofia::Adapter::Base)]
|
|
26
|
+
def validate_and_set(adapter)
|
|
27
|
+
case adapter&.to_sym
|
|
28
|
+
when :net_http
|
|
29
|
+
[:net_http, Sofia::Adapter::NetHTTP]
|
|
30
|
+
else
|
|
31
|
+
Kernel.raise Sofia::Error::ArgumentError, "unknown adapter #{adapter}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
rescue NoMethodError
|
|
35
|
+
Kernel.raise Sofia::Error::ArgumentError, "unknown adapter #{adapter}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
module Types
|
|
6
|
+
class BaseUrl
|
|
7
|
+
|
|
8
|
+
#: (untyped base_url) -> void
|
|
9
|
+
def initialize(base_url)
|
|
10
|
+
@base_url = validate_and_normalize(base_url).freeze #: String
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
#: -> String
|
|
14
|
+
def to_s
|
|
15
|
+
@base_url
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
#: (untyped) -> String
|
|
21
|
+
def validate_and_normalize(base_url)
|
|
22
|
+
unless base_url.is_a?(String) && !base_url.strip.empty?
|
|
23
|
+
raise Sofia::Error::ArgumentError, 'base_url must be a non-empty String'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
normalized = base_url.chomp('/')
|
|
27
|
+
return normalized if normalized.start_with?('http://', 'https://')
|
|
28
|
+
|
|
29
|
+
"https://#{normalized}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
module Types
|
|
6
|
+
class Body
|
|
7
|
+
|
|
8
|
+
#: Hash[String, untyped]
|
|
9
|
+
attr_reader :body
|
|
10
|
+
|
|
11
|
+
#: (?untyped? body) -> void
|
|
12
|
+
def initialize(body = nil)
|
|
13
|
+
@body = validate_and_normalize(body) #: Hash[String, untyped]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#: -> Hash[String, untyped]
|
|
17
|
+
def to_h
|
|
18
|
+
@body.dup
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
#: (untyped? body) -> Hash[String, untyped]
|
|
24
|
+
def validate_and_normalize(body)
|
|
25
|
+
return {} unless body
|
|
26
|
+
|
|
27
|
+
Kernel.raise Sofia::Error::ArgumentError, 'body must be a Hash' unless body.is_a?(Hash)
|
|
28
|
+
body.each_with_object({}) do |(key, value), acc|
|
|
29
|
+
next if value.nil?
|
|
30
|
+
|
|
31
|
+
acc[key.to_s] =
|
|
32
|
+
case value
|
|
33
|
+
when Hash
|
|
34
|
+
validate_and_normalize(value)
|
|
35
|
+
when Array
|
|
36
|
+
validate_and_normalize_array(value)
|
|
37
|
+
else
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#: (Array[untyped]) -> Array[untyped]
|
|
44
|
+
def validate_and_normalize_array(array)
|
|
45
|
+
array.each_with_object([]) do |value, acc|
|
|
46
|
+
next if value.nil?
|
|
47
|
+
|
|
48
|
+
acc << case value
|
|
49
|
+
when Hash
|
|
50
|
+
validate_and_normalize(value)
|
|
51
|
+
when Array
|
|
52
|
+
validate_and_normalize_array(value)
|
|
53
|
+
else
|
|
54
|
+
value
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
module Types
|
|
6
|
+
class Headers
|
|
7
|
+
|
|
8
|
+
#: (?untyped headers) -> void
|
|
9
|
+
def initialize(headers = {})
|
|
10
|
+
defaults = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
|
|
11
|
+
headers = validate_and_normalize(headers)
|
|
12
|
+
@headers = defaults.merge(headers) #: Hash[String, String]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#: (untyped key) -> void
|
|
16
|
+
def [](key)
|
|
17
|
+
@headers[key.to_s]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#: (untyped key, untyped value) -> void
|
|
21
|
+
def []=(key, value)
|
|
22
|
+
@headers[key.to_s] = value.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: -> Hash[String, String]
|
|
26
|
+
def to_h
|
|
27
|
+
@headers.dup
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#: () { ([String, String]) -> untyped } -> Hash[String, String]
|
|
31
|
+
def each(&block) # rubocop:disable Naming/BlockForwarding
|
|
32
|
+
@headers.each(&block) # rubocop:disable Naming/BlockForwarding
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
#: (untyped) -> Hash[String, String]
|
|
38
|
+
def validate_and_normalize(headers)
|
|
39
|
+
Kernel.raise Sofia::Error::ArgumentError, 'headers must be a Hash' unless headers.is_a?(Hash)
|
|
40
|
+
normalized = {}
|
|
41
|
+
|
|
42
|
+
headers.each do |key, value|
|
|
43
|
+
next if value.nil?
|
|
44
|
+
|
|
45
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
|
46
|
+
Kernel.raise Sofia::Error::ArgumentError, "invalid header value for #{key}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
normalized[key.to_s] = value.to_s
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
normalized
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
module Types
|
|
6
|
+
class Params
|
|
7
|
+
extend T::Sig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
#: (?untyped params) -> void
|
|
11
|
+
def initialize(params = {}) # rubocop:disable Style/OptionHash
|
|
12
|
+
@params = validate_and_normalize(params || {}) #: Hash[String, String]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#: (untyped key) -> void
|
|
16
|
+
def [](key)
|
|
17
|
+
@params[key.to_s]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#: (untyped key, untyped value) -> void
|
|
21
|
+
def []=(key, value)
|
|
22
|
+
@params[key.to_s] = value.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: -> Hash[String, String]
|
|
26
|
+
def to_h
|
|
27
|
+
@params.dup
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#: -> String
|
|
31
|
+
def to_s
|
|
32
|
+
URI.encode_www_form(@params)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
#: (untyped) -> Hash[String, String]
|
|
38
|
+
def validate_and_normalize(headers)
|
|
39
|
+
Kernel.raise Sofia::Error::ArgumentError, 'params must be a Hash' unless headers.is_a?(Hash)
|
|
40
|
+
normalized = {}
|
|
41
|
+
|
|
42
|
+
headers.each do |key, value|
|
|
43
|
+
next if value.nil?
|
|
44
|
+
|
|
45
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
|
46
|
+
Kernel.raise Sofia::Error::ArgumentError, "invalid param value for #{key}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
normalized[key.to_s] = value.to_s
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
normalized
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
module Types
|
|
6
|
+
class Path
|
|
7
|
+
|
|
8
|
+
#: (?untyped path) -> void
|
|
9
|
+
def initialize(path = '/')
|
|
10
|
+
@path = validate_and_normalize(path).freeze #: String
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
#: -> String
|
|
14
|
+
def to_s
|
|
15
|
+
@path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
#: (untyped) -> String
|
|
21
|
+
def validate_and_normalize(path)
|
|
22
|
+
Kernel.raise Sofia::Error::ArgumentError, 'path must be a String' unless path.is_a?(String)
|
|
23
|
+
|
|
24
|
+
path.start_with?('/') ? path : "/#{path}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/sofia/types.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Sofia
|
|
5
|
+
module Types; end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require_relative 'types/adapter'
|
|
9
|
+
require_relative 'types/base_url'
|
|
10
|
+
require_relative 'types/headers'
|
|
11
|
+
require_relative 'types/body'
|
|
12
|
+
require_relative 'types/params'
|
|
13
|
+
require_relative 'types/path'
|
data/lib/sofia.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'sorbet-runtime'
|
|
8
|
+
require 'byebug'
|
|
9
|
+
|
|
10
|
+
require_relative 'sofia/version'
|
|
11
|
+
require_relative 'sofia/request'
|
|
12
|
+
require_relative 'sofia/response'
|
|
13
|
+
require_relative 'sofia/adapter'
|
|
14
|
+
require_relative 'sofia/client'
|
|
15
|
+
require_relative 'sofia/error'
|
|
16
|
+
require_relative 'sofia/types'
|
|
17
|
+
|
|
18
|
+
module Sofia
|
|
19
|
+
class << self
|
|
20
|
+
#: (base_url: untyped, ?adapter: untyped) -> Sofia::Client
|
|
21
|
+
def new(base_url:, adapter: nil)
|
|
22
|
+
Client.new(base_url: base_url, adapter: adapter)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/sig/sofia.rbs
ADDED
data/sorbet/config
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
**/*.rbi linguist-vendored=true
|