vigiles 0.1.0.pre.beta4 → 0.1.0.pre.beta6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/core_ext/action_dispatch/request.rb +31 -0
- data/lib/core_ext.rb +1 -0
- data/lib/vigiles/archive/parameter.rb +28 -0
- data/lib/vigiles/archive/request.rb +6 -1
- data/lib/vigiles/archive/response.rb +26 -7
- data/lib/vigiles/archive.rb +7 -8
- data/lib/vigiles/constants.rb +14 -0
- data/lib/vigiles/conversation_recorder.rb +11 -0
- data/lib/vigiles/conversation_recorders/application_json.rb +13 -0
- data/lib/vigiles/conversation_recorders/unknown.rb +11 -0
- data/lib/vigiles/middleware/record_conversation.rb +9 -10
- data/lib/vigiles/spec.rb +11 -1
- data/lib/vigiles/types.rb +4 -0
- data/lib/vigiles/version.rb +2 -2
- data/lib/vigiles.rb +26 -11
- metadata +6 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ad47542ac3fa029cef8be2b99b104eb7995abf67765c8e0582426249f2d5a5a
|
4
|
+
data.tar.gz: a468738d5ef0ad815be016420198ef939b254997509b25228f5b3779fa51976f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df8d3f3c5361e26b23b6a8cce014c4c55f3b15cc09f754a5ed3c34f15250360e09d6a0964ed23df5b1eb81cb87bec575198f71237fd24e1ce5f3ff201b279dc6
|
7
|
+
data.tar.gz: fe0759a9e54c564ee235f421dbd8c7d8cfe34378777c1c3737fb29fa152a7f277c4a94808e138ec4fb61c0ef7a619bed8b60a9d14d3059299772a3ef4c2dbcda
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ActionDispatch
|
5
|
+
class Request
|
6
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
7
|
+
def original_headers
|
8
|
+
# prefix content-type and content-length with `HTTP_` too,
|
9
|
+
# just for uniformity. apparently the rack specification
|
10
|
+
# requires content length and content type headers to not
|
11
|
+
# have the `HTTP_` prefix.
|
12
|
+
# see https://github.com/rack/rack/blob/main/SPEC.rdoc
|
13
|
+
#
|
14
|
+
@original_headers ||=
|
15
|
+
begin
|
16
|
+
original = {
|
17
|
+
"HTTP_CONTENT_LENGTH" => get_header("CONTENT_LENGTH"),
|
18
|
+
"HTTP_CONTENT_TYPE" => get_header("CONTENT_TYPE")
|
19
|
+
}
|
20
|
+
|
21
|
+
each_header do |header, value|
|
22
|
+
next unless header.start_with?("HTTP_")
|
23
|
+
|
24
|
+
original[header] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
original.freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/core_ext.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vigiles
|
5
|
+
module Archive
|
6
|
+
class Parameter < T::Struct
|
7
|
+
class Visibility < T::Enum
|
8
|
+
enums do
|
9
|
+
PersonallyIdentifiableInformation = new("personally_identifiable_information")
|
10
|
+
AuthorizationKey = new("authorization_key")
|
11
|
+
Password = new("password")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Source < T::Enum
|
16
|
+
enums do
|
17
|
+
Internal = new("internal")
|
18
|
+
External = new("external")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
const :visibility, Visibility
|
23
|
+
const :encrypted, T::Boolean
|
24
|
+
const :source, Source
|
25
|
+
const :name, String
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -37,13 +37,18 @@ module Vigiles
|
|
37
37
|
|
38
38
|
sig { params(request: ActionDispatch::Request).returns(Request) }
|
39
39
|
def self.from(request)
|
40
|
+
preferred_headers = Vigiles.specification.request_headers
|
41
|
+
available_headers = request.original_headers
|
42
|
+
recorded_headers = (available_headers if preferred_headers.empty?)
|
43
|
+
recorded_headers ||= preferred_headers.to_h { |h| [h, available_headers[h]] }
|
44
|
+
|
40
45
|
Request.new(
|
41
46
|
content_type: request.content_type || (raise InvalidParameterError, "content_type"),
|
42
47
|
user_agent: request.user_agent || "unknown_user_agent",
|
43
48
|
timestamp: DateTime.now,
|
44
49
|
remote_ip: IPAddr.new(request.remote_ip),
|
45
50
|
protocol: request.protocol,
|
46
|
-
headers:
|
51
|
+
headers: recorded_headers,
|
47
52
|
origin: request.origin || "unknown_origin_url",
|
48
53
|
payload: request.body.read,
|
49
54
|
http_method: Types::HttpMethod.deserialize(request.method),
|
@@ -1,21 +1,40 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: false
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Vigiles
|
5
5
|
module Archive
|
6
6
|
class Response < T::Struct
|
7
|
+
const :rack_response, Rack::Response
|
7
8
|
const :content_type, String
|
8
9
|
const :headers, Types::Headers
|
9
10
|
const :payload, Types::Payload
|
10
11
|
const :status, Integer
|
11
12
|
|
12
|
-
sig { params(
|
13
|
-
def self.
|
13
|
+
sig { params(rack_response: Rack::Response).returns(Types::Payload) }
|
14
|
+
private_class_method def self.extract_payload(rack_response)
|
15
|
+
case (body = rack_response.body)
|
16
|
+
when Array
|
17
|
+
return { body: :empty_no_content } if body.empty?
|
18
|
+
|
19
|
+
{ body: :not_empty_handle_later }
|
20
|
+
when Rack::BodyProxy
|
21
|
+
body_proxy = body
|
22
|
+
body_proxy = body_proxy.instance_variable_get(:@body) until body_proxy.is_a?(Array)
|
23
|
+
JSON.parse(body_proxy[0])
|
24
|
+
else
|
25
|
+
debugger
|
26
|
+
{ body: :unknown_response_payload_type }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { params(res: Rack::Response).returns(Response) }
|
31
|
+
def self.from(res)
|
14
32
|
Response.new(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
33
|
+
rack_response: res,
|
34
|
+
content_type: res.headers["Content-Type"] || "unknown_content_type",
|
35
|
+
headers: res.headers.as_json,
|
36
|
+
payload: extract_payload(res),
|
37
|
+
status: res.status
|
19
38
|
)
|
20
39
|
end
|
21
40
|
end
|
data/lib/vigiles/archive.rb
CHANGED
@@ -16,18 +16,17 @@ module Vigiles
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
sig { params(
|
20
|
-
def self.record_conversation(
|
19
|
+
sig { params(req: ActionDispatch::Request, res: Rack::Response).returns(T.nilable(Conversation)) }
|
20
|
+
def self.record_conversation(req:, res:)
|
21
21
|
# preferring to call `response.request` instead of preparing a new
|
22
22
|
# request (via `ActionDispatch::Request.new(env)` and passing it
|
23
23
|
# as an argument to this method because we can always recover the
|
24
24
|
# specific request that elicited a given response, according to
|
25
25
|
# https://github.com/rails/rails/blob/cacb8475a9d4373c0db437e7be4905685f03cefa/actionpack/lib/action_dispatch/http/response.rb#L53
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
extras = Extras.from(ad_request.env)
|
26
|
+
response = Response.from(res)
|
27
|
+
metadata = Metadata.from(req.env)
|
28
|
+
request = Request.from(req)
|
29
|
+
extras = Extras.from(req.env)
|
31
30
|
|
32
31
|
case (content_type = request.content_type)
|
33
32
|
when ContentType::ApplicationJson.serialize then record_json_conversation(request:, response:, metadata:, extras:)
|
@@ -63,7 +62,7 @@ module Vigiles
|
|
63
62
|
request_id: request.id,
|
64
63
|
response_content_type: response.content_type,
|
65
64
|
response_headers: response.headers,
|
66
|
-
response_payload:
|
65
|
+
response_payload: response.payload,
|
67
66
|
response_status: response.status
|
68
67
|
)
|
69
68
|
rescue => e
|
data/lib/vigiles/constants.rb
CHANGED
@@ -3,5 +3,19 @@
|
|
3
3
|
|
4
4
|
module Vigiles
|
5
5
|
module Constants
|
6
|
+
DEFAULT_CONTENT_TYPES = T.let(
|
7
|
+
Set.new(
|
8
|
+
%w[
|
9
|
+
application/json
|
10
|
+
]
|
11
|
+
), T::Set[String]
|
12
|
+
)
|
13
|
+
|
14
|
+
DEFAULT_CONTENT_TYPE_RECORDERS = T.let(
|
15
|
+
{
|
16
|
+
"application/json" => Vigiles::ConversationRecorders::ApplicationJson.instance
|
17
|
+
}.freeze,
|
18
|
+
T::Hash[String, Vigiles::ConversationRecorder]
|
19
|
+
)
|
6
20
|
end
|
7
21
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vigiles
|
5
|
+
module ConversationRecorders
|
6
|
+
class ApplicationJson < ConversationRecorder
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
sig { override.params(_response: ActionDispatch::Response).returns(Archive::Conversation) }
|
10
|
+
def record(_response) = Archive::Conversation.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vigiles
|
5
|
+
module ConversationRecorders
|
6
|
+
class Unknown < ConversationRecorder
|
7
|
+
sig { override.params(_response: ActionDispatch::Response).returns(Archive::Conversation) }
|
8
|
+
def record(_response) = Archive::Conversation.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -12,20 +12,19 @@ module Vigiles
|
|
12
12
|
|
13
13
|
sig { params(env: T.untyped).returns(T.untyped) }
|
14
14
|
def call(env)
|
15
|
-
|
16
|
-
|
15
|
+
req = ActionDispatch::Request.new(env)
|
16
|
+
record_conversation(req) do
|
17
|
+
@app.call(req.env)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
|
-
sig { params(blk: T.proc.returns(T.untyped)).returns(T.untyped) }
|
21
|
-
private def record_conversation(&blk)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
Vigiles.maybe_record_conversation(response) unless response.nil?
|
26
|
-
rack_response
|
21
|
+
sig { params(req: ActionDispatch::Request, blk: T.proc.returns(T.untyped)).returns(T.untyped) }
|
22
|
+
private def record_conversation(req, &blk)
|
23
|
+
res = Rack::Response[*blk.call]
|
24
|
+
Vigiles.maybe_record_conversation(req:, res:)
|
25
|
+
res.to_a
|
27
26
|
ensure
|
28
|
-
|
27
|
+
res.to_a
|
29
28
|
end
|
30
29
|
end
|
31
30
|
end
|
data/lib/vigiles/spec.rb
CHANGED
@@ -3,7 +3,17 @@
|
|
3
3
|
|
4
4
|
module Vigiles
|
5
5
|
class Spec < T::Struct
|
6
|
+
const :content_type_recorders, T::Hash[String, ConversationRecorder]
|
7
|
+
const :request_content_types, T::Set[String]
|
8
|
+
const :request_headers, T::Set[String]
|
9
|
+
|
6
10
|
sig { returns(Spec) }
|
7
|
-
def self.make_default_spec
|
11
|
+
def self.make_default_spec
|
12
|
+
Spec.new(
|
13
|
+
content_type_recorders: Constants::DEFAULT_CONTENT_TYPE_RECORDERS,
|
14
|
+
request_content_types: Constants::DEFAULT_CONTENT_TYPES,
|
15
|
+
request_headers: Set[]
|
16
|
+
)
|
17
|
+
end
|
8
18
|
end
|
9
19
|
end
|
data/lib/vigiles/types.rb
CHANGED
@@ -28,5 +28,9 @@ module Vigiles
|
|
28
28
|
JsonPayload = T.type_alias { T::Hash[T.untyped, T.untyped] }
|
29
29
|
HtmlPayload = String
|
30
30
|
Payload = T.type_alias { T.any(JsonPayload, HtmlPayload) }
|
31
|
+
|
32
|
+
ContentTypeRecorder = T.type_alias do
|
33
|
+
T::Hash[String, T.proc.params(arg0: ActionDispatch::Response).returns(Vigiles::Archive::Conversation)]
|
34
|
+
end
|
31
35
|
end
|
32
36
|
end
|
data/lib/vigiles/version.rb
CHANGED
data/lib/vigiles.rb
CHANGED
@@ -7,6 +7,7 @@ require_relative "core_ext"
|
|
7
7
|
require "action_dispatch"
|
8
8
|
|
9
9
|
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
10
|
+
loader.inflector.inflect("uri" => "URI")
|
10
11
|
loader.ignore("#{__dir__}/generators")
|
11
12
|
loader.ignore("#{__dir__}/core_ext.rb")
|
12
13
|
loader.ignore("#{__dir__}/core_ext")
|
@@ -15,19 +16,35 @@ loader.setup
|
|
15
16
|
module Vigiles
|
16
17
|
extend T::Sig
|
17
18
|
|
18
|
-
sig {
|
19
|
-
def self.
|
20
|
-
|
19
|
+
sig { returns(Vigiles::Spec) }
|
20
|
+
def self.specification
|
21
|
+
@specification ||= T.let(
|
22
|
+
Vigiles::Spec.make_default_spec,
|
23
|
+
T.nilable(Vigiles::Spec)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { params(spec: Vigiles::Spec).returns(Vigiles::Spec) }
|
28
|
+
def self.specification=(spec)
|
29
|
+
@specification = spec
|
30
|
+
end
|
21
31
|
|
22
|
-
|
32
|
+
sig { params(req: ActionDispatch::Request, res: Rack::Response).returns(T.nilable(Archive::Conversation)) }
|
33
|
+
def self.maybe_record_conversation(req:, res:)
|
34
|
+
return unless should_record?(req)
|
35
|
+
|
36
|
+
Archive.record_conversation(req:, res:)
|
23
37
|
rescue Archive::UnrecordableRequestError
|
24
38
|
nil
|
25
39
|
end
|
26
40
|
|
27
41
|
sig { params(blk: T.untyped).void }
|
28
42
|
def self.configure(&blk)
|
29
|
-
|
30
|
-
|
43
|
+
blk.call(specification)
|
44
|
+
|
45
|
+
# TODO(yaw, 2024-06-15): ensure that the specification is valid.
|
46
|
+
# ensure that for every content type a recorder is configured. otherwise
|
47
|
+
# assign the general recorder for unknown content types.
|
31
48
|
end
|
32
49
|
|
33
50
|
sig { params(request: ActionDispatch::Request).returns(T::Boolean) }
|
@@ -35,10 +52,8 @@ module Vigiles
|
|
35
52
|
!request.nil?
|
36
53
|
end
|
37
54
|
|
38
|
-
sig { params(
|
39
|
-
private_class_method def self.should_record?(
|
40
|
-
|
41
|
-
|
42
|
-
content_type_match?(request)
|
55
|
+
sig { params(req: ActionDispatch::Request).returns(T::Boolean) }
|
56
|
+
private_class_method def self.should_record?(req)
|
57
|
+
content_type_match?(req)
|
43
58
|
end
|
44
59
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vigiles
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.pre.
|
4
|
+
version: 0.1.0.pre.beta6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yaw Boakye
|
@@ -205,6 +205,7 @@ files:
|
|
205
205
|
- README.md
|
206
206
|
- Rakefile
|
207
207
|
- lib/core_ext.rb
|
208
|
+
- lib/core_ext/action_dispatch/request.rb
|
208
209
|
- lib/core_ext/object.rb
|
209
210
|
- lib/generators/templates/archive_conversation_migration.rb.erb
|
210
211
|
- lib/generators/templates/initializer.rb
|
@@ -216,9 +217,13 @@ files:
|
|
216
217
|
- lib/vigiles/archive/conversation.rb
|
217
218
|
- lib/vigiles/archive/extras.rb
|
218
219
|
- lib/vigiles/archive/metadata.rb
|
220
|
+
- lib/vigiles/archive/parameter.rb
|
219
221
|
- lib/vigiles/archive/request.rb
|
220
222
|
- lib/vigiles/archive/response.rb
|
221
223
|
- lib/vigiles/constants.rb
|
224
|
+
- lib/vigiles/conversation_recorder.rb
|
225
|
+
- lib/vigiles/conversation_recorders/application_json.rb
|
226
|
+
- lib/vigiles/conversation_recorders/unknown.rb
|
222
227
|
- lib/vigiles/middleware/record_conversation.rb
|
223
228
|
- lib/vigiles/spec.rb
|
224
229
|
- lib/vigiles/types.rb
|