vigiles 0.1.0.pre.beta2

Sign up to get free protection for your applications and to get access to all the features.
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