vigiles 0.1.0.pre.beta2

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +26 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +14 -0
  6. data/Rakefile +12 -0
  7. data/lib/core_ext/object.rb +7 -0
  8. data/lib/core_ext.rb +4 -0
  9. data/lib/generators/templates/archive_conversation_migration.rb.erb +41 -0
  10. data/lib/generators/templates/initializer.rb +6 -0
  11. data/lib/generators/vigiles/initializer_generator.rb +22 -0
  12. data/lib/generators/vigiles/install_generator.rb +25 -0
  13. data/lib/generators/vigiles/migration_generator.rb +32 -0
  14. data/lib/vigiles/archive/conversation.rb +12 -0
  15. data/lib/vigiles/archive/extras.rb +14 -0
  16. data/lib/vigiles/archive/metadata.rb +14 -0
  17. data/lib/vigiles/archive/request.rb +43 -0
  18. data/lib/vigiles/archive/response.rb +23 -0
  19. data/lib/vigiles/archive.rb +72 -0
  20. data/lib/vigiles/middleware/record_conversation.rb +47 -0
  21. data/lib/vigiles/spec.rb +9 -0
  22. data/lib/vigiles/types.rb +32 -0
  23. data/lib/vigiles/version.rb +6 -0
  24. data/lib/vigiles.rb +41 -0
  25. data/sorbet/config +4 -0
  26. data/sorbet/rbi/annotations/.gitattributes +1 -0
  27. data/sorbet/rbi/annotations/activemodel.rbi +89 -0
  28. data/sorbet/rbi/annotations/activerecord.rbi +92 -0
  29. data/sorbet/rbi/annotations/activesupport.rbi +421 -0
  30. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  31. data/sorbet/rbi/dsl/.gitattributes +1 -0
  32. data/sorbet/rbi/dsl/active_support/callbacks.rbi +22 -0
  33. data/sorbet/rbi/gems/.gitattributes +1 -0
  34. data/sorbet/rbi/gems/activemodel@7.0.5.rbi +8 -0
  35. data/sorbet/rbi/gems/activerecord@7.0.5.rbi +8 -0
  36. data/sorbet/rbi/gems/activesupport@7.0.5.rbi +14 -0
  37. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  38. data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +8 -0
  39. data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
  40. data/sorbet/rbi/gems/i18n@1.14.1.rbi +8 -0
  41. data/sorbet/rbi/gems/json@2.7.1.rbi +1561 -0
  42. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  43. data/sorbet/rbi/gems/minitest@5.22.2.rbi +1535 -0
  44. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  45. data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
  46. data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
  47. data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
  48. data/sorbet/rbi/gems/prism@0.24.0.rbi +31040 -0
  49. data/sorbet/rbi/gems/racc@1.7.3.rbi +161 -0
  50. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  51. data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
  52. data/sorbet/rbi/gems/rbi@0.1.9.rbi +3006 -0
  53. data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
  54. data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
  55. data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7036 -0
  56. data/sorbet/rbi/gems/rubocop-minitest@0.34.5.rbi +2576 -0
  57. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +328 -0
  58. data/sorbet/rbi/gems/rubocop-sorbet@0.7.4.rbi +1442 -0
  59. data/sorbet/rbi/gems/rubocop@1.60.2.rbi +57386 -0
  60. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  61. data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
  62. data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23136 -0
  63. data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3508 -0
  64. data/sorbet/rbi/gems/thor@1.3.0.rbi +4345 -0
  65. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +8 -0
  66. data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
  67. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  68. data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
  69. data/sorbet/rbi/todo.rbi +16 -0
  70. data/sorbet/tapioca/config.yml +13 -0
  71. data/sorbet/tapioca/require.rb +12 -0
  72. data/vigiles.gemspec +48 -0
  73. metadata +300 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d020325255cc9a0513dbc92c0460fce06536b59a18c0fbe7c3d5b549f40aa0d2
4
+ data.tar.gz: '0096c987ef42a2b552667ed494c8a20601ff8355e647048169a5445afc33d337'
5
+ SHA512:
6
+ metadata.gz: 84fde40e68c78440e04b7aebb79a07d0876d8b5c5c24b5499cfcc2915a3e34776450602d0531474c3588e631090f4acce3baeef6c2fef760d8d3b75678e3d059
7
+ data.tar.gz: 29f49773ea835792322a44e8620320d2be31fdcdce3cd286e317a30a57348aa6b1b3c61c75e2016803b5fbdb5bdba6882dad5c647446bbec4c992b7bf375d8f2
data/.rubocop.yml ADDED
@@ -0,0 +1,26 @@
1
+ require:
2
+ - rubocop-rake
3
+ - rubocop-minitest
4
+ - rubocop-sorbet
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 2.7
8
+ Exclude:
9
+ - "**/generators/**/*"
10
+ - "**/sorbet/**/*"
11
+ - "**/bin/**/*"
12
+
13
+ Style/StringLiterals:
14
+ Enabled: true
15
+ EnforcedStyle: double_quotes
16
+
17
+ Style/StringLiteralsInInterpolation:
18
+ Enabled: true
19
+ EnforcedStyle: double_quotes
20
+
21
+ Style/AccessModifierDeclarations:
22
+ Enabled: true
23
+ EnforcedStyle: inline
24
+
25
+ Layout/LineLength:
26
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-03-05
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Yaw Boakye
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/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # Vigiles
2
+
3
+ Sometimes it helps to be able to examine, in totality, the request your
4
+ Rails application received, and the specific response it elicited. Very handy
5
+ when you're debugging. Also very handy when your engineers have to assist
6
+ non-technical customer support staff. That aside, it's generally a great
7
+ visualization of the black box abstraction, where your entire application is
8
+ seen as one logical function, taking input (request) and producing output
9
+ (response).
10
+
11
+ At the moment, this gem is unable to conduct you on your research journey from a
12
+ given request to its response: you'd have to call on your logs and other
13
+ observability systems you've engaged. But it's easy—rather,
14
+ possible—to see how we can bring this in house, with a little tracing.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,7 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ class Object
5
+ extend T::Sig
6
+ extend T::Helpers
7
+ end
data/lib/core_ext.rb ADDED
@@ -0,0 +1,4 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "core_ext/object"
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateVigilesArchiveConversationsTable < ActiveRecord::Migration<%= migration_version %>
4
+
5
+ def change
6
+ create_table :vigiles_archive_conversations do |t|
7
+ t.text :request_content_type, null: false
8
+ t.text :request_user_agent, null: false
9
+ t.datetime :request_timestamp, null: false
10
+ t.inet :request_remote_ip, null: false
11
+ t.text :request_protocol, null: false
12
+ t.jsonb :request_headers, null: false, default: {}
13
+
14
+ # if the request doesn't origin from an identifiable origin,
15
+ # like a website, this value is typically nil. in which case
16
+ # we can substitute with `request_user_agent`.
17
+ t.text :request_origin
18
+ t.jsonb :request_payload, null: false, default: {}
19
+ t.text :request_method, null: false
20
+ t.text :request_path, null: false
21
+ t.text :request_url, null: false
22
+ t.text :request_id, null: false
23
+
24
+ t.text :response_content_type, null: false
25
+ t.jsonb :response_headers, null: false
26
+ t.jsonb :response_payload, null: false
27
+ t.integer :response_status, null: false
28
+
29
+ # conversation `metadata` holds all other information that might be
30
+ # useful when inspecting the conversation. for example, metadata
31
+ # could hold user information (id, rate limiting usage, etc), or
32
+ # the (rack) environment within which rails handled the request.
33
+ t.jsonb :metadata, null: false, default: {}
34
+
35
+ # `extras` are added here as convenience. it's a grab bag of all
36
+ # data that wouldn't cleanly fit in any of the above columns. it's
37
+ # also a mixed bag of request and response data.
38
+ t.jsonb :extras, null: false, default: {}
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ Vigiles.configure do |spec|
5
+
6
+ end
@@ -0,0 +1,22 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Vigiles
5
+ module Generators
6
+ class InitializerGenerator < ::Rails::Generators::Base
7
+ desc <<~DOC.squish
8
+ This generator creates the vigiles initializer file at the
9
+ `config/initializers/vigiles.rb` path and sets up a sane
10
+ default configuration.
11
+ DOC
12
+
13
+ source_root File.expand_path("../templates", __dir__)
14
+
15
+ def copy_initializer_file
16
+ copy_file \
17
+ "initializer.rb",
18
+ "config/initializers/vigiles.rb"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "initializer_generator"
5
+
6
+ module Vigiles
7
+ module Generators
8
+ class InstallGenerator < ::Rails::Generators::Base
9
+ SUB_GENERATORS = Set.new(
10
+ %w[
11
+ vigiles:initializer
12
+ vigiles:migration
13
+ ]
14
+ ).freeze
15
+
16
+ source_root File.expand_path("../templates", __dir__)
17
+
18
+ def install_vigiles
19
+ SUB_GENERATORS.each do |generator_task|
20
+ invoke generator_task
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "rails/generators"
5
+ require "rails/generators/active_record"
6
+
7
+ module Vigiles
8
+ module Generators
9
+ class MigrationGenerator < ::Rails::Generators::Base
10
+ desc <<~DOC.squish
11
+ DOC
12
+
13
+ include ::Rails::Generators::Migration
14
+
15
+ source_root File.expand_path("../templates", __dir__)
16
+
17
+ def install
18
+ migration_template(
19
+ "archive_conversation_migration.rb.erb",
20
+ "db/migrate/create_vigiles_archive_conversations_table.rb",
21
+ migration_version:
22
+ )
23
+ end
24
+
25
+ def migration_version = "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
26
+
27
+ def self.next_migration_number(dirname)
28
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "active_record"
5
+
6
+ module Vigiles
7
+ module Archive
8
+ class Conversation < ::ActiveRecord::Base
9
+ self.table_name = "vigiles_archive_conversations"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Vigiles
5
+ module Archive
6
+ class Extras < T::Struct;
7
+ const :request_env, T.untyped
8
+
9
+ def self.from(request_env)
10
+ Extras.new(request_env:)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Vigiles
5
+ module Archive
6
+ class Metadata < T::Struct
7
+ const :request_env, T.untyped
8
+
9
+ def self.from(request_env)
10
+ Metadata.new(request_env:)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "uri"
5
+
6
+ module Vigiles
7
+ module Archive
8
+ extend T::Sig
9
+
10
+ class Request < T::Struct
11
+ const :content_type, String
12
+ const :http_method, Types::HttpMethod
13
+ const :user_agent, String
14
+ const :timestamp, DateTime
15
+ const :remote_ip, IPAddr
16
+ const :protocol, String
17
+ const :headers, Types::Headers
18
+ const :origin, String
19
+ const :payload, Types::Payload
20
+ const :path, String
21
+ const :url, T.any(URI::HTTPS, URI::HTTP)
22
+ const :id, String
23
+
24
+ sig { params(request: ActionDispatch::Request).returns(Request) }
25
+ def self.from(request)
26
+ Request.new(
27
+ content_type: request.content_type,
28
+ user_agent: request.user_agent,
29
+ timestamp: DateTime.now,
30
+ remote_ip: IPAddr.new(request.remote_ip),
31
+ protocol: request.protocol,
32
+ headers: {},
33
+ origin: request.origin || "n/a",
34
+ payload: request.body.read,
35
+ http_method: Types::HttpMethod.deserialize(request.method),
36
+ path: request.path,
37
+ url: URI.parse(request.url),
38
+ id: request.request_id
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Vigiles
5
+ module Archive
6
+ class Response < T::Struct
7
+ const :content_type, String
8
+ const :headers, Types::Headers
9
+ const :payload, Types::Payload
10
+ const :status, Integer
11
+
12
+ sig { params(response: ActionDispatch::Response).returns(Response) }
13
+ def self.from(response)
14
+ Response.new(
15
+ content_type: response.content_type,
16
+ headers: response.headers.as_json,
17
+ payload: response.body,
18
+ status: response.status
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,72 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "action_dispatch"
5
+
6
+ module Vigiles
7
+ module Archive
8
+ extend T::Sig
9
+
10
+ Types = Vigiles::Types
11
+ ContentType = Types::ContentType
12
+
13
+ sig { params(ad_response: ActionDispatch::Response).returns(T.nilable(Conversation)) }
14
+ def self.record_conversation(ad_response)
15
+ # preferring to call `response.request` instead of preparing a new
16
+ # request (via `ActionDispatch::Request.new(env)` and passing it
17
+ # as an argument to this method because we can always recover the
18
+ # specific request that elicited a given response, according to
19
+ # https://github.com/rails/rails/blob/cacb8475a9d4373c0db437e7be4905685f03cefa/actionpack/lib/action_dispatch/http/response.rb#L53
20
+ ad_request = ad_response.request
21
+ response = Response.from(ad_response)
22
+ metadata = Metadata.from(ad_request.env)
23
+ request = Request.from(ad_request)
24
+ extras = Extras.from(ad_request.env)
25
+
26
+ case (content_type = request.content_type)
27
+ when ContentType::ApplicationJson.serialize then record_json_conversation(request:, response:, metadata:, extras:)
28
+ when ContentType::TextHtml.serialize then record_html_conversation(request:, response:, metadata:, extras:)
29
+ else record_conversation_with_unknown_content_type(request:)
30
+ end
31
+ end
32
+
33
+ sig do
34
+ params(
35
+ request: Request,
36
+ response: Response,
37
+ metadata: Metadata,
38
+ extras: Extras
39
+ )
40
+ .returns(T.nilable(Conversation))
41
+ end
42
+ private_class_method def self.record_json_conversation(request:, response:, metadata:, extras:)
43
+ Conversation.create!(
44
+ request_content_type: request.content_type,
45
+ request_user_agent: request.user_agent,
46
+ request_timestamp: request.timestamp,
47
+ request_remote_ip: request.remote_ip,
48
+ request_protocol: request.protocol,
49
+ request_headers: request.headers,
50
+ request_origin: request.origin,
51
+ request_payload: request.payload,
52
+ request_method: request.http_method.serialize,
53
+ request_path: request.path,
54
+ request_url: request.url,
55
+ request_id: request.id,
56
+ response_content_type: response.content_type,
57
+ response_headers: response.headers,
58
+ response_payload: JSON.load(response.payload),
59
+ response_status: response.status
60
+ )
61
+ rescue => e
62
+ end
63
+ private_class_method def self.record_html_conversation(request:, response:, metadata:, extras:); end
64
+ private_class_method def self.record_conversation_with_unknown_content_type(
65
+ request:,
66
+ response:,
67
+ metadata:,
68
+ extras:
69
+ )
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rack"
5
+
6
+ module Vigiles
7
+ module Middleware
8
+ class RecordConversation
9
+ class Options < T::Struct
10
+ const :logger, T.untyped
11
+
12
+ sig { returns(Options) }
13
+ def self.defaults
14
+ Options.new(
15
+ logger: Rails.logger
16
+ )
17
+ end
18
+ end
19
+
20
+ sig { params(app: T.untyped, options: Options).void }
21
+ def initialize(app, options = Options.defaults)
22
+ @app = app
23
+ @logger = options.logger
24
+ end
25
+
26
+ sig { params(env: T.untyped).returns(T.untyped) }
27
+ def call(env)
28
+ record_conversation do
29
+ @app.call(env)
30
+ end
31
+ end
32
+
33
+ private def record_conversation(&blk)
34
+ rack_response = blk.call
35
+ _, _, body = rack_response
36
+ response = body.instance_variable_get(:@response)
37
+
38
+ convo = Vigiles.maybe_record_conversation(response)
39
+ @logger.info "conversation recorded: conversation_id=#{convo&.id}" if convo
40
+
41
+ rack_response
42
+ ensure
43
+ rack_response
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Vigiles
5
+ class Spec < T::Struct
6
+ sig { returns(Spec) }
7
+ def self.make_default_spec; end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Vigiles
5
+ module Types
6
+ extend T::Sig
7
+
8
+ class ContentType < T::Enum
9
+ enums do
10
+ ApplicationJson = new("application/json")
11
+ TextHtml = new("text/html")
12
+ Unknown = new
13
+ end
14
+ end
15
+
16
+ class HttpMethod < T::Enum
17
+ enums do
18
+ OPTIONS = new("OPTIONS")
19
+ DELETE = new("DELETE")
20
+ POST = new("POST")
21
+ HEAD = new("HEAD")
22
+ GET = new("GET")
23
+ PUT = new("PUT")
24
+ end
25
+ end
26
+
27
+ Headers = T.type_alias { T::Hash[String, T.untyped] }
28
+ JsonPayload = T.type_alias { T::Hash[T.untyped, T.untyped] }
29
+ HtmlPayload = String
30
+ Payload = T.type_alias { T.any(JsonPayload, HtmlPayload) }
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Vigiles
5
+ VERSION = "0.1.0-beta2"
6
+ end
data/lib/vigiles.rb ADDED
@@ -0,0 +1,41 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "zeitwerk"
5
+ require "sorbet-runtime"
6
+ require_relative "core_ext"
7
+
8
+ loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
9
+ loader.ignore("#{__dir__}/generators")
10
+ loader.ignore("#{__dir__}/core_ext.rb")
11
+ loader.ignore("#{__dir__}/core_ext")
12
+ loader.setup
13
+
14
+ module Vigiles
15
+ extend T::Sig
16
+
17
+ sig { params(response: ActionDispatch::Response).returns(T.nilable(Archive::Conversation)) }
18
+ def self.maybe_record_conversation(response)
19
+ return unless should_record?(response)
20
+
21
+ Archive.record_conversation(response)
22
+ end
23
+
24
+ sig { params(blk: T.untyped).void }
25
+ def self.configure(&blk)
26
+ default_spec = Vigiles::Spec.default_specification
27
+ blk.call(default_spec)
28
+ end
29
+
30
+ sig { params(request: ActionDispatch::Request).returns(T::Boolean) }
31
+ private_class_method def self.content_type_match?(request)
32
+ !request.nil?
33
+ end
34
+
35
+ sig { params(response: ActionDispatch::Response).returns(T::Boolean) }
36
+ private_class_method def self.should_record?(response)
37
+ request = response.request
38
+
39
+ content_type_match?(request)
40
+ end
41
+ end
data/sorbet/config ADDED
@@ -0,0 +1,4 @@
1
+ --dir
2
+ .
3
+ --ignore=tmp/
4
+ --ignore=vendor/
@@ -0,0 +1 @@
1
+ **/*.rbi linguist-vendored=true
@@ -0,0 +1,89 @@
1
+ # typed: true
2
+
3
+ # DO NOT EDIT MANUALLY
4
+ # This file was pulled from a central RBI files repository.
5
+ # Please run `bin/tapioca annotations` to update it.
6
+
7
+ class ActiveModel::Errors
8
+ Elem = type_member { { fixed: ActiveModel::Error } }
9
+
10
+ sig { params(attribute: T.any(Symbol, String)).returns(T::Array[String]) }
11
+ def [](attribute); end
12
+
13
+ sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(ActiveModel::Error) }
14
+ def add(attribute, type = :invalid, **options); end
15
+
16
+ sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(T::Boolean) }
17
+ def added?(attribute, type = :invalid, options = {}); end
18
+
19
+ sig { params(options: T.untyped).returns(T::Hash[T.untyped, T.untyped]) }
20
+ def as_json(options = nil); end
21
+
22
+ sig { returns(T::Array[Symbol]) }
23
+ def attribute_names; end
24
+
25
+ sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(T.nilable(T::Array[String])) }
26
+ def delete(attribute, type = nil, **options); end
27
+
28
+ sig { returns(T::Hash[Symbol, T::Array[T::Hash[Symbol, T.untyped]]]) }
29
+ def details; end
30
+
31
+ sig { returns(T::Array[Elem]) }
32
+ def errors; end
33
+
34
+ sig { params(attribute: T.any(Symbol, String), message: String).returns(String) }
35
+ def full_message(attribute, message); end
36
+
37
+ sig { returns(T::Array[String]) }
38
+ def full_messages; end
39
+
40
+ sig { params(attribute: T.any(Symbol, String)).returns(T::Array[String]) }
41
+ def full_messages_for(attribute); end
42
+
43
+ sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(String) }
44
+ def generate_message(attribute, type = :invalid, options = {}); end
45
+
46
+ sig { returns(T::Hash[Symbol, T::Array[ActiveModel::Error]]) }
47
+ def group_by_attribute; end
48
+
49
+ sig { params(attribute: T.any(Symbol, String)).returns(T::Boolean) }
50
+ def has_key?(attribute); end
51
+
52
+ sig { params(error: ActiveModel::Error, override_options: T.untyped).returns(T::Array[ActiveModel::Error]) }
53
+ def import(error, override_options = {}); end
54
+
55
+ sig { params(attribute: T.any(Symbol, String)).returns(T::Boolean) }
56
+ def include?(attribute); end
57
+
58
+ sig { params(attribute: T.any(Symbol, String)).returns(T::Boolean) }
59
+ def key?(attribute); end
60
+
61
+ sig { params(other: T.untyped).returns(T::Array[ActiveModel::Error]) }
62
+ def merge!(other); end
63
+
64
+ sig { returns(T::Hash[Symbol, T::Array[String]]) }
65
+ def messages; end
66
+
67
+ sig { params(attribute: T.any(Symbol, String)).returns(T::Array[String]) }
68
+ def messages_for(attribute); end
69
+
70
+ sig { returns(T::Array[Elem]) }
71
+ def objects; end
72
+
73
+ sig { params(attribute: T.any(Symbol, String), type: T.untyped).returns(T::Boolean) }
74
+ def of_kind?(attribute, type = :invalid); end
75
+
76
+ sig { returns(T::Array[String]) }
77
+ def to_a; end
78
+
79
+ sig { params(full_messages: T.untyped).returns(T::Hash[Symbol, T::Array[String]]) }
80
+ def to_hash(full_messages = false); end
81
+
82
+ sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(T::Array[ActiveModel::Error]) }
83
+ def where(attribute, type = nil, **options); end
84
+ end
85
+
86
+ module ActiveModel::Validations
87
+ sig { returns(ActiveModel::Errors) }
88
+ def errors; end
89
+ end