f_http_client 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/.rspec +3 -0
- data/.rubocop.yml +121 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +132 -0
- data/LICENSE +21 -0
- data/README.md +126 -0
- data/Rakefile +12 -0
- data/examples/post_find.rb +31 -0
- data/lib/f_http_client/base.rb +114 -0
- data/lib/f_http_client/cache/http_response_analizer.rb +29 -0
- data/lib/f_http_client/cache/key.rb +14 -0
- data/lib/f_http_client/cache/null.rb +13 -0
- data/lib/f_http_client/cache/rails.rb +32 -0
- data/lib/f_http_client/configuration.rb +14 -0
- data/lib/f_http_client/log.rb +38 -0
- data/lib/f_http_client/logger/default.rb +40 -0
- data/lib/f_http_client/logger/null.rb +16 -0
- data/lib/f_http_client/logger/rails.rb +16 -0
- data/lib/f_http_client/parser/response.rb +16 -0
- data/lib/f_http_client/processor/exception.rb +49 -0
- data/lib/f_http_client/processor/response.rb +125 -0
- data/lib/f_http_client/rspec/support/helpers/fake_response.rb +15 -0
- data/lib/f_http_client/rspec/support/helpers.rb +3 -0
- data/lib/f_http_client/rspec/support/support.rb +3 -0
- data/lib/f_http_client/rspec/support.rb +3 -0
- data/lib/f_http_client/rspec.rb +3 -0
- data/lib/f_http_client/service.rb +22 -0
- data/lib/f_http_client/store.rb +37 -0
- data/lib/f_http_client/version.rb +5 -0
- data/lib/f_http_client.rb +34 -0
- data/sig/f_http_client.rbs +4 -0
- metadata +149 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fa009fca4cb5465b7a9c07e71037e15c5dffbbe97804e1b6a7ee2888cbfa771c
|
|
4
|
+
data.tar.gz: '058b8ef192570f144a44c4dac7e7f95aa401923f4176522944421b507b93e7e0'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a80d429fdc6fc7134ffeee8d6d627a87bad579dc4b29c38113e1a927e503cca3174de5d45829a0a39eb535c3c35b1193a2c3971bdad02acbfdc1cf04e305f1bc
|
|
7
|
+
data.tar.gz: e1f5e810e2fb1003fa8d503b7703699c75e2c1dc139062306b6eb1ade158cbdc1fe2273ea0cf76d772858a7cc803d61dfa2f3f4d29622d5103b5a7476f24123b
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require:
|
|
2
|
+
- rubocop-rspec
|
|
3
|
+
- rubocop-performance
|
|
4
|
+
|
|
5
|
+
AllCops:
|
|
6
|
+
TargetRubyVersion: 3.0
|
|
7
|
+
SuggestExtensions: false
|
|
8
|
+
NewCops: enable
|
|
9
|
+
CacheRootDirectory: tmp
|
|
10
|
+
Exclude:
|
|
11
|
+
- 'vendor/**/*'
|
|
12
|
+
- 'bin/**/*'
|
|
13
|
+
- '.git/**/*'
|
|
14
|
+
- 'examples/**/*'
|
|
15
|
+
|
|
16
|
+
##### LAYOUT #####
|
|
17
|
+
|
|
18
|
+
Layout/FirstArrayElementIndentation:
|
|
19
|
+
Enabled: true
|
|
20
|
+
EnforcedStyle: consistent
|
|
21
|
+
|
|
22
|
+
Layout/FirstHashElementIndentation:
|
|
23
|
+
Enabled: true
|
|
24
|
+
EnforcedStyle: consistent
|
|
25
|
+
|
|
26
|
+
Layout/LineLength:
|
|
27
|
+
Max: 120
|
|
28
|
+
AllowedPatterns: ['(\A|\s)#']
|
|
29
|
+
|
|
30
|
+
##### STYLE #####
|
|
31
|
+
|
|
32
|
+
Style/PreferredHashMethods:
|
|
33
|
+
Enabled: true
|
|
34
|
+
Safe: false
|
|
35
|
+
EnforcedStyle: verbose
|
|
36
|
+
|
|
37
|
+
Style/SymbolArray:
|
|
38
|
+
Enabled: true
|
|
39
|
+
EnforcedStyle: percent
|
|
40
|
+
MinSize: 3
|
|
41
|
+
|
|
42
|
+
Style/WordArray:
|
|
43
|
+
Enabled: true
|
|
44
|
+
EnforcedStyle: percent
|
|
45
|
+
MinSize: 3
|
|
46
|
+
|
|
47
|
+
Style/MultilineBlockChain:
|
|
48
|
+
Enabled: false
|
|
49
|
+
|
|
50
|
+
Style/Lambda:
|
|
51
|
+
Enabled: true
|
|
52
|
+
EnforcedStyle: literal
|
|
53
|
+
|
|
54
|
+
Style/LambdaCall:
|
|
55
|
+
Enabled: false
|
|
56
|
+
|
|
57
|
+
Style/Documentation:
|
|
58
|
+
Enabled: false
|
|
59
|
+
|
|
60
|
+
Style/ClassAndModuleChildren:
|
|
61
|
+
Enabled: false
|
|
62
|
+
|
|
63
|
+
##### LINT #####
|
|
64
|
+
|
|
65
|
+
Lint/MissingSuper:
|
|
66
|
+
Enabled: false
|
|
67
|
+
|
|
68
|
+
##### NAMING #####
|
|
69
|
+
|
|
70
|
+
Naming/InclusiveLanguage:
|
|
71
|
+
Enabled: false
|
|
72
|
+
|
|
73
|
+
Naming/PredicateName:
|
|
74
|
+
ForbiddenPrefixes:
|
|
75
|
+
- is_
|
|
76
|
+
- have_
|
|
77
|
+
# allows `has_` predicate
|
|
78
|
+
|
|
79
|
+
###### METRICS #####
|
|
80
|
+
|
|
81
|
+
Metrics/AbcSize:
|
|
82
|
+
Enabled: true
|
|
83
|
+
Max: 22
|
|
84
|
+
|
|
85
|
+
Metrics/BlockLength:
|
|
86
|
+
Enabled: true
|
|
87
|
+
CountComments: false
|
|
88
|
+
Exclude:
|
|
89
|
+
- '**/*.gemspec'
|
|
90
|
+
- 'spec/**/*'
|
|
91
|
+
|
|
92
|
+
Metrics/ClassLength:
|
|
93
|
+
Max: 200
|
|
94
|
+
|
|
95
|
+
Metrics/MethodLength:
|
|
96
|
+
Enabled: true
|
|
97
|
+
CountAsOne: ['array', 'heredoc']
|
|
98
|
+
Max: 20
|
|
99
|
+
|
|
100
|
+
##### RSPEC #####
|
|
101
|
+
|
|
102
|
+
RSpec/ContextWording:
|
|
103
|
+
Prefixes:
|
|
104
|
+
- and
|
|
105
|
+
- but
|
|
106
|
+
- when
|
|
107
|
+
- with
|
|
108
|
+
- without
|
|
109
|
+
|
|
110
|
+
RSpec/ExampleLength:
|
|
111
|
+
Max: 20
|
|
112
|
+
|
|
113
|
+
RSpec/FilePath:
|
|
114
|
+
CustomTransform:
|
|
115
|
+
FHTTPClient: f_http_client
|
|
116
|
+
|
|
117
|
+
RSpec/MultipleMemoizedHelpers:
|
|
118
|
+
Enabled: false
|
|
119
|
+
|
|
120
|
+
RSpec/NestedGroups:
|
|
121
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2023-02-13
|
|
4
|
+
|
|
5
|
+
- Initial release
|
|
6
|
+
- Add Caching strategies;
|
|
7
|
+
- Add Logging strategies;
|
|
8
|
+
- Add Custom parse for JSON responses;
|
|
9
|
+
- Add Response and Exception processors;
|
|
10
|
+
- Add Base client class;
|
|
11
|
+
- Add Basic configuration;
|
|
12
|
+
- Add RSpec helpers to simulate responses
|
data/Gemfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in f_http_client.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
group :development do
|
|
9
|
+
gem 'f_service', github: 'Fretadao/f_service', branch: 'master'
|
|
10
|
+
gem 'rubocop'
|
|
11
|
+
gem 'rubocop-performance'
|
|
12
|
+
gem 'rubocop-rspec'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
group :test do
|
|
16
|
+
gem 'rspec', '~> 3.0'
|
|
17
|
+
gem 'simplecov'
|
|
18
|
+
gem 'webmock'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
group :development, :test do
|
|
22
|
+
gem 'pry'
|
|
23
|
+
gem 'pry-nav'
|
|
24
|
+
gem 'rake', '~> 13.0'
|
|
25
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
GIT
|
|
2
|
+
remote: https://github.com/Fretadao/f_service.git
|
|
3
|
+
revision: 22d3e1c8bd4aa83b14f34b1af19aa4cd22b8ad8b
|
|
4
|
+
branch: master
|
|
5
|
+
specs:
|
|
6
|
+
f_service (0.2.0)
|
|
7
|
+
|
|
8
|
+
PATH
|
|
9
|
+
remote: .
|
|
10
|
+
specs:
|
|
11
|
+
f_http_client (0.1.0)
|
|
12
|
+
activesupport
|
|
13
|
+
addressable
|
|
14
|
+
dry-configurable
|
|
15
|
+
dry-initializer
|
|
16
|
+
httparty
|
|
17
|
+
|
|
18
|
+
GEM
|
|
19
|
+
remote: https://rubygems.org/
|
|
20
|
+
specs:
|
|
21
|
+
activesupport (7.0.4.3)
|
|
22
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
23
|
+
i18n (>= 1.6, < 2)
|
|
24
|
+
minitest (>= 5.1)
|
|
25
|
+
tzinfo (~> 2.0)
|
|
26
|
+
addressable (2.8.1)
|
|
27
|
+
public_suffix (>= 2.0.2, < 6.0)
|
|
28
|
+
ast (2.4.2)
|
|
29
|
+
coderay (1.1.3)
|
|
30
|
+
concurrent-ruby (1.2.2)
|
|
31
|
+
crack (0.4.5)
|
|
32
|
+
rexml
|
|
33
|
+
diff-lcs (1.5.0)
|
|
34
|
+
docile (1.4.0)
|
|
35
|
+
dry-configurable (1.0.1)
|
|
36
|
+
dry-core (~> 1.0, < 2)
|
|
37
|
+
zeitwerk (~> 2.6)
|
|
38
|
+
dry-core (1.0.0)
|
|
39
|
+
concurrent-ruby (~> 1.0)
|
|
40
|
+
zeitwerk (~> 2.6)
|
|
41
|
+
dry-initializer (3.1.1)
|
|
42
|
+
hashdiff (1.0.1)
|
|
43
|
+
httparty (0.21.0)
|
|
44
|
+
mini_mime (>= 1.0.0)
|
|
45
|
+
multi_xml (>= 0.5.2)
|
|
46
|
+
i18n (1.12.0)
|
|
47
|
+
concurrent-ruby (~> 1.0)
|
|
48
|
+
json (2.6.3)
|
|
49
|
+
method_source (1.0.0)
|
|
50
|
+
mini_mime (1.1.2)
|
|
51
|
+
minitest (5.18.0)
|
|
52
|
+
multi_xml (0.6.0)
|
|
53
|
+
parallel (1.22.1)
|
|
54
|
+
parser (3.2.1.0)
|
|
55
|
+
ast (~> 2.4.1)
|
|
56
|
+
pry (0.14.2)
|
|
57
|
+
coderay (~> 1.1)
|
|
58
|
+
method_source (~> 1.0)
|
|
59
|
+
pry-nav (1.0.0)
|
|
60
|
+
pry (>= 0.9.10, < 0.15)
|
|
61
|
+
public_suffix (5.0.1)
|
|
62
|
+
rainbow (3.1.1)
|
|
63
|
+
rake (13.0.6)
|
|
64
|
+
regexp_parser (2.7.0)
|
|
65
|
+
rexml (3.2.5)
|
|
66
|
+
rspec (3.12.0)
|
|
67
|
+
rspec-core (~> 3.12.0)
|
|
68
|
+
rspec-expectations (~> 3.12.0)
|
|
69
|
+
rspec-mocks (~> 3.12.0)
|
|
70
|
+
rspec-core (3.12.1)
|
|
71
|
+
rspec-support (~> 3.12.0)
|
|
72
|
+
rspec-expectations (3.12.2)
|
|
73
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
74
|
+
rspec-support (~> 3.12.0)
|
|
75
|
+
rspec-mocks (3.12.3)
|
|
76
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
77
|
+
rspec-support (~> 3.12.0)
|
|
78
|
+
rspec-support (3.12.0)
|
|
79
|
+
rubocop (1.45.1)
|
|
80
|
+
json (~> 2.3)
|
|
81
|
+
parallel (~> 1.10)
|
|
82
|
+
parser (>= 3.2.0.0)
|
|
83
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
84
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
85
|
+
rexml (>= 3.2.5, < 4.0)
|
|
86
|
+
rubocop-ast (>= 1.24.1, < 2.0)
|
|
87
|
+
ruby-progressbar (~> 1.7)
|
|
88
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
89
|
+
rubocop-ast (1.26.0)
|
|
90
|
+
parser (>= 3.2.1.0)
|
|
91
|
+
rubocop-capybara (2.17.1)
|
|
92
|
+
rubocop (~> 1.41)
|
|
93
|
+
rubocop-performance (1.16.0)
|
|
94
|
+
rubocop (>= 1.7.0, < 2.0)
|
|
95
|
+
rubocop-ast (>= 0.4.0)
|
|
96
|
+
rubocop-rspec (2.18.1)
|
|
97
|
+
rubocop (~> 1.33)
|
|
98
|
+
rubocop-capybara (~> 2.17)
|
|
99
|
+
ruby-progressbar (1.11.0)
|
|
100
|
+
simplecov (0.22.0)
|
|
101
|
+
docile (~> 1.1)
|
|
102
|
+
simplecov-html (~> 0.11)
|
|
103
|
+
simplecov_json_formatter (~> 0.1)
|
|
104
|
+
simplecov-html (0.12.3)
|
|
105
|
+
simplecov_json_formatter (0.1.4)
|
|
106
|
+
tzinfo (2.0.6)
|
|
107
|
+
concurrent-ruby (~> 1.0)
|
|
108
|
+
unicode-display_width (2.4.2)
|
|
109
|
+
webmock (3.18.1)
|
|
110
|
+
addressable (>= 2.8.0)
|
|
111
|
+
crack (>= 0.3.2)
|
|
112
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
113
|
+
zeitwerk (2.6.7)
|
|
114
|
+
|
|
115
|
+
PLATFORMS
|
|
116
|
+
x86_64-linux
|
|
117
|
+
|
|
118
|
+
DEPENDENCIES
|
|
119
|
+
f_http_client!
|
|
120
|
+
f_service!
|
|
121
|
+
pry
|
|
122
|
+
pry-nav
|
|
123
|
+
rake (~> 13.0)
|
|
124
|
+
rspec (~> 3.0)
|
|
125
|
+
rubocop
|
|
126
|
+
rubocop-performance
|
|
127
|
+
rubocop-rspec
|
|
128
|
+
simplecov
|
|
129
|
+
webmock
|
|
130
|
+
|
|
131
|
+
BUNDLED WITH
|
|
132
|
+
2.4.6
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Fretadão
|
|
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
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# FHTTPClient
|
|
2
|
+
|
|
3
|
+
Provides a basic skeleton for creating an HTTP client using the FService architecture.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add to your `.gemspec` client file `spec.add_runtime_dependency 'f_http_client'` and run `bundle install`.
|
|
8
|
+
|
|
9
|
+
## Configure
|
|
10
|
+
|
|
11
|
+
The gem allow the following configuration
|
|
12
|
+
|
|
13
|
+
- base_uri: a URI to be used for all services for your client;
|
|
14
|
+
- log_strategy: s symbol representing which logger the gem will use;
|
|
15
|
+
- null (default): means that the client will not log anything;
|
|
16
|
+
- rails: means that Rails logger will be used;
|
|
17
|
+
- any other value the gem will use a default logger which loggs at STDOUT.
|
|
18
|
+
- cache
|
|
19
|
+
- strategy: Defines which cache strategy will be used;
|
|
20
|
+
- null (default): the client does not log anything;
|
|
21
|
+
- rails: the Rails.cache will be used to perform caching;
|
|
22
|
+
- expires_in: Deines in seconds how much time the cache will be kept (default 0).
|
|
23
|
+
|
|
24
|
+
```rb
|
|
25
|
+
module BlogClient
|
|
26
|
+
class Configuration < FHTTPClient::Configuration
|
|
27
|
+
setting :paginate do
|
|
28
|
+
setting :enabled, default: true
|
|
29
|
+
setting :per_page, :20
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
BlogClient::Configuration.configure do |config|
|
|
35
|
+
config.base_uri = 'https://jsonplaceholder.typicode.com'
|
|
36
|
+
confg.log_strategy = :rails
|
|
37
|
+
|
|
38
|
+
config.cache.strategy = :rails
|
|
39
|
+
config.cache.expires_in = 25.minutes
|
|
40
|
+
config.paginate.per_page = 50
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BlogClient::Base < FHTTPClient::Base
|
|
45
|
+
def self.config
|
|
46
|
+
BlogClient::Configuration.config
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
cache_expires_in 10.minutes
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
Could create a class:
|
|
56
|
+
|
|
57
|
+
```rb
|
|
58
|
+
# frozen_string_literal: true
|
|
59
|
+
|
|
60
|
+
module BlogClient
|
|
61
|
+
module Post
|
|
62
|
+
class Find < BlogClient::Base
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def make_request
|
|
66
|
+
self.class.get(formatted_path, headers: headers)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def path_template
|
|
70
|
+
'/posts/%<id>s'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def headers
|
|
74
|
+
@headers.merge(content_type: 'application/json')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# BlogClient::Post::Find.(path_params: { id: 1 })
|
|
81
|
+
# .and_then { |response| response.parsed_response }
|
|
82
|
+
#
|
|
83
|
+
# => {
|
|
84
|
+
# userId: 1,
|
|
85
|
+
# id: 1,
|
|
86
|
+
# title: "How to use a FHTTPCLient Gem",
|
|
87
|
+
# body: "A great text here."
|
|
88
|
+
# }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Result examples:
|
|
92
|
+
|
|
93
|
+
```rb
|
|
94
|
+
Person::Create.(name: 'Joe Vicenzo', birthdate: '2000-01-01')
|
|
95
|
+
.and_then { |user| return redirect_to users_path(user.id) }
|
|
96
|
+
.on_failure(:unprocessable_entity) { |errors| return render_action :new, locals: { errors: errors } }
|
|
97
|
+
.on_failure(:client_error) { |errors| render_action :new, warning: errors }
|
|
98
|
+
.on_failure(:server_error) { |error| render_action :new, warning: ['Try again latter.'] }
|
|
99
|
+
.on_failure(:server_error) { |error| render_action :new, warning: ['Server is busy. Try again latter.'] }
|
|
100
|
+
.on_failure { |_error, type| render_action :new, warning: ["Unexpected error. Contact admin and talk about #{type} error."] }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This gem uses the gem [HTTParty](https://github.com/jnunemaker/httparty) as base to perform requests.
|
|
104
|
+
Then we can use any [example](https://github.com/jnunemaker/httparty/tree/master/examples) to implements the method make_request, for GET, POST, PUT, etc.
|
|
105
|
+
The class *FHTTPClient::Base* provides the following options to help building the request:
|
|
106
|
+
- *headers*: can be used to provide custom headers;
|
|
107
|
+
- *query*: can be used to provide querystring params;
|
|
108
|
+
- *body*: can be used to provide a body when request requires this;
|
|
109
|
+
- *options*: can be used provide any other option for HTTParty;
|
|
110
|
+
- *path_params*, can be used to fills params which is in the request path.
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
|
115
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
116
|
+
|
|
117
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
118
|
+
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Fretadao/f_http_client.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Post
|
|
4
|
+
class Find < FHTTPClient::Base
|
|
5
|
+
base_uri 'https://jsonplaceholder.typicode.com'
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def make_request
|
|
10
|
+
self.class.get(formatted_path, headers: headers)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def path_template
|
|
14
|
+
'/posts/%<id>s'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def headers
|
|
18
|
+
@headers.merge(content_type: 'application/json')
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Post::Find.(path_params: { id: 1 })
|
|
24
|
+
# .and_then { |response| response.parsed_response }
|
|
25
|
+
#
|
|
26
|
+
# => {
|
|
27
|
+
# userId: 1,
|
|
28
|
+
# id: 1,
|
|
29
|
+
# title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
|
|
30
|
+
# body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
|
|
31
|
+
# }
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
# Allows to have an http client to build a service
|
|
5
|
+
# @abstract
|
|
6
|
+
class Base < FHTTPClient::Service
|
|
7
|
+
include HTTParty
|
|
8
|
+
|
|
9
|
+
DEFAULT_SKIP_CACHE_IF = ->(response) { FHTTPClient::Cache::HTTPResponseAnalyzer.not_cacheable?(response) }
|
|
10
|
+
|
|
11
|
+
option :headers, default: -> { {} }
|
|
12
|
+
option :query, default: -> { {} }
|
|
13
|
+
option :body, default: -> { {} }
|
|
14
|
+
option :options, default: -> { {} }
|
|
15
|
+
option :path_params, default: -> { {} }
|
|
16
|
+
|
|
17
|
+
parser FHTTPClient::Parser::Response
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
configure_base_uri.and_then do
|
|
21
|
+
response = make_cached_request.value!
|
|
22
|
+
|
|
23
|
+
FHTTPClient::Processor::Response.(response: response, log_strategy: log_strategy)
|
|
24
|
+
end
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
FHTTPClient::Processor::Exception.(error: e, log_strategy: log_strategy)
|
|
27
|
+
.on_failure(:uncaught_error) { raise e }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.config
|
|
31
|
+
raise NotImplementedError, 'Clients must implement .config'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.cache_strategy(strategy = nil)
|
|
35
|
+
default_options[:cache_strategy] = config.cache.strategy if default_options[:cache_strategy].nil?
|
|
36
|
+
return default_options[:cache_strategy] unless strategy
|
|
37
|
+
|
|
38
|
+
default_options[:cache_strategy] = strategy
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.cache_expires_in(expires_in = nil)
|
|
42
|
+
default_options[:cache_expires_in] = config.cache.expires_in if default_options[:cache_expires_in].nil?
|
|
43
|
+
|
|
44
|
+
return default_options[:cache_expires_in] unless expires_in
|
|
45
|
+
|
|
46
|
+
default_options[:cache_expires_in] = expires_in
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def configure_base_uri
|
|
52
|
+
return Success(:uri_already_configured) if self.class.base_uri.present?
|
|
53
|
+
|
|
54
|
+
return Failure(:no_base_uri_configured) if config.base_uri.blank?
|
|
55
|
+
|
|
56
|
+
self.class.base_uri(config.base_uri)
|
|
57
|
+
|
|
58
|
+
Success(:base_uri_configured)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def make_cached_request
|
|
62
|
+
FHTTPClient::Store.(
|
|
63
|
+
key: cache_key,
|
|
64
|
+
strategy: cache_strategy,
|
|
65
|
+
block: -> { make_request },
|
|
66
|
+
expires_in: cache_expires_in,
|
|
67
|
+
skip_if: skip_cache_if
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def make_request
|
|
72
|
+
raise NotImplementedError, 'Clients must implement #make_request'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def path_template
|
|
76
|
+
raise NotImplementedError, 'Clients must implement #path_template'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def formatted_path
|
|
80
|
+
@formatted_path ||= format(path_template, path_params)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def cache_key
|
|
84
|
+
FHTTPClient::Cache::Key.generate(
|
|
85
|
+
self.class,
|
|
86
|
+
path: formatted_path,
|
|
87
|
+
headers: headers,
|
|
88
|
+
query: query,
|
|
89
|
+
body: body,
|
|
90
|
+
options: options
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def config
|
|
95
|
+
self.class.config
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def log_strategy
|
|
99
|
+
config.log_strategy
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def cache_strategy
|
|
103
|
+
self.class.default_options[:cache_strategy] || config.cache.strategy
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def cache_expires_in
|
|
107
|
+
self.class.default_options[:cache_expires_in] || config.cache.expires_in
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def skip_cache_if
|
|
111
|
+
DEFAULT_SKIP_CACHE_IF
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Cache
|
|
5
|
+
module HTTPResponseAnalyzer
|
|
6
|
+
attr_reader :response
|
|
7
|
+
|
|
8
|
+
NOT_CACHEABLE_RESPONSES = %i[
|
|
9
|
+
server_error?
|
|
10
|
+
gateway_timeout?
|
|
11
|
+
request_timeout?
|
|
12
|
+
unauthorized?
|
|
13
|
+
too_many_requests?
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def not_cacheable_responses
|
|
18
|
+
NOT_CACHEABLE_RESPONSES
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def not_cacheable?(response)
|
|
22
|
+
not_cacheable_responses.any? do |not_cacheable_response|
|
|
23
|
+
response.public_send(not_cacheable_response)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Cache
|
|
5
|
+
class Key
|
|
6
|
+
def self.generate(*args)
|
|
7
|
+
params = args.extract_options!
|
|
8
|
+
klass = args.first.to_s
|
|
9
|
+
|
|
10
|
+
[klass.underscore, params.compact_blank.to_query].filter_map(&:presence).join('?')
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Cache
|
|
5
|
+
RailsNotDefined = Class.new(StandardError)
|
|
6
|
+
|
|
7
|
+
class Rails
|
|
8
|
+
class << self
|
|
9
|
+
def fetch(name, options = {})
|
|
10
|
+
raise RailsNotDefined unless defined?(::Rails)
|
|
11
|
+
|
|
12
|
+
cache_entry = cache.read(name)
|
|
13
|
+
|
|
14
|
+
return cache_entry if cache_entry.present?
|
|
15
|
+
return unless block_given?
|
|
16
|
+
|
|
17
|
+
result = yield
|
|
18
|
+
|
|
19
|
+
cache.write(name, result, options) unless options[:skip_if]&.(result)
|
|
20
|
+
|
|
21
|
+
result
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def cache
|
|
27
|
+
::Rails.cache
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
class Configuration
|
|
5
|
+
extend Dry::Configurable
|
|
6
|
+
|
|
7
|
+
setting :base_uri
|
|
8
|
+
setting :log_strategy, default: :null
|
|
9
|
+
setting :cache do
|
|
10
|
+
setting :strategy, default: :null
|
|
11
|
+
setting :expires_in, default: 0
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
# Allow to log a message to system log
|
|
5
|
+
#
|
|
6
|
+
# Attributes:
|
|
7
|
+
# - message: object containint the message to be logged;
|
|
8
|
+
# - level (optional): level symbol to log the message (debug, info, warn error, fatal or unknown);
|
|
9
|
+
# - tags (optional): list of tags to be added to log line.
|
|
10
|
+
#
|
|
11
|
+
# Example:
|
|
12
|
+
# FHTTPClient::Log.(message: { name: 'Bruno' }.to_json)
|
|
13
|
+
# FHTTPClient::Log.(message: { name: 'Bruno' }.to_json, stragegy: :rails)
|
|
14
|
+
# FHTTPClient::Log.(message: { name: 'Bruno' }.to_json, tags: ['Response'])
|
|
15
|
+
# FHTTPClient::Log.(message: { response: { name: 'Bruno' } }.to_json, level: :warn)
|
|
16
|
+
class Log < FHTTPClient::Service
|
|
17
|
+
LOGGERS = {
|
|
18
|
+
rails: FHTTPClient::Logger::Rails,
|
|
19
|
+
default: FHTTPClient::Logger::Default
|
|
20
|
+
}.freeze
|
|
21
|
+
private_constant :LOGGERS
|
|
22
|
+
|
|
23
|
+
option :message
|
|
24
|
+
option :strategy, default: -> { :null }
|
|
25
|
+
option :level, default: -> { :info }
|
|
26
|
+
option :tags, default: -> { [] }
|
|
27
|
+
|
|
28
|
+
def run
|
|
29
|
+
logger.tagged(*Array(tags)).public_send(level, message)
|
|
30
|
+
|
|
31
|
+
Success(:logged)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def logger
|
|
35
|
+
@logger ||= LOGGERS.fetch(strategy, FHTTPClient::Logger::Null).new
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Logger
|
|
5
|
+
# A Basic logger to use when no logger is provided
|
|
6
|
+
class Default
|
|
7
|
+
def initialize(tags: [])
|
|
8
|
+
@logger = ::Logger.new($stdout)
|
|
9
|
+
@current_tags = tags
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def tagged(*tags)
|
|
13
|
+
tagged = self.class.new(tags: current_tags + tags)
|
|
14
|
+
block_given? ? yield(tagged) : tagged
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def debug(message) = add(::Logger::DEBUG, message)
|
|
18
|
+
def info(message) = add(::Logger::INFO, message)
|
|
19
|
+
def warn(message) = add(::Logger::WARN, message)
|
|
20
|
+
def error(message) = add(::Logger::ERROR, message)
|
|
21
|
+
def fatal(message) = add(::Logger::FATAL, message)
|
|
22
|
+
def unknown(message) = add(::Logger::UNKNOWN, message)
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :logger, :current_tags
|
|
27
|
+
|
|
28
|
+
def add(severity, message)
|
|
29
|
+
formatted_tags = format_tags
|
|
30
|
+
full_message = formatted_tags.empty? ? message : [format_tags, message].join(' - ')
|
|
31
|
+
|
|
32
|
+
logger.add(severity, full_message)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def format_tags
|
|
36
|
+
current_tags.map { |tag| "[#{tag}]" }.join(' ')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Logger
|
|
5
|
+
# Logs nothing
|
|
6
|
+
class Null
|
|
7
|
+
def tagged(...) = block_given? ? yield(self) : self
|
|
8
|
+
def debug(...) = nil
|
|
9
|
+
def info(...) = nil
|
|
10
|
+
def warn(...) = nil
|
|
11
|
+
def error(...) = nil
|
|
12
|
+
def fatal(...) = nil
|
|
13
|
+
def unknown(...) = nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Logger
|
|
5
|
+
# Logging with Rails logger
|
|
6
|
+
class Rails
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
delegate %i[tagged debug info warn error fatal unknown] => :@logger
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@logger = ::Rails.logger
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Parser
|
|
5
|
+
# Parse a json response letting this as symbol hash
|
|
6
|
+
# Following those examples: https://github.com/jnunemaker/httparty/blob/master/examples/custom_parsers.rb
|
|
7
|
+
# And looking default formats: https://github.com/jnunemaker/httparty/blob/fb7c40303b7e6429196ef748754505768520407c/lib/httparty/parser.rb#L42
|
|
8
|
+
class Response < HTTParty::Parser
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def json
|
|
12
|
+
JSON.parse(body, quirks_mode: true, allow_nan: true, symbolize_names: true)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Processor
|
|
5
|
+
# This Service proccess a HTTP exception generating failure result.
|
|
6
|
+
#
|
|
7
|
+
# Example:
|
|
8
|
+
#
|
|
9
|
+
# def process
|
|
10
|
+
# error = Errno::ECONNREFUSED.new('Failed to open TCP connection to :80')
|
|
11
|
+
# FHTTPClient::Processor::ResponseProcessor.(error: error)
|
|
12
|
+
# .on_failure(:connection_refused) { return 'The server has been refused the connection.' }
|
|
13
|
+
# .on_failure { |error_message| return "Generic #{error_message}" }
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# proccess
|
|
17
|
+
# # => 'The server has been refused the connection.'
|
|
18
|
+
class Exception < FHTTPClient::Service
|
|
19
|
+
option :error
|
|
20
|
+
option :log_strategy, default: -> { :null }
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
log_data.and_then { Failure(error_name, :exception, data: error) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def error_name
|
|
29
|
+
case error.class.to_s
|
|
30
|
+
when 'Errno::ECONNREFUSED'
|
|
31
|
+
:connection_refused
|
|
32
|
+
when /timeout/i
|
|
33
|
+
:timeout
|
|
34
|
+
else
|
|
35
|
+
:uncaught_error
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def log_data
|
|
40
|
+
FHTTPClient::Log.(
|
|
41
|
+
message: { error: error.class.to_s, message: error.message, backtrace: error.backtrace.join(', ') }.to_json,
|
|
42
|
+
tags: 'EXTERNAL REQUEST',
|
|
43
|
+
level: :error,
|
|
44
|
+
strategy: log_strategy
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
module Processor
|
|
5
|
+
# This Service proccess a HTTP response, generating a success or failure result.
|
|
6
|
+
#
|
|
7
|
+
# Example:
|
|
8
|
+
#
|
|
9
|
+
# def process
|
|
10
|
+
# response = OpenStruct.new(success?: false, parsed_response: {}, headers: {}, message: 'Conflict')
|
|
11
|
+
# processor = FHTTPClient::Processor::Response.new(response: response)
|
|
12
|
+
# processor.()
|
|
13
|
+
# .on_success { |value| "Success! #{value.inspect}"}
|
|
14
|
+
# .on_failure(:conflict) { |value| return "A conflict happened! #{value.inspect}"}
|
|
15
|
+
# .on_failure(:client_error) { |value| return "An unexpected client error happened #{value.inspect}"}
|
|
16
|
+
# .on_failure { |value| return "Generic #{value}" }
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# proccess
|
|
20
|
+
# # => "A conflict happened! {}"
|
|
21
|
+
class Response < FHTTPClient::Service
|
|
22
|
+
extend Forwardable
|
|
23
|
+
|
|
24
|
+
option :response
|
|
25
|
+
option :log_strategy, default: -> { :null }
|
|
26
|
+
|
|
27
|
+
STATUS_FAMILIES = {
|
|
28
|
+
200..299 => :successful,
|
|
29
|
+
400..499 => :client_error,
|
|
30
|
+
500..599 => :server_error,
|
|
31
|
+
300..399 => :redirection,
|
|
32
|
+
100..199 => :informational
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
private_constant :STATUS_FAMILIES
|
|
36
|
+
|
|
37
|
+
def run
|
|
38
|
+
log_data.and_then { success? ? success_response : failure_response }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def_delegators :@response, :headers, :success?, :parsed_response, :code
|
|
44
|
+
|
|
45
|
+
def success_response
|
|
46
|
+
Success(response_type, response_family, data: response)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def failure_response
|
|
50
|
+
Failure(response_type, response_family, data: response)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Private:
|
|
54
|
+
# Transform HTTParty response message in to a symbol
|
|
55
|
+
# Ex: When the request returns a 400 (Bad Request)
|
|
56
|
+
#
|
|
57
|
+
# # => :bad_request
|
|
58
|
+
def response_type
|
|
59
|
+
message.empty? ? :unknown_type : message.downcase.tr(' ', '_').to_sym
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Private:
|
|
63
|
+
# Transform HTTParty response code in to a symbol
|
|
64
|
+
# Ex: When the request returns a 400 (Bad Request)
|
|
65
|
+
#
|
|
66
|
+
# # => :client_error
|
|
67
|
+
def response_family
|
|
68
|
+
STATUS_FAMILIES
|
|
69
|
+
.find(-> { [code, :unknown_family] }) { |codes, _family| codes.cover?(code.to_i) }
|
|
70
|
+
.last
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Private
|
|
74
|
+
# Generates a HTTParty response message from a Net::HTTPRespoonse Class
|
|
75
|
+
# Examples:
|
|
76
|
+
#
|
|
77
|
+
# response_type(Net::HTTPOK)
|
|
78
|
+
# # => "OK"
|
|
79
|
+
#
|
|
80
|
+
# response_type(Net::HTTPUnprocessableEntity)
|
|
81
|
+
# # => "Unprocessable Entity"
|
|
82
|
+
def message
|
|
83
|
+
@message ||= response_class
|
|
84
|
+
.to_s
|
|
85
|
+
.delete_prefix('Net::HTTP')
|
|
86
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1 \2')
|
|
87
|
+
.gsub(/([a-z])([A-Z])/, '\1 \2')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Private
|
|
91
|
+
# Get an HTTP status error name for a status code
|
|
92
|
+
#
|
|
93
|
+
# Ex: 400
|
|
94
|
+
# # => "Net:HTTPBadRequest"
|
|
95
|
+
def response_class
|
|
96
|
+
Net::HTTPResponse::CODE_TO_OBJ[code.to_s].to_s
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def log_data
|
|
100
|
+
FHTTPClient::Log.(
|
|
101
|
+
message: { request: request_log_data, response: response_log_data }.to_json,
|
|
102
|
+
strategy: log_strategy,
|
|
103
|
+
tags: 'EXTERNAL REQUEST'
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def request_log_data
|
|
108
|
+
request = response.request
|
|
109
|
+
request_options = request.options
|
|
110
|
+
|
|
111
|
+
{
|
|
112
|
+
headers: request_options[:headers],
|
|
113
|
+
method: request.http_method.name.split('::').last.upcase,
|
|
114
|
+
path: request.uri.path,
|
|
115
|
+
querystring: Addressable::URI.parse(request.uri.query)&.query_values,
|
|
116
|
+
body: request_options[:body]
|
|
117
|
+
}.compact_blank
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def response_log_data
|
|
121
|
+
{ code: code, human_code: message, headers: headers, body: parsed_response }.compact_blank
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClientFakeResponse
|
|
4
|
+
def build_httparty_response(code: 200, parsed_response: {})
|
|
5
|
+
request_object = HTTParty::Request.new Net::HTTP::Get, '/'
|
|
6
|
+
response_object = Net::HTTPResponse::CODE_TO_OBJ[code.to_s].new('1.1', code, '')
|
|
7
|
+
allow(response_object).to receive(:body)
|
|
8
|
+
|
|
9
|
+
HTTParty::Response.new(request_object, response_object, -> { parsed_response })
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
RSpec.configure do |config|
|
|
14
|
+
config.include FHTTPClientFakeResponse
|
|
15
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
# Allow defining keyword args for a class
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# class User::Create < FHTTPClient::Service
|
|
8
|
+
# option :name
|
|
9
|
+
# option :age
|
|
10
|
+
# option :email, default: -> { 'guest@user.com' }
|
|
11
|
+
#
|
|
12
|
+
# def run
|
|
13
|
+
# Success(:created, data: "Hello #{name}! Your email is: #{email}")
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# User.(name: 'Matheus', age: 22)
|
|
18
|
+
# => #<FService::Result::Success:0x0000557fae615ea8 @handled=false, @matching_types=[], @types=[:created], @value="Hello Bruno! Your email is: guest@user.com">
|
|
19
|
+
class Service < FService::Base
|
|
20
|
+
extend Dry::Initializer
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FHTTPClient
|
|
4
|
+
# Allow to log a message to system log
|
|
5
|
+
#
|
|
6
|
+
# Attributes:
|
|
7
|
+
# - strategy: symbol representing cache stragegy to be used (null or rails);
|
|
8
|
+
# - block: the lambda to have its result cached;
|
|
9
|
+
# - key: the cache key to be used to store the result;
|
|
10
|
+
# - expires_in (optional): seconds to keep the result in cache;
|
|
11
|
+
# - skip_if (optional): a block to run over the block result and decide if result caching must be persisted or skiped.
|
|
12
|
+
#
|
|
13
|
+
# Example:
|
|
14
|
+
# FHTTPClient::Cache.(strategy: :rails, key: 'users/list?name=bruno', block: -> { make_request })
|
|
15
|
+
# FHTTPClient::Cache.(strategy: :rails, key: 'users/list?name=bruno', block: -> { make_request }, expires_in: 15.minutes)
|
|
16
|
+
# FHTTPClient::Cache.(strategy: :rails, key: 'users/list?name=bruno', block: -> { make_request }, skip_if: ->(response) { response.status != 200 })
|
|
17
|
+
class Store < FHTTPClient::Service
|
|
18
|
+
option :strategy
|
|
19
|
+
option :key
|
|
20
|
+
option :block
|
|
21
|
+
option :expires_in, default: -> {}
|
|
22
|
+
option :skip_if, default: -> {}
|
|
23
|
+
|
|
24
|
+
def run
|
|
25
|
+
Success(
|
|
26
|
+
:done,
|
|
27
|
+
data: cache.fetch(key, { expires_in: expires_in, skip_if: skip_if }.compact_blank, &block)
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def cache
|
|
34
|
+
@cache ||= strategy == :rails ? FHTTPClient::Cache::Rails : FHTTPClient::Cache::Null
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'f_http_client/version'
|
|
4
|
+
require 'active_support/core_ext/array'
|
|
5
|
+
require 'active_support/core_ext/enumerable'
|
|
6
|
+
require 'active_support/core_ext/string'
|
|
7
|
+
require 'addressable'
|
|
8
|
+
require 'dry-configurable'
|
|
9
|
+
require 'dry-initializer'
|
|
10
|
+
require 'forwardable'
|
|
11
|
+
require 'httparty'
|
|
12
|
+
require 'f_service'
|
|
13
|
+
|
|
14
|
+
require 'f_http_client/configuration'
|
|
15
|
+
require 'f_http_client/service'
|
|
16
|
+
require 'f_http_client/store'
|
|
17
|
+
require 'f_http_client/cache/http_response_analizer'
|
|
18
|
+
require 'f_http_client/cache/key'
|
|
19
|
+
require 'f_http_client/cache/null'
|
|
20
|
+
require 'f_http_client/cache/rails'
|
|
21
|
+
|
|
22
|
+
require 'f_http_client/logger/default'
|
|
23
|
+
require 'f_http_client/logger/null'
|
|
24
|
+
require 'f_http_client/logger/rails'
|
|
25
|
+
require 'f_http_client/log'
|
|
26
|
+
|
|
27
|
+
require 'f_http_client/parser/response'
|
|
28
|
+
require 'f_http_client/processor/exception'
|
|
29
|
+
require 'f_http_client/processor/response'
|
|
30
|
+
|
|
31
|
+
require 'f_http_client/base'
|
|
32
|
+
|
|
33
|
+
module FHTTPClient
|
|
34
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: f_http_client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Fretadao Tech Team
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-05-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: addressable
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: dry-configurable
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: dry-initializer
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: httparty
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
description:
|
|
84
|
+
email:
|
|
85
|
+
- tech@fretadao.com.br
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- ".rspec"
|
|
91
|
+
- ".rubocop.yml"
|
|
92
|
+
- CHANGELOG.md
|
|
93
|
+
- Gemfile
|
|
94
|
+
- Gemfile.lock
|
|
95
|
+
- LICENSE
|
|
96
|
+
- README.md
|
|
97
|
+
- Rakefile
|
|
98
|
+
- examples/post_find.rb
|
|
99
|
+
- lib/f_http_client.rb
|
|
100
|
+
- lib/f_http_client/base.rb
|
|
101
|
+
- lib/f_http_client/cache/http_response_analizer.rb
|
|
102
|
+
- lib/f_http_client/cache/key.rb
|
|
103
|
+
- lib/f_http_client/cache/null.rb
|
|
104
|
+
- lib/f_http_client/cache/rails.rb
|
|
105
|
+
- lib/f_http_client/configuration.rb
|
|
106
|
+
- lib/f_http_client/log.rb
|
|
107
|
+
- lib/f_http_client/logger/default.rb
|
|
108
|
+
- lib/f_http_client/logger/null.rb
|
|
109
|
+
- lib/f_http_client/logger/rails.rb
|
|
110
|
+
- lib/f_http_client/parser/response.rb
|
|
111
|
+
- lib/f_http_client/processor/exception.rb
|
|
112
|
+
- lib/f_http_client/processor/response.rb
|
|
113
|
+
- lib/f_http_client/rspec.rb
|
|
114
|
+
- lib/f_http_client/rspec/support.rb
|
|
115
|
+
- lib/f_http_client/rspec/support/helpers.rb
|
|
116
|
+
- lib/f_http_client/rspec/support/helpers/fake_response.rb
|
|
117
|
+
- lib/f_http_client/rspec/support/support.rb
|
|
118
|
+
- lib/f_http_client/service.rb
|
|
119
|
+
- lib/f_http_client/store.rb
|
|
120
|
+
- lib/f_http_client/version.rb
|
|
121
|
+
- sig/f_http_client.rbs
|
|
122
|
+
homepage: https://github.com/Fretadao/f_http_client
|
|
123
|
+
licenses:
|
|
124
|
+
- MIT
|
|
125
|
+
metadata:
|
|
126
|
+
homepage_uri: https://github.com/Fretadao/f_http_client
|
|
127
|
+
source_code_uri: https://github.com/Fretadao/f_http_client
|
|
128
|
+
changelog_uri: https://github.com/Fretadao/f_http_client/blob/master/CHANGELOG.md
|
|
129
|
+
rubygems_mfa_required: 'true'
|
|
130
|
+
post_install_message:
|
|
131
|
+
rdoc_options: []
|
|
132
|
+
require_paths:
|
|
133
|
+
- lib
|
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: 3.0.0
|
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - ">="
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '0'
|
|
144
|
+
requirements: []
|
|
145
|
+
rubygems_version: 3.3.5
|
|
146
|
+
signing_key:
|
|
147
|
+
specification_version: 4
|
|
148
|
+
summary: Gem to provade a base for an HTTP client using FService architecture
|
|
149
|
+
test_files: []
|