api_base 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: fb79ff7f3e672342333dc4debea004482c4e77394e32431cb4ca982d1db0e30f
|
4
|
+
data.tar.gz: f0aca5fed7084789d670dec00b483dac726e856b5e72c2acaf10191060f02ef5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f460e76e1bb523fbdef9d3bae0e36d4f2d8fef411fe27a26d6dc46fda8b999416ff3968285ddbfc1e7c44cb98fdd79e4a2bd7a18214855ab055d0e647ff030e2
|
7
|
+
data.tar.gz: 7d19df570df0a2883036e1f6b63087c6d29fde24f5df75180265b59ac9932ec272063f589c14f2c1039e66e83fa2a0d52d1841e19b50e0833c4d4734030d33d0
|
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.2.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-09-02 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: 7.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: 7.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
|