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.
- checksums.yaml +7 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +14 -0
- data/Rakefile +12 -0
- data/lib/core_ext/object.rb +7 -0
- data/lib/core_ext.rb +4 -0
- data/lib/generators/templates/archive_conversation_migration.rb.erb +41 -0
- data/lib/generators/templates/initializer.rb +6 -0
- data/lib/generators/vigiles/initializer_generator.rb +22 -0
- data/lib/generators/vigiles/install_generator.rb +25 -0
- data/lib/generators/vigiles/migration_generator.rb +32 -0
- data/lib/vigiles/archive/conversation.rb +12 -0
- data/lib/vigiles/archive/extras.rb +14 -0
- data/lib/vigiles/archive/metadata.rb +14 -0
- data/lib/vigiles/archive/request.rb +43 -0
- data/lib/vigiles/archive/response.rb +23 -0
- data/lib/vigiles/archive.rb +72 -0
- data/lib/vigiles/middleware/record_conversation.rb +47 -0
- data/lib/vigiles/spec.rb +9 -0
- data/lib/vigiles/types.rb +32 -0
- data/lib/vigiles/version.rb +6 -0
- data/lib/vigiles.rb +41 -0
- data/sorbet/config +4 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activemodel.rbi +89 -0
- data/sorbet/rbi/annotations/activerecord.rbi +92 -0
- data/sorbet/rbi/annotations/activesupport.rbi +421 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/dsl/.gitattributes +1 -0
- data/sorbet/rbi/dsl/active_support/callbacks.rbi +22 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/activemodel@7.0.5.rbi +8 -0
- data/sorbet/rbi/gems/activerecord@7.0.5.rbi +8 -0
- data/sorbet/rbi/gems/activesupport@7.0.5.rbi +14 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +8 -0
- data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
- data/sorbet/rbi/gems/i18n@1.14.1.rbi +8 -0
- data/sorbet/rbi/gems/json@2.7.1.rbi +1561 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
- data/sorbet/rbi/gems/minitest@5.22.2.rbi +1535 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
- data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
- data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
- data/sorbet/rbi/gems/prism@0.24.0.rbi +31040 -0
- data/sorbet/rbi/gems/racc@1.7.3.rbi +161 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
- data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
- data/sorbet/rbi/gems/rbi@0.1.9.rbi +3006 -0
- data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
- data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
- data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7036 -0
- data/sorbet/rbi/gems/rubocop-minitest@0.34.5.rbi +2576 -0
- data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +328 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.7.4.rbi +1442 -0
- data/sorbet/rbi/gems/rubocop@1.60.2.rbi +57386 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
- data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
- data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23136 -0
- data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3508 -0
- data/sorbet/rbi/gems/thor@1.3.0.rbi +4345 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +8 -0
- data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
- data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
- data/sorbet/rbi/todo.rbi +16 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +12 -0
- data/vigiles.gemspec +48 -0
- 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
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
data/lib/core_ext.rb
ADDED
@@ -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,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,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
|
data/lib/vigiles/spec.rb
ADDED
@@ -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
|
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 @@
|
|
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
|