vigiles 0.1.0.pre.beta5 → 0.1.0.pre.beta6
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/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 +1 -1
- data/lib/vigiles.rb +25 -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
@@ -16,19 +16,35 @@ loader.setup
|
|
16
16
|
module Vigiles
|
17
17
|
extend T::Sig
|
18
18
|
|
19
|
-
sig {
|
20
|
-
def self.
|
21
|
-
|
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
|
22
31
|
|
23
|
-
|
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:)
|
24
37
|
rescue Archive::UnrecordableRequestError
|
25
38
|
nil
|
26
39
|
end
|
27
40
|
|
28
41
|
sig { params(blk: T.untyped).void }
|
29
42
|
def self.configure(&blk)
|
30
|
-
|
31
|
-
|
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.
|
32
48
|
end
|
33
49
|
|
34
50
|
sig { params(request: ActionDispatch::Request).returns(T::Boolean) }
|
@@ -36,10 +52,8 @@ module Vigiles
|
|
36
52
|
!request.nil?
|
37
53
|
end
|
38
54
|
|
39
|
-
sig { params(
|
40
|
-
private_class_method def self.should_record?(
|
41
|
-
|
42
|
-
|
43
|
-
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)
|
44
58
|
end
|
45
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
|