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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e4ea4b46169baf532079483c8827ee4d96f44165e3b5b1f4c021ac99a9a82bd
4
- data.tar.gz: 8735c9877784c68115ea803aea9bad226d7926528208cbad19fef1f5d0d00b60
3
+ metadata.gz: 6ad47542ac3fa029cef8be2b99b104eb7995abf67765c8e0582426249f2d5a5a
4
+ data.tar.gz: a468738d5ef0ad815be016420198ef939b254997509b25228f5b3779fa51976f
5
5
  SHA512:
6
- metadata.gz: 8d8d94f910b17027a7db1776db0f1ec84b197a2a47e64de039cfab327af8d59cbfd7037ada8fa403f2c04cf4ef2f7697cb206da86529deab44d4f90bb7c92919
7
- data.tar.gz: 0ed649f1d6fe5ef184d286eba622c7fbade046686bf56d7e8f0f35e06aad77820fa64c5434b64f68663537791e80ef5036c7ee228a79267463571e1319457107
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
@@ -2,3 +2,4 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative "core_ext/object"
5
+ require_relative "core_ext/action_dispatch/request"
@@ -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: strict
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(response: ActionDispatch::Response).returns(Response) }
13
- def self.from(response)
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
- content_type: response.content_type,
16
- headers: response.headers.as_json,
17
- payload: response.body,
18
- status: response.status
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
@@ -16,18 +16,17 @@ module Vigiles
16
16
  end
17
17
  end
18
18
 
19
- sig { params(ad_response: ActionDispatch::Response).returns(T.nilable(Conversation)) }
20
- def self.record_conversation(ad_response)
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
- ad_request = ad_response.request
27
- response = Response.from(ad_response)
28
- metadata = Metadata.from(ad_request.env)
29
- request = Request.from(ad_request)
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: JSON.load(response.payload),
65
+ response_payload: response.payload,
67
66
  response_status: response.status
68
67
  )
69
68
  rescue => e
@@ -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,11 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Vigiles
5
+ class ConversationRecorder
6
+ abstract!
7
+
8
+ sig { abstract.params(response: ActionDispatch::Response).returns(Archive::Conversation) }
9
+ def record(response); end
10
+ end
11
+ 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
- record_conversation do
16
- @app.call(env)
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
- rack_response = blk.call
23
- _, _, body = rack_response
24
- response = body.instance_variable_get(:@response)
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
- rack_response
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 = Spec.new
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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Vigiles
5
- VERSION = "0.1.0-beta5"
5
+ VERSION = "0.1.0-beta6"
6
6
  end
data/lib/vigiles.rb CHANGED
@@ -16,19 +16,35 @@ loader.setup
16
16
  module Vigiles
17
17
  extend T::Sig
18
18
 
19
- sig { params(response: ActionDispatch::Response).returns(T.nilable(Archive::Conversation)) }
20
- def self.maybe_record_conversation(response)
21
- return unless should_record?(response)
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
- Archive.record_conversation(response)
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
- default_spec = Vigiles::Spec.make_default_spec
31
- blk.call(default_spec)
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(response: ActionDispatch::Response).returns(T::Boolean) }
40
- private_class_method def self.should_record?(response)
41
- request = response.request
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.beta5
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