vigiles 0.1.0.pre.beta7 → 0.1.0.pre.beta9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ce9b4330b1d4ad0334d37f23d154149d01580c47f3f3c32d012f4b094244fac
4
- data.tar.gz: 11aa9a2571cf62454937a3e61b01b7de70bb702255b952d46f533d8831572ccd
3
+ metadata.gz: 7c95b320d216c89bd72c728b2b0402cae897d984554fd6c41e66739eac544e9b
4
+ data.tar.gz: 55bd64c8191703e6d5b6aac98945c54fd9d06a65b5b094e2c338ad64c9641fb8
5
5
  SHA512:
6
- metadata.gz: 07be4f5abcbfae46edd0c480a6b3766924979990c065b7833c41c50ff6ff6401ab8b0798a643d5da339cb3a71e885be62af6034d774a2d400befb6cdf9db3e61
7
- data.tar.gz: dfe33dbe27d11e5ac0604538c803158315e5fcdd61726ef0107734dc61aa53eff04f7e6e769c2dae6d1fa5d534682d0d8895eec90ef1cd5d8e8d926157474a56
6
+ metadata.gz: e98f9cf83934a297eed7ac621f0200b118610690ccd1c9f51279759edce9bfa0db23bd4161adcd762a891b7a3aad6c3606d3e2d66b5ae566615c971ee3bd6460
7
+ data.tar.gz: 8b90d7d720d5104d3280aefbf6065892f24d677489ae6c096bab31a887fbc4237ac76765f0425a16ed011aaee2dba4c2828f90a6411d6cc6dff95e761eddf943
@@ -35,12 +35,21 @@ module Vigiles
35
35
  const :url, T.any(URI::HTTPS, URI::HTTP)
36
36
  const :id, String
37
37
 
38
+ sig { params(header_key: String).returns(String) }
39
+ private_class_method def self.best_effort_unfuck_http_header(header_key)
40
+ (header_key.starts_with?("HTTP_") ? T.must(header_key[5..]) : header_key)
41
+ .split(/_/)
42
+ .map(&:titlecase)
43
+ .join("-")
44
+ end
45
+
38
46
  sig { params(request: ActionDispatch::Request).returns(Request) }
39
47
  def self.from(request)
40
- preferred_headers = Vigiles.specification.request_headers
48
+ preferred_headers = Vigiles.spec.request_headers
41
49
  available_headers = request.original_headers
42
50
  recorded_headers = (available_headers if preferred_headers.empty?)
43
51
  recorded_headers ||= preferred_headers.to_h { |h| [h, available_headers[h]] }
52
+ unfucked_headers = recorded_headers.transform_keys { best_effort_unfuck_http_header _1 }
44
53
 
45
54
  Request.new(
46
55
  content_type: request.content_type || (raise InvalidParameterError, "content_type"),
@@ -48,7 +57,7 @@ module Vigiles
48
57
  timestamp: DateTime.now,
49
58
  remote_ip: IPAddr.new(request.remote_ip),
50
59
  protocol: request.protocol,
51
- headers: recorded_headers,
60
+ headers: unfucked_headers,
52
61
  origin: request.origin || "unknown_origin_url",
53
62
  payload: request.body.read,
54
63
  http_method: Types::HttpMethod.deserialize(request.method),
@@ -18,66 +18,14 @@ module Vigiles
18
18
 
19
19
  sig { params(req: ActionDispatch::Request, res: Rack::Response).returns(T.nilable(Conversation)) }
20
20
  def self.record_conversation(req:, res:)
21
- # preferring to call `response.request` instead of preparing a new
22
- # request (via `ActionDispatch::Request.new(env)` and passing it
23
- # as an argument to this method because we can always recover the
24
- # specific request that elicited a given response, according to
25
- # https://github.com/rails/rails/blob/cacb8475a9d4373c0db437e7be4905685f03cefa/actionpack/lib/action_dispatch/http/response.rb#L53
26
- response = Response.from(res)
27
- metadata = Metadata.from(req.env)
28
- request = Request.from(req)
29
- extras = Extras.from(req.env)
30
-
31
- case (content_type = request.content_type)
32
- when ContentType::ApplicationJson.serialize then record_json_conversation(request:, response:, metadata:, extras:)
33
- when ContentType::TextHtml.serialize then record_html_conversation(request:, response:, metadata:, extras:)
34
- else record_conversation_with_unknown_content_type(request:, response:, metadata:, extras:)
21
+ content_type = req.content_type
22
+ if (recorder = Vigiles.spec.recorders[content_type]).nil?
23
+ raise \
24
+ UnrecordableRequestError,
25
+ "no recorder configured for content type: #{content_type}"
35
26
  end
36
- rescue Request::InvalidParameterError => e
37
- raise UnrecordableRequestError, "#{e.parameter} considered invalid"
38
- end
39
-
40
- sig do
41
- params(
42
- request: Request,
43
- response: Response,
44
- metadata: Metadata,
45
- extras: Extras
46
- )
47
- .returns(T.nilable(Conversation))
48
- end
49
- private_class_method def self.record_json_conversation(request:, response:, metadata:, extras:)
50
- Conversation.create!(
51
- request_content_type: request.content_type,
52
- request_user_agent: request.user_agent,
53
- request_timestamp: request.timestamp,
54
- request_remote_ip: request.remote_ip,
55
- request_protocol: request.protocol,
56
- request_headers: request.headers,
57
- request_origin: request.origin,
58
- request_payload: request.payload,
59
- request_method: request.http_method.serialize,
60
- request_path: request.path,
61
- request_url: request.url,
62
- request_id: request.id,
63
- response_content_type: response.content_type,
64
- response_headers: response.headers,
65
- response_payload: response.payload,
66
- response_status: response.status
67
- )
68
- rescue => e
69
- end
70
-
71
- sig { params(request: Request, response: Response, metadata: Metadata, extras: Extras).returns(T.nilable(Conversation)) }
72
- private_class_method def self.record_html_conversation(request:, response:, metadata:, extras:); end
73
27
 
74
- sig { params(request: Request, response: Response, metadata: Metadata, extras: Extras).returns(T.nilable(Conversation)) }
75
- private_class_method def self.record_conversation_with_unknown_content_type(
76
- request:,
77
- response:,
78
- metadata:,
79
- extras:
80
- )
28
+ recorder.record(req:, res:)
81
29
  end
82
30
  end
83
31
  end
@@ -3,9 +3,24 @@
3
3
 
4
4
  module Vigiles
5
5
  class ConversationRecorder
6
+ class MisconfiguredRecorderError < StandardError
7
+ sig { returns(String) }
8
+ attr_reader :expected
9
+
10
+ sig { returns(String) }
11
+ attr_reader :actual
12
+
13
+ sig { params(expected: String, actual: String).void }
14
+ def initialize(expected:, actual:)
15
+ @expected = expected
16
+ @actual = actual
17
+ super
18
+ end
19
+ end
20
+
6
21
  abstract!
7
22
 
8
- sig { abstract.params(response: ActionDispatch::Response).returns(Archive::Conversation) }
9
- def record(response); end
23
+ sig { abstract.params(req: ActionDispatch::Request, res: Rack::Response).returns(Archive::Conversation) }
24
+ def record(req:, res:); end
10
25
  end
11
26
  end
@@ -6,8 +6,45 @@ module Vigiles
6
6
  class ApplicationJson < ConversationRecorder
7
7
  include Singleton
8
8
 
9
- sig { override.params(_response: ActionDispatch::Response).returns(Archive::Conversation) }
10
- def record(_response) = Archive::Conversation.new
9
+ ConversationRecorder = Vigiles::ConversationRecorder
10
+ ContentType = Vigiles::Types::ContentType
11
+ Conversation = Vigiles::Archive::Conversation
12
+ Response = Vigiles::Archive::Response
13
+ Metadata = Vigiles::Archive::Metadata
14
+ Request = Vigiles::Archive::Request
15
+ Extras = Vigiles::Archive::Extras
16
+
17
+ sig { override.params(req: ActionDispatch::Request, res: Rack::Response).returns(Archive::Conversation) }
18
+ def record(req:, res:)
19
+ unless req.content_type == ContentType::ApplicationJson.serialize
20
+ raise ConversationRecorder::MisconfiguredRecorderError.new(
21
+ expected: ContentType::ApplicationJson.serialize,
22
+ actual: req.content_type
23
+ )
24
+ end
25
+
26
+ response = Response.from(res)
27
+ request = Request.from(req)
28
+
29
+ Conversation.create!(
30
+ request_content_type: request.content_type,
31
+ request_user_agent: request.user_agent,
32
+ request_timestamp: request.timestamp,
33
+ request_remote_ip: request.remote_ip,
34
+ request_protocol: request.protocol,
35
+ request_headers: request.headers,
36
+ request_origin: request.origin,
37
+ request_payload: request.payload,
38
+ request_method: request.http_method.serialize,
39
+ request_path: request.path,
40
+ request_url: request.url,
41
+ request_id: request.id,
42
+ response_content_type: response.content_type,
43
+ response_headers: response.headers,
44
+ response_payload: response.payload,
45
+ response_status: response.status
46
+ )
47
+ end
11
48
  end
12
49
  end
13
50
  end
@@ -4,8 +4,10 @@
4
4
  module Vigiles
5
5
  module ConversationRecorders
6
6
  class Unknown < ConversationRecorder
7
- sig { override.params(_response: ActionDispatch::Response).returns(Archive::Conversation) }
8
- def record(_response) = Archive::Conversation.new
7
+ include Singleton
8
+
9
+ sig { override.params(req: ActionDispatch::Request, res: Rack::Response).returns(Archive::Conversation) }
10
+ def record(req:, res:) = Archive::Conversation.new
9
11
  end
10
12
  end
11
13
  end
data/lib/vigiles/spec.rb CHANGED
@@ -3,16 +3,16 @@
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]
6
+ const :request_content_types, T::Set[String]
7
+ const :request_headers, T::Set[String]
8
+ const :recorders, T::Hash[String, ConversationRecorder]
9
9
 
10
10
  sig { returns(Spec) }
11
11
  def self.make_default_spec
12
12
  Spec.new(
13
- content_type_recorders: Constants::DEFAULT_CONTENT_TYPE_RECORDERS,
14
13
  request_content_types: Constants::DEFAULT_CONTENT_TYPES,
15
- request_headers: Set[]
14
+ request_headers: Set[].freeze,
15
+ recorders: Constants::DEFAULT_CONTENT_TYPE_RECORDERS
16
16
  )
17
17
  end
18
18
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Vigiles
5
- VERSION = "0.1.0-beta7"
5
+ VERSION = "0.1.0-beta9"
6
6
  end
data/lib/vigiles.rb CHANGED
@@ -17,16 +17,16 @@ module Vigiles
17
17
  extend T::Sig
18
18
 
19
19
  sig { returns(Vigiles::Spec) }
20
- def self.specification
21
- @specification ||= T.let(
20
+ def self.spec
21
+ @spec ||= T.let(
22
22
  Vigiles::Spec.make_default_spec,
23
23
  T.nilable(Vigiles::Spec)
24
24
  )
25
25
  end
26
26
 
27
27
  sig { params(spec: Vigiles::Spec).returns(Vigiles::Spec) }
28
- def self.specification=(spec)
29
- @specification = spec
28
+ def self.spec=(spec)
29
+ @spec = spec
30
30
  end
31
31
 
32
32
  sig { params(req: ActionDispatch::Request, res: Rack::Response).returns(T.nilable(Archive::Conversation)) }
@@ -40,9 +40,9 @@ module Vigiles
40
40
 
41
41
  sig { params(blk: T.untyped).void }
42
42
  def self.configure(&blk)
43
- blk.call(specification)
43
+ blk.call(spec)
44
44
 
45
- # TODO(yaw, 2024-06-15): ensure that the specification is valid.
45
+ # TODO(yaw, 2024-06-15): ensure that the spec is valid.
46
46
  # ensure that for every content type a recorder is configured. otherwise
47
47
  # assign the general recorder for unknown content types.
48
48
  end
@@ -3,10 +3,10 @@
3
3
 
4
4
  require "action_dispatch"
5
5
  require "active_record"
6
+ require "logger"
6
7
  require "minitest/autorun"
7
8
  require "rails/generators"
8
9
  require "rails/generators/active_record"
9
10
  require "securerandom"
10
11
  require "sorbet-runtime"
11
- require "uri"
12
12
  require "zeitwerk"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vigiles
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.beta7
4
+ version: 0.1.0.pre.beta9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaw Boakye
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-24 00:00:00.000000000 Z
11
+ date: 2024-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sorbet-runtime