api_base 0.1.1 → 0.3.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 +4 -4
- data/MIT-LICENSE +20 -0
- data/README.md +11 -23
- data/Rakefile +8 -4
- data/app/assets/config/api_base_manifest.js +1 -0
- data/app/assets/stylesheets/api_base/application.css +15 -0
- data/app/controllers/api_base/application_controller.rb +6 -0
- data/app/helpers/api_base/application_helper.rb +6 -0
- data/app/jobs/api_base/application_job.rb +6 -0
- data/app/mailers/api_base/application_mailer.rb +8 -0
- data/app/models/api_base/api_log.rb +108 -0
- data/app/models/api_base/application_record.rb +7 -0
- data/app/views/layouts/api_base/application.html.erb +15 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20220612165032_create_api_logs.rb +3 -1
- data/lib/api_base/behaviours/get_json.rb +2 -2
- data/lib/api_base/behaviours/post_json.rb +2 -2
- data/lib/api_base/behaviours/shared.rb +8 -8
- data/lib/api_base/connection.rb +24 -0
- data/lib/api_base/{base.rb → endpoint.rb} +22 -27
- data/lib/api_base/engine.rb +7 -0
- data/lib/api_base/errors/api_error.rb +8 -0
- data/lib/api_base/errors/processing_error.rb +8 -0
- data/lib/api_base/service.rb +17 -0
- data/lib/api_base/version.rb +3 -1
- data/lib/api_base.rb +10 -8
- data/lib/tasks/api_base_tasks.rake +5 -0
- metadata +36 -28
- data/.gitignore +0 -12
- data/.rspec +0 -3
- data/.travis.yml +0 -6
- data/Gemfile +0 -7
- data/LICENSE.txt +0 -21
- data/api_base.gemspec +0 -33
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/lib/api_base/models/api_log.rb +0 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad387684e0176452fd738348781bf05e627d44cc170946c5820049e17c74fbf0
|
4
|
+
data.tar.gz: c59f1adedbe0fbc80fcf3568fcf467b646987f22932d3a053747babfac7505f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be650a32b4bd36756f491d9383bf2ee20320631591baa3370026ef354386136edea8e4cdb9b86f8aecd2e8aa7d7cea894731a793727565feb380b4d961346a08
|
7
|
+
data.tar.gz: 01350e16a0b860a95e426e2a07aa1e842025f7900f438de544a8afc888570f38fa5c509d3f3e1e84fde4b30744c04f9df44df0f229f3a36294ffba36578132a9
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2022 Stefan Froelich
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,40 +1,28 @@
|
|
1
1
|
# ApiBase
|
2
|
+
Short description and motivation.
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
6
|
|
7
7
|
## Installation
|
8
|
-
|
9
8
|
Add this line to your application's Gemfile:
|
10
9
|
|
11
10
|
```ruby
|
12
|
-
gem
|
11
|
+
gem "api_base"
|
13
12
|
```
|
14
13
|
|
15
14
|
And then execute:
|
16
|
-
|
17
|
-
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
18
|
|
19
19
|
Or install it yourself as:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
## Usage
|
24
|
-
|
25
|
-
TODO: Write usage instructions here
|
26
|
-
|
27
|
-
## Development
|
28
|
-
|
29
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
-
|
31
|
-
To install this gem onto your local machine, run `bundle exec rake install`. 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
20
|
+
```bash
|
21
|
+
$ gem install api_base
|
22
|
+
```
|
32
23
|
|
33
24
|
## Contributing
|
34
|
-
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/api_base.
|
36
|
-
|
25
|
+
Contribution directions go here.
|
37
26
|
|
38
27
|
## License
|
39
|
-
|
40
28
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
|
2
|
-
require "rspec/core/rake_task"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require 'bundler/setup'
|
5
4
|
|
6
|
-
|
5
|
+
APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
|
6
|
+
load 'rails/tasks/engine.rake'
|
7
|
+
|
8
|
+
load 'rails/tasks/statistics.rake'
|
9
|
+
|
10
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1 @@
|
|
1
|
+
//= link_directory ../stylesheets/api_base .css
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# == Schema Information
|
4
|
+
#
|
5
|
+
# Table name: api_logs
|
6
|
+
#
|
7
|
+
# id :bigint not null, primary key
|
8
|
+
# api :text not null
|
9
|
+
# duration :float
|
10
|
+
# endpoint :text not null
|
11
|
+
# exception :jsonb
|
12
|
+
# method :text not null
|
13
|
+
# origin :text not null
|
14
|
+
# request_body :jsonb
|
15
|
+
# request_headers :jsonb
|
16
|
+
# response_body :jsonb
|
17
|
+
# response_headers :jsonb
|
18
|
+
# source :text not null
|
19
|
+
# status_code :integer
|
20
|
+
# created_at :datetime not null
|
21
|
+
# updated_at :datetime not null
|
22
|
+
#
|
23
|
+
require 'English'
|
24
|
+
|
25
|
+
module ApiBase
|
26
|
+
class ApiLog < ApplicationRecord
|
27
|
+
attribute :sanitized, :boolean, default: false
|
28
|
+
|
29
|
+
validate :ensure_nothing_changed, unless: :new_record?
|
30
|
+
validate :ensure_data_sanitized
|
31
|
+
|
32
|
+
validates_presence_of :api, :origin, :source, :endpoint
|
33
|
+
|
34
|
+
validates :source, presence: true, inclusion: { in: %w[outgoing_request incoming_webhook] }
|
35
|
+
validates :method, presence: true, inclusion: { in: %w[GET POST DELETE PUT] }
|
36
|
+
|
37
|
+
def self.start_outgoing_request(origin, method, endpoint, payload)
|
38
|
+
ApiLog.new api: origin.identifier, origin: origin.class.to_s, source: 'outgoing_request',
|
39
|
+
endpoint: "#{origin.connection.url_prefix}#{endpoint}", method:,
|
40
|
+
request_headers: origin.connection.headers, request_body: payload
|
41
|
+
end
|
42
|
+
|
43
|
+
def complete_outgoing_request(response, duration)
|
44
|
+
# Ensure we are recording the actual headers that were sent on the request.
|
45
|
+
# The ones set from the connection might not be the final headers.
|
46
|
+
self.request_headers = response.env.request_headers
|
47
|
+
# Set the rest of the response attributes.
|
48
|
+
assign_attributes status_code: response.status, duration:,
|
49
|
+
response_body: response.body, response_headers: response.headers
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.start_webhook_request(origin, request)
|
53
|
+
ApiLog.new api: origin, origin: origin.class.to_s, source: 'incoming_webhook',
|
54
|
+
endpoint: request.fullpath, method: request.method,
|
55
|
+
request_headers: request.headers.env.reject { |key|
|
56
|
+
key.to_s.include?('.')
|
57
|
+
}, request_body: request.params
|
58
|
+
end
|
59
|
+
|
60
|
+
def complete_webhook_request(response, duration)
|
61
|
+
# Set the rest of the response attributes.
|
62
|
+
assign_attributes status_code: response.status, duration:,
|
63
|
+
response_body: response.body, response_headers: response.headers
|
64
|
+
end
|
65
|
+
|
66
|
+
def filter_sensitive_data
|
67
|
+
parse_json_fields
|
68
|
+
|
69
|
+
%i[request_headers request_body response_headers response_body exception].each do |prop|
|
70
|
+
self[prop] = yield(self[prop]) if self[prop].is_a?(Hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
self.sanitized = true
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def ensure_nothing_changed
|
79
|
+
errors.add(:base, 'Record is read-only') if changed?
|
80
|
+
end
|
81
|
+
|
82
|
+
def ensure_data_sanitized
|
83
|
+
errors.add(:base, 'Data must be sanitized') unless sanitized?
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_json_fields
|
87
|
+
%i[request_headers request_body response_headers response_body exception].each do |prop|
|
88
|
+
self[prop] = safely_parse_json(self[prop])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def safely_parse_json(value)
|
93
|
+
case value
|
94
|
+
when nil, Hash
|
95
|
+
value
|
96
|
+
when String
|
97
|
+
JSON.parse value
|
98
|
+
when StandardError
|
99
|
+
[e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR).to_json
|
100
|
+
else
|
101
|
+
value.to_s.to_json
|
102
|
+
end
|
103
|
+
rescue StandardError
|
104
|
+
# Something we can't encode. Let's preserve it as a string.
|
105
|
+
value.to_s.to_json
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/config/routes.rb
ADDED
@@ -13,13 +13,13 @@ module ApiBase
|
|
13
13
|
def execute_request(endpoint, _payload)
|
14
14
|
execute do
|
15
15
|
connection.get(endpoint) do |req|
|
16
|
-
req.headers[
|
16
|
+
req.headers['Content-Type'] = 'application/json'
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def method
|
22
|
-
|
22
|
+
'GET'
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -14,13 +14,13 @@ module ApiBase
|
|
14
14
|
execute do
|
15
15
|
connection.post(endpoint, payload) do |req|
|
16
16
|
req.body = payload.to_json
|
17
|
-
req.headers[
|
17
|
+
req.headers['Content-Type'] = 'application/json'
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def method
|
23
|
-
|
23
|
+
'POST'
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -5,7 +5,7 @@ module ApiBase
|
|
5
5
|
# Shared module that adds common methods to api behaviours
|
6
6
|
module Shared
|
7
7
|
def behaviour_delegate(endpoint, payload = {})
|
8
|
-
api_log =
|
8
|
+
api_log = ApiBase::ApiLog.start_outgoing_request(self, method, endpoint, payload)
|
9
9
|
response, duration = make_request(endpoint, payload)
|
10
10
|
api_log.complete_outgoing_request response, duration
|
11
11
|
|
@@ -25,26 +25,26 @@ module ApiBase
|
|
25
25
|
protected
|
26
26
|
|
27
27
|
def make_request(endpoint, payload)
|
28
|
-
trace_active_tag(
|
29
|
-
trace_active_tag(
|
28
|
+
trace_active_tag('request.endpoint', endpoint)
|
29
|
+
trace_active_tag('request.payload', filter_object(payload))
|
30
30
|
|
31
31
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
32
32
|
response = execute_request(endpoint, payload)
|
33
33
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
34
34
|
|
35
|
-
trace_active_tag(
|
36
|
-
trace_active_tag(
|
37
|
-
trace_active_tag(
|
35
|
+
trace_active_tag('response.status', response.status)
|
36
|
+
trace_active_tag('response.body', filter_object(response.body))
|
37
|
+
trace_active_tag('response.duration', duration)
|
38
38
|
|
39
39
|
[response, duration]
|
40
40
|
end
|
41
41
|
|
42
42
|
def method
|
43
|
-
raise NotImplementedError,
|
43
|
+
raise NotImplementedError, 'method is not implemented'
|
44
44
|
end
|
45
45
|
|
46
46
|
def execute_request
|
47
|
-
raise NotImplementedError,
|
47
|
+
raise NotImplementedError, 'execute_request is not implemented'
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiBase
|
4
|
+
module Connection
|
5
|
+
def connection
|
6
|
+
@connection_cache ||= {}
|
7
|
+
@connection_cache[connection_name] ||= with_connection(connection_name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def connection_name
|
11
|
+
defined?(@connection_name) ? @connection_name.to_sym : :default
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_connection(connection_name)
|
15
|
+
@connection_name = connection_name
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def with_connection(connection_name)
|
21
|
+
raise NotImplementedError, "with_connection is not implemented"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,35 +1,34 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'stoplight'
|
5
|
+
require 'api_base/service'
|
6
|
+
require 'api_base/connection'
|
7
|
+
require 'api_base/concerns/traceable'
|
8
|
+
require 'api_base/concerns/filterer'
|
9
|
+
require 'api_base/errors/api_error'
|
10
|
+
require 'api_base/errors/processing_error'
|
6
11
|
|
7
12
|
module ApiBase
|
8
|
-
class
|
13
|
+
class Endpoint
|
9
14
|
include ActiveSupport::Rescuable
|
10
15
|
include Concerns::Traceable
|
11
16
|
include Concerns::Filterer
|
12
|
-
|
13
|
-
|
14
|
-
raise NotImplementedError, "identifier is not implemented"
|
15
|
-
end
|
16
|
-
|
17
|
-
def connection
|
18
|
-
raise NotImplementedError, "connection is not implemented"
|
19
|
-
end
|
20
|
-
|
21
|
-
def sensitive_data_keys
|
22
|
-
raise NotImplementedError, "sensitive_data_keys is not implemented"
|
23
|
-
end
|
17
|
+
include ApiBase::Service
|
18
|
+
include ApiBase::Connection
|
24
19
|
|
25
20
|
rescue_from Stoplight::Error::RedLight do
|
26
21
|
Rails.logger.warn "#{identifier} api circuit is closed"
|
27
|
-
raise
|
22
|
+
raise ApiBase::Errors::ApiError, 'Circuit broken'
|
28
23
|
end
|
29
24
|
|
30
25
|
rescue_from Faraday::TimeoutError do
|
31
26
|
Rails.logger.warn "#{identifier} api timed-out"
|
32
|
-
raise
|
27
|
+
raise ApiBase::Errors::ApiError, 'Request timed-out'
|
28
|
+
end
|
29
|
+
|
30
|
+
def identifier
|
31
|
+
"#{service_name}:#{connection_name}"
|
33
32
|
end
|
34
33
|
|
35
34
|
protected
|
@@ -42,7 +41,7 @@ module ApiBase
|
|
42
41
|
light.with_error_handler do |error, handler|
|
43
42
|
# We don't want processing errors to affect our circuit breakers
|
44
43
|
# They are our api equivalent of runtime errors.
|
45
|
-
raise error if error.is_a?(
|
44
|
+
raise error if error.is_a?(ApiBase::Errors::ProcessingError)
|
46
45
|
|
47
46
|
handler.call(error)
|
48
47
|
end
|
@@ -56,15 +55,11 @@ module ApiBase
|
|
56
55
|
def validate_status_code(response)
|
57
56
|
return if success_status_codes.include?(response.status)
|
58
57
|
|
59
|
-
raise
|
60
|
-
end
|
61
|
-
|
62
|
-
def success_status_codes
|
63
|
-
[200, 201]
|
58
|
+
raise ApiBase::Errors::ProcessingError, "Request failed with status: #{response.status}"
|
64
59
|
end
|
65
60
|
|
66
61
|
def filterer
|
67
|
-
@filterer ||= ActiveSupport::ParameterFilter.new
|
62
|
+
@filterer ||= ActiveSupport::ParameterFilter.new sensitive_keys
|
68
63
|
end
|
69
64
|
end
|
70
65
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiBase
|
4
|
+
module Service
|
5
|
+
def service_name
|
6
|
+
raise NotImplementedError, 'service_name is not implemented'
|
7
|
+
end
|
8
|
+
|
9
|
+
def sensitive_keys
|
10
|
+
raise NotImplementedError, 'sensitive_keys is not implemented'
|
11
|
+
end
|
12
|
+
|
13
|
+
def success_status_codes
|
14
|
+
[200, 201]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/api_base/version.rb
CHANGED
data/lib/api_base.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
|
2
|
-
require "api_base/base"
|
3
|
-
require "api_base/behaviours/shared"
|
4
|
-
require "api_base/behaviours/get_json"
|
5
|
-
require "api_base/behaviours/post_json"
|
6
|
-
require "api_base/models/api_log"
|
1
|
+
# frozen_string_literal: true
|
7
2
|
|
8
|
-
|
9
|
-
|
3
|
+
require 'api_base/version'
|
4
|
+
require 'api_base/engine'
|
5
|
+
require 'api_base/endpoint'
|
6
|
+
require 'api_base/behaviours/shared'
|
7
|
+
require 'api_base/behaviours/get_json'
|
8
|
+
require 'api_base/behaviours/post_json'
|
9
|
+
require 'api_base/errors/api_error'
|
10
|
+
require 'api_base/errors/processing_error'
|
10
11
|
|
12
|
+
module ApiBase
|
11
13
|
# Your code goes here...
|
12
14
|
end
|
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_base
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: faraday
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '1.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 6.0.3
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 6.0.3
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: stoplight
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 3.0.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 3.0.0
|
55
55
|
description: Building blocks for building API Clients
|
56
56
|
email:
|
57
57
|
- sfroelich01@gmail.com
|
@@ -59,26 +59,34 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
-
-
|
63
|
-
- ".rspec"
|
64
|
-
- ".travis.yml"
|
65
|
-
- Gemfile
|
66
|
-
- LICENSE.txt
|
62
|
+
- MIT-LICENSE
|
67
63
|
- README.md
|
68
64
|
- Rakefile
|
69
|
-
-
|
70
|
-
-
|
71
|
-
-
|
65
|
+
- app/assets/config/api_base_manifest.js
|
66
|
+
- app/assets/stylesheets/api_base/application.css
|
67
|
+
- app/controllers/api_base/application_controller.rb
|
68
|
+
- app/helpers/api_base/application_helper.rb
|
69
|
+
- app/jobs/api_base/application_job.rb
|
70
|
+
- app/mailers/api_base/application_mailer.rb
|
71
|
+
- app/models/api_base/api_log.rb
|
72
|
+
- app/models/api_base/application_record.rb
|
73
|
+
- app/views/layouts/api_base/application.html.erb
|
74
|
+
- config/routes.rb
|
72
75
|
- db/migrate/20220612165032_create_api_logs.rb
|
73
76
|
- lib/api_base.rb
|
74
|
-
- lib/api_base/base.rb
|
75
77
|
- lib/api_base/behaviours/get_json.rb
|
76
78
|
- lib/api_base/behaviours/post_json.rb
|
77
79
|
- lib/api_base/behaviours/shared.rb
|
78
80
|
- lib/api_base/concerns/filterer.rb
|
79
81
|
- lib/api_base/concerns/traceable.rb
|
80
|
-
- lib/api_base/
|
82
|
+
- lib/api_base/connection.rb
|
83
|
+
- lib/api_base/endpoint.rb
|
84
|
+
- lib/api_base/engine.rb
|
85
|
+
- lib/api_base/errors/api_error.rb
|
86
|
+
- lib/api_base/errors/processing_error.rb
|
87
|
+
- lib/api_base/service.rb
|
81
88
|
- lib/api_base/version.rb
|
89
|
+
- lib/tasks/api_base_tasks.rake
|
82
90
|
homepage: https://github.com/ussd-engine/api-base
|
83
91
|
licenses:
|
84
92
|
- MIT
|
@@ -95,14 +103,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
103
|
requirements:
|
96
104
|
- - ">="
|
97
105
|
- !ruby/object:Gem::Version
|
98
|
-
version:
|
106
|
+
version: '0'
|
99
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
108
|
requirements:
|
101
109
|
- - ">="
|
102
110
|
- !ruby/object:Gem::Version
|
103
111
|
version: '0'
|
104
112
|
requirements: []
|
105
|
-
rubygems_version: 3.
|
113
|
+
rubygems_version: 3.3.7
|
106
114
|
signing_key:
|
107
115
|
specification_version: 4
|
108
116
|
summary: Building blocks for building API Clients
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/LICENSE.txt
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
The MIT License (MIT)
|
2
|
-
|
3
|
-
Copyright (c) 2022 Stefan Froelich
|
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
|
13
|
-
all 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
|
21
|
-
THE SOFTWARE.
|
data/api_base.gemspec
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
require_relative "lib/api_base/version"
|
2
|
-
|
3
|
-
Gem::Specification.new do |spec|
|
4
|
-
spec.name = "api_base"
|
5
|
-
spec.version = ApiBase::VERSION
|
6
|
-
spec.authors = ["Stefan Froelich"]
|
7
|
-
spec.email = ["sfroelich01@gmail.com"]
|
8
|
-
|
9
|
-
spec.summary = "Building blocks for building API Clients"
|
10
|
-
spec.description = spec.summary
|
11
|
-
spec.homepage = "https://github.com/ussd-engine/api-base"
|
12
|
-
spec.license = "MIT"
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
-
|
15
|
-
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
-
|
17
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
-
spec.metadata["changelog_uri"] = spec.homepage
|
20
|
-
|
21
|
-
# Specify which files should be added to the gem when it is released.
|
22
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
24
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
-
end
|
26
|
-
spec.bindir = "exe"
|
27
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
-
spec.require_paths = ["lib"]
|
29
|
-
|
30
|
-
spec.add_dependency "rails", "~> 7"
|
31
|
-
spec.add_dependency "faraday", "~> 2"
|
32
|
-
spec.add_dependency "stoplight", "~> 3"
|
33
|
-
end
|
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "api_base"
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start(__FILE__)
|
data/bin/setup
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
# == Schema Information
|
2
|
-
#
|
3
|
-
# Table name: api_logs
|
4
|
-
#
|
5
|
-
# id :bigint not null, primary key
|
6
|
-
# api :text not null
|
7
|
-
# duration :float
|
8
|
-
# endpoint :text not null
|
9
|
-
# exception :jsonb
|
10
|
-
# method :text not null
|
11
|
-
# origin :text not null
|
12
|
-
# request_body :jsonb
|
13
|
-
# request_headers :jsonb
|
14
|
-
# response_body :jsonb
|
15
|
-
# response_headers :jsonb
|
16
|
-
# source :text not null
|
17
|
-
# status_code :integer
|
18
|
-
# created_at :datetime not null
|
19
|
-
# updated_at :datetime not null
|
20
|
-
#
|
21
|
-
require "English"
|
22
|
-
require "active_record"
|
23
|
-
|
24
|
-
module ApiBase
|
25
|
-
module Models
|
26
|
-
class ApiLog < ActiveRecord::Base
|
27
|
-
attribute :sanitized, :boolean, default: false
|
28
|
-
|
29
|
-
validate :nothing_changed, unless: :new_record?
|
30
|
-
validate :data_sanitized
|
31
|
-
|
32
|
-
validates_presence_of :api, :origin, :source, :endpoint
|
33
|
-
|
34
|
-
validates :source, presence: true, inclusion: { in: %w[outgoing_request incoming_webhook] }
|
35
|
-
validates :method, presence: true, inclusion: { in: %w[GET POST DELETE PUT] }
|
36
|
-
|
37
|
-
def self.start_outgoing_request(origin, method, endpoint, payload)
|
38
|
-
ApiLog.new api: origin.identifier, origin: origin.class.to_s, source: "outgoing_request",
|
39
|
-
endpoint: "#{origin.connection.url_prefix}#{endpoint}", method: method,
|
40
|
-
request_headers: origin.connection.headers, request_body: payload
|
41
|
-
end
|
42
|
-
|
43
|
-
def complete_outgoing_request(response, duration)
|
44
|
-
# Ensure we are recording the actual headers that were sent on the request.
|
45
|
-
# The ones set from the connection might not be the final headers.
|
46
|
-
self.request_headers = response.env.request_headers
|
47
|
-
# Set the rest of the response attributes.
|
48
|
-
assign_attributes status_code: response.status, duration: duration,
|
49
|
-
response_body: response.body, response_headers: response.headers
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.start_weekhook_request(origin, request)
|
53
|
-
ApiLog.new api: origin, origin: origin.class.to_s, source: "incoming_webhook",
|
54
|
-
endpoint: request.fullpath, method: request.method,
|
55
|
-
request_headers: request.headers.env.reject { |key|
|
56
|
-
key.to_s.include?(".")
|
57
|
-
}, request_body: request.params
|
58
|
-
end
|
59
|
-
|
60
|
-
def complete_webhook_request(response, duration)
|
61
|
-
# Set the rest of the response attributes.
|
62
|
-
assign_attributes status_code: response.status, duration: duration,
|
63
|
-
response_body: response.body, response_headers: response.headers
|
64
|
-
end
|
65
|
-
|
66
|
-
def filter_sensitive_data
|
67
|
-
parse_json_fields
|
68
|
-
|
69
|
-
%i[request_headers request_body response_headers response_body exception].each do |prop|
|
70
|
-
self[prop] = yield(self[prop]) if self[prop].is_a?(Hash)
|
71
|
-
end
|
72
|
-
|
73
|
-
self.sanitized = true
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def nothing_changed
|
79
|
-
errors.add(:base, "Record is read-only") if changed?
|
80
|
-
end
|
81
|
-
|
82
|
-
def data_sanitized
|
83
|
-
errors.add(:base, "Data must be sanitized") unless sanitized?
|
84
|
-
end
|
85
|
-
|
86
|
-
def parse_json_fields
|
87
|
-
%i[request_headers request_body response_headers response_body exception].each do |prop|
|
88
|
-
self[prop] = safely_parse_json(self[prop])
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def safely_parse_json(value)
|
93
|
-
case value
|
94
|
-
when nil, Hash
|
95
|
-
value
|
96
|
-
when String
|
97
|
-
JSON.parse value
|
98
|
-
when StandardError
|
99
|
-
[e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR).to_json
|
100
|
-
else
|
101
|
-
value.to_s.to_json
|
102
|
-
end
|
103
|
-
rescue StandardError
|
104
|
-
# Something we can't encode. Let's preserve it as a string.
|
105
|
-
value.to_s.to_json
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|