bidi2pdf 0.1.14 → 0.1.16
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 +4 -4
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +69 -2
- data/README.md +258 -10
- data/docker/Dockerfile +4 -0
- data/docker/Dockerfile.slim +5 -1
- data/lib/bidi2pdf/bidi/browser_tab.rb +45 -4
- data/lib/bidi2pdf/bidi/buffered_web_socket_client.rb +178 -0
- data/lib/bidi2pdf/bidi/client.rb +12 -5
- data/lib/bidi2pdf/bidi/commands/base.rb +2 -0
- data/lib/bidi2pdf/bidi/network_event.rb +12 -3
- data/lib/bidi2pdf/bidi/network_events.rb +9 -1
- data/lib/bidi2pdf/bidi/session.rb +1 -1
- data/lib/bidi2pdf/chromedriver_manager.rb +10 -6
- data/lib/bidi2pdf/cli/json_output.rb +30 -0
- data/lib/bidi2pdf/cli.rb +559 -17
- data/lib/bidi2pdf/diagnose.rb +119 -0
- data/lib/bidi2pdf/error_codes.rb +59 -0
- data/lib/bidi2pdf/exit_codes.rb +44 -0
- data/lib/bidi2pdf/launcher.rb +23 -0
- data/lib/bidi2pdf/manifest.rb +65 -0
- data/lib/bidi2pdf/notifications/json_subscriber.rb +78 -0
- data/lib/bidi2pdf/notifications/logging_subscriber.rb +2 -0
- data/lib/bidi2pdf/pdf_inspection.rb +62 -0
- data/lib/bidi2pdf/recipe/loader.rb +44 -0
- data/lib/bidi2pdf/recipe/runner.rb +229 -0
- data/lib/bidi2pdf/recipe/schema_shape.rb +228 -0
- data/lib/bidi2pdf/recipe/validator.rb +138 -0
- data/lib/bidi2pdf/recipe.rb +83 -0
- data/lib/bidi2pdf/result.rb +67 -0
- data/lib/bidi2pdf/result_collector.rb +150 -0
- data/lib/bidi2pdf/schema.rb +382 -0
- data/lib/bidi2pdf/session_runner.rb +42 -0
- data/lib/bidi2pdf/session_warmer.rb +377 -0
- data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rb +4 -2
- data/lib/bidi2pdf/version.rb +1 -1
- data/lib/bidi2pdf.rb +114 -9
- data/sig/bidi2pdf/bidi/browser_tab.rbs +17 -0
- data/sig/bidi2pdf/bidi/buffered_web_socket_client.rbs +81 -0
- data/sig/bidi2pdf/bidi/client.rbs +10 -2
- data/sig/bidi2pdf/bidi/network_event.rbs +11 -1
- data/sig/bidi2pdf/bidi/session.rbs +1 -1
- data/sig/bidi2pdf/chromedriver_manager.rbs +4 -0
- data/sig/bidi2pdf/cli/json_output.rbs +19 -0
- data/sig/bidi2pdf/cli.rbs +112 -2
- data/sig/bidi2pdf/diagnose.rbs +30 -0
- data/sig/bidi2pdf/error_codes.rbs +21 -0
- data/sig/bidi2pdf/exit_codes.rbs +21 -0
- data/sig/bidi2pdf/launcher.rbs +9 -0
- data/sig/bidi2pdf/manifest.rbs +38 -0
- data/sig/bidi2pdf/notifications/json_subscriber.rbs +45 -0
- data/sig/bidi2pdf/pdf_inspection.rbs +33 -0
- data/sig/bidi2pdf/recipe/loader.rbs +20 -0
- data/sig/bidi2pdf/recipe/runner.rbs +92 -0
- data/sig/bidi2pdf/recipe/schema_shape.rbs +85 -0
- data/sig/bidi2pdf/recipe/validator.rbs +54 -0
- data/sig/bidi2pdf/recipe.rbs +72 -0
- data/sig/bidi2pdf/result.rbs +71 -0
- data/sig/bidi2pdf/result_collector.rbs +106 -0
- data/sig/bidi2pdf/schema.rbs +45 -0
- data/sig/bidi2pdf/session_runner.rbs +17 -0
- data/sig/bidi2pdf/session_warmer.rbs +221 -0
- data/sig/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rbs +1 -1
- data/sig/bidi2pdf/version.rbs +1 -1
- data/sig/bidi2pdf.rbs +84 -0
- data/tasks/release_credentials_check.rake +97 -0
- metadata +39 -4
|
@@ -40,14 +40,22 @@ module Bidi2pdf
|
|
|
40
40
|
|
|
41
41
|
# Starts the WebSocket client and establishes a connection.
|
|
42
42
|
#
|
|
43
|
-
# @return [
|
|
43
|
+
# @return [BufferedWebSocketClient] The WebSocket connection object.
|
|
44
44
|
def start: () -> untyped
|
|
45
45
|
|
|
46
|
-
# Checks if the WebSocket client has started.
|
|
46
|
+
# Checks if the WebSocket client has started. Unlike #open?, this never flips back to false on
|
|
47
|
+
# its own if the connection dies externally - it only reflects whether #start has run.
|
|
47
48
|
#
|
|
48
49
|
# @return [Boolean] True if the client has started, false otherwise.
|
|
49
50
|
def started?: () -> untyped
|
|
50
51
|
|
|
52
|
+
# Checks if the underlying WebSocket connection is actually open right now - false before
|
|
53
|
+
# #start, after #close, or if the reader thread noticed the connection died on its own (a
|
|
54
|
+
# real liveness signal, not just an internal "did we ever start" flag).
|
|
55
|
+
#
|
|
56
|
+
# @return [Boolean] True if the underlying socket reports itself open.
|
|
57
|
+
def open?: () -> untyped
|
|
58
|
+
|
|
51
59
|
# Waits until the WebSocket connection is open.
|
|
52
60
|
#
|
|
53
61
|
# @param [Integer] timeout The timeout duration in seconds.
|
|
@@ -15,6 +15,8 @@ module Bidi2pdf
|
|
|
15
15
|
|
|
16
16
|
@http_method: untyped
|
|
17
17
|
|
|
18
|
+
@navigation: untyped
|
|
19
|
+
|
|
18
20
|
@end_timestamp: untyped
|
|
19
21
|
|
|
20
22
|
@bytes_received: untyped
|
|
@@ -37,9 +39,17 @@ module Bidi2pdf
|
|
|
37
39
|
|
|
38
40
|
attr_reader bytes_received: untyped
|
|
39
41
|
|
|
42
|
+
attr_reader navigation: untyped
|
|
43
|
+
|
|
40
44
|
STATE_MAP: { "network.responseStarted" => "started", "network.responseCompleted" => "completed", "network.fetchError" => "error" }
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
# @param [String, nil] navigation The BiDi navigation ID this request belongs to - present on
|
|
47
|
+
# the main-frame document request, letting a caller correlate a specific
|
|
48
|
+
# browsingContext.navigate call to the network event carrying its actual HTTP status. A
|
|
49
|
+
# sub-resource request (an image, a script, ...) has no real BiDi navigation ID and stays
|
|
50
|
+
# nil here - #id already distinguishes one event from another in logs, so this field is
|
|
51
|
+
# left alone rather than filled with a value the protocol never sent.
|
|
52
|
+
def initialize: (id: untyped, url: untyped, timestamp: untyped, timing: untyped, state: untyped, ?http_status_code: untyped?, ?http_method: untyped?, ?navigation: untyped?) -> void
|
|
43
53
|
|
|
44
54
|
def update_state: (untyped new_state, ?timestamp: untyped?, ?timing: untyped?, ?http_status_code: untyped?, ?bytes_received: untyped?) -> untyped
|
|
45
55
|
|
|
@@ -37,7 +37,7 @@ module Bidi2pdf
|
|
|
37
37
|
SUBSCRIBE_EVENTS: ::Array["script"]
|
|
38
38
|
|
|
39
39
|
# Default Chrome arguments for the session.
|
|
40
|
-
DEFAULT_CHROME_ARGS: ::Array["--allow-pre-commit-input" | "--disable-dev-shm-usage" | "--disable-gpu" | "--disable-popup-blocking" | "--disable-hang-monitor" | "--disable-background-networking" | "--disable-background-timer-throttling" | "--disable-client-side-phishing-detection" | "--disable-component-extensions-with-background-pages" | "--disable-crash-reporter" | "--disable-default-apps" | "--disable-infobars" | "--disable-ipc-flooding-protection" | "--disable-prompt-on-repost" | "--disable-renderer-backgrounding" | "--disable-search-engine-choice-screen" | "--disable-sync" | "--enable-automation" | "--export-tagged-pdf" | "--force-color-profile=srgb" | "--generate-pdf-document-outline" | "--metrics-recording-only" | "--no-first-run" | "--password-store=basic" | "--use-mock-keychain" | "--disable-backgrounding-occluded-windows" | "--disable-breakpad" | "--enable-features=PdfOopif" | "--disable-features=Translate,AcceptCHFrame,MediaRouter,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,IsolateSandboxedIframes" | "--disable-extensions
|
|
40
|
+
DEFAULT_CHROME_ARGS: ::Array["--allow-pre-commit-input" | "--disable-dev-shm-usage" | "--disable-gpu" | "--disable-popup-blocking" | "--disable-hang-monitor" | "--disable-background-networking" | "--disable-background-timer-throttling" | "--disable-client-side-phishing-detection" | "--disable-component-extensions-with-background-pages" | "--disable-crash-reporter" | "--disable-default-apps" | "--disable-infobars" | "--disable-ipc-flooding-protection" | "--disable-prompt-on-repost" | "--disable-renderer-backgrounding" | "--disable-search-engine-choice-screen" | "--disable-sync" | "--enable-automation" | "--export-tagged-pdf" | "--force-color-profile=srgb" | "--generate-pdf-document-outline" | "--metrics-recording-only" | "--no-first-run" | "--password-store=basic" | "--use-mock-keychain" | "--disable-backgrounding-occluded-windows" | "--disable-breakpad" | "--enable-features=PdfOopif" | "--disable-features=Translate,AcceptCHFrame,MediaRouter,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,IsolateSandboxedIframes" | "--disable-extensions"]
|
|
41
41
|
|
|
42
42
|
# @return [URI] The URI of the session.
|
|
43
43
|
attr_reader session_uri: untyped
|
|
@@ -59,6 +59,10 @@ module Bidi2pdf
|
|
|
59
59
|
|
|
60
60
|
def build_cmd: () -> untyped
|
|
61
61
|
|
|
62
|
+
# Chromedriver's own internal log verbosity is independent from this app's - defaults to
|
|
63
|
+
# mirroring Bidi2pdf.logger.level for backward compatibility, but Bidi2pdf.chromedriver_log_level
|
|
64
|
+
# lets a consumer quiet chromedriver's own (often much more verbose, e.g. full BiDi command/
|
|
65
|
+
# response dumps at its own INFO level) output without changing their app's own log level.
|
|
62
66
|
def chromedriver_log_level: () -> untyped
|
|
63
67
|
|
|
64
68
|
def user_data_dir_arg: () -> (untyped | ::String)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
class CLI < Thor
|
|
3
|
+
# Shared machinery for --json/--json-stream/--output - across render/diagnose/run: reserving
|
|
4
|
+
# stdout for exactly one machine-readable payload, and mapping a Result to a process exit
|
|
5
|
+
# status.
|
|
6
|
+
module JsonOutput
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# Bidi2pdf.logger (and friends) default to $stdout (see lib/bidi2pdf.rb), so a
|
|
10
|
+
# --json/--output - render has to redirect them for its duration or every log line would
|
|
11
|
+
# land in the same stream as the JSON document / PDF bytes it is trying to keep pure.
|
|
12
|
+
# Logger#reopen swaps the destination in place, so nothing else holding a reference to
|
|
13
|
+
# these loggers needs to know.
|
|
14
|
+
def reserve_stdout_for_machine_output: () { () -> untyped } -> untyped
|
|
15
|
+
|
|
16
|
+
def exit_for_result: (untyped result) -> untyped
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/sig/bidi2pdf/cli.rbs
CHANGED
|
@@ -1,21 +1,131 @@
|
|
|
1
1
|
module Bidi2pdf
|
|
2
2
|
# rubocop:disable Metrics/AbcSize
|
|
3
3
|
class CLI < Thor
|
|
4
|
+
@run_launcher: untyped
|
|
5
|
+
|
|
6
|
+
@render_input_path: untyped
|
|
7
|
+
|
|
8
|
+
@stdin_content: untyped
|
|
9
|
+
|
|
10
|
+
@stdin_tempfile: untyped
|
|
11
|
+
|
|
4
12
|
@launcher: untyped
|
|
5
13
|
|
|
14
|
+
include JsonOutput
|
|
15
|
+
|
|
6
16
|
def self.exit_on_failure?: () -> true
|
|
7
17
|
|
|
8
|
-
def render: () -> untyped
|
|
18
|
+
def render: () -> (nil | untyped)
|
|
9
19
|
|
|
10
20
|
def version: () -> untyped
|
|
11
21
|
|
|
22
|
+
def schema: (untyped kind) -> untyped
|
|
23
|
+
|
|
24
|
+
def diagnose: () -> untyped
|
|
25
|
+
|
|
26
|
+
# Named run_recipe, not run: Thor::Base reserves "run" as a method name (Thor::Base::
|
|
27
|
+
# ClassMethods#is_thor_reserved_word?) and refuses to define a command with that name. `map`
|
|
28
|
+
# below is what makes `bidi2pdf run recipe.yml` dispatch here.
|
|
29
|
+
def run_recipe: (untyped recipe_path) -> untyped
|
|
30
|
+
|
|
12
31
|
def template: () -> untyped
|
|
13
32
|
|
|
14
33
|
private
|
|
15
34
|
|
|
35
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
36
|
+
def perform_human_render: () -> untyped
|
|
37
|
+
|
|
38
|
+
def perform_structured_render: () -> untyped
|
|
39
|
+
|
|
40
|
+
def emit_structured_render_output: (untyped result, untyped collector) -> untyped
|
|
41
|
+
|
|
42
|
+
def perform_human_diagnose: () -> untyped
|
|
43
|
+
|
|
44
|
+
def perform_structured_diagnose: () -> untyped
|
|
45
|
+
|
|
46
|
+
def diagnose_payload: (untyped result, untyped diagnostics) -> { schema_version: 1, ok: untyped, command: "diagnose", page: untyped, console: untyped, network_failures: untyped, fonts: untyped, print_media: untyped, paged_js: untyped, screenshot: untyped, pdf: untyped, warnings: untyped, error: untyped }
|
|
47
|
+
|
|
48
|
+
def capture_diagnose_screenshot: (untyped tab) -> (nil | untyped)
|
|
49
|
+
|
|
50
|
+
def capture_diagnose_pdf: (untyped tab) -> (nil | { path: untyped, pages: untyped, not_blank: untyped })
|
|
51
|
+
|
|
52
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
53
|
+
def perform_human_run: (untyped recipe_path) -> untyped
|
|
54
|
+
|
|
55
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
56
|
+
def perform_structured_run: (untyped recipe_path) -> untyped
|
|
57
|
+
|
|
58
|
+
def load_and_validate_recipe: (untyped recipe_path) -> untyped
|
|
59
|
+
|
|
60
|
+
def recipe_requested_url: (untyped recipe) -> untyped
|
|
61
|
+
|
|
62
|
+
def with_recipe_tab: (untyped recipe) { (untyped) -> untyped } -> untyped
|
|
63
|
+
|
|
64
|
+
# A failing action/assertion raises Recipe::Runner::StepFailure (its own entries plus the real
|
|
65
|
+
# Bidi2pdf::Error as #cause). The block always receives the entries run so far - whether every
|
|
66
|
+
# step succeeded or the run stopped partway - before the cause is re-raised so
|
|
67
|
+
# ResultCollector's ordinary error handling takes over; without the block, a failure's partial
|
|
68
|
+
# entries would otherwise be lost along with the exception that discarded them.
|
|
69
|
+
def run_recipe_steps: (untyped runner, untyped method_name) { (untyped) -> untyped } -> untyped
|
|
70
|
+
|
|
71
|
+
def launcher_for_recipe: (untyped recipe) -> untyped
|
|
72
|
+
|
|
73
|
+
def recipe_input_path: (untyped recipe) -> (nil | untyped)
|
|
74
|
+
|
|
75
|
+
def print_for_recipe: (untyped tab, untyped recipe) -> untyped
|
|
76
|
+
|
|
77
|
+
def symbolize_print_options: (untyped opts) -> untyped
|
|
78
|
+
|
|
79
|
+
def write_recipe_screenshot: (untyped tab, untyped recipe) -> (nil | untyped)
|
|
80
|
+
|
|
81
|
+
def write_recipe_manifest: (untyped recipe, untyped result) -> untyped
|
|
82
|
+
|
|
83
|
+
def recipe_input_type: (untyped recipe) -> ("url" | "html_file" | "stdin")
|
|
84
|
+
|
|
85
|
+
def run_payload: (untyped recipe_path, untyped recipe, untyped result, untyped action_entries, untyped assertion_entries, untyped state) -> { schema_version: 1, ok: untyped, command: "run", recipe: untyped, actions: untyped, assertions: untyped, assigned: untyped, output: untyped, duration_ms: untyped, warnings: untyped, error: untyped }
|
|
86
|
+
|
|
87
|
+
def recipe_output_summary: (untyped recipe, untyped result) -> untyped
|
|
88
|
+
|
|
89
|
+
# --json and --output - both need exclusive use of stdout - rejected before anything else
|
|
90
|
+
# runs, so neither stream is touched. The rejection
|
|
91
|
+
# itself is reported on stderr, since stdout's meaning is exactly what is in dispute.
|
|
92
|
+
def reject_conflicting_output_streams!: () -> untyped
|
|
93
|
+
|
|
94
|
+
def json_mode?: () -> untyped
|
|
95
|
+
|
|
96
|
+
def json_stream?: () -> untyped
|
|
97
|
+
|
|
98
|
+
def stdin_mode?: () -> untyped
|
|
99
|
+
|
|
100
|
+
def stdout_output?: () -> untyped
|
|
101
|
+
|
|
102
|
+
def render_output_label: () -> ("-" | untyped)
|
|
103
|
+
|
|
104
|
+
def render_output_path: () -> (nil | untyped)
|
|
105
|
+
|
|
106
|
+
def requested_source_description: () -> untyped
|
|
107
|
+
|
|
108
|
+
def write_manifest: (untyped result) -> untyped
|
|
109
|
+
|
|
110
|
+
def input_type: () -> ("url" | "html_file" | "stdin")
|
|
111
|
+
|
|
16
112
|
def load_config: () -> (nil | untyped)
|
|
17
113
|
|
|
18
|
-
def
|
|
114
|
+
def input_sources_provided: () -> untyped
|
|
115
|
+
|
|
116
|
+
def validate_required_options!: () -> untyped
|
|
117
|
+
|
|
118
|
+
def render_input_path: () -> untyped
|
|
119
|
+
|
|
120
|
+
def stdin_content: () -> untyped
|
|
121
|
+
|
|
122
|
+
def write_stdin_tempfile: () -> untyped
|
|
123
|
+
|
|
124
|
+
def cleanup_stdin_tempfile: () -> (nil | untyped)
|
|
125
|
+
|
|
126
|
+
def version_info: () -> { schema_version: 1, ok: true, command: "version", bidi2pdf: untyped, ruby: untyped, pdf_reader: untyped }
|
|
127
|
+
|
|
128
|
+
def pdf_reader_version: () -> (nil | untyped)
|
|
19
129
|
|
|
20
130
|
def validate_print_options: (untyped opts) -> untyped
|
|
21
131
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# Collects the render-focused diagnostics behind `bidi2pdf diagnose`: why does the PDF not look
|
|
3
|
+
# like the page, not what the page's content is. Everything here reuses
|
|
4
|
+
# BrowserTab#execute_script - no DOM dump, no headings/links/forms extraction; console and
|
|
5
|
+
# network data come from the caller's own
|
|
6
|
+
# ResultCollector, the same source `render` uses, not from here.
|
|
7
|
+
#
|
|
8
|
+
# Each script returns (or resolves to) a JSON string, extracted via #dig("result", "value") -
|
|
9
|
+
# BiDi's script.evaluate already awaits a returned Promise (awaitPromise: true, see
|
|
10
|
+
# Commands::ScriptEvaluate), so document.fonts.ready needs no wrap_in_promise: wrapper.
|
|
11
|
+
class Diagnose
|
|
12
|
+
@tab: untyped
|
|
13
|
+
|
|
14
|
+
PAGE_SCRIPT: "JSON.stringify({\n title: document.title,\n url: window.location.href,\n lang: document.documentElement.lang || null\n})\n"
|
|
15
|
+
|
|
16
|
+
FONTS_SCRIPT: "document.fonts.ready.then(function () {\n var loaded = [];\n var failed = [];\n document.fonts.forEach(function (face) {\n var label = face.family + \" \" + face.weight + \" \" + face.style;\n if (face.status === \"error\") { failed.push(label); } else { loaded.push(label); }\n });\n return JSON.stringify({ status: failed.length > 0 ? \"partial\" : \"loaded\", loaded: loaded, failed: failed });\n})\n"
|
|
17
|
+
|
|
18
|
+
PRINT_MEDIA_SCRIPT: "(function () {\n var printSheets = [];\n var pageRules = [];\n var unreadable = [];\n\n for (var i = 0; i < document.styleSheets.length; i++) {\n var sheet = document.styleSheets[i];\n try {\n var rules = sheet.cssRules || sheet.rules;\n for (var j = 0; j < rules.length; j++) {\n var rule = rules[j];\n if (rule.media && Array.prototype.includes.call(rule.media, \"print\")) {\n printSheets.push(sheet.href || \"(inline)\");\n }\n if (typeof CSSRule !== \"undefined\" && rule.type === CSSRule.PAGE_RULE) {\n pageRules.push(rule.cssText);\n }\n }\n } catch (e) {\n unreadable.push(sheet.href || \"(inline)\");\n }\n }\n\n var fixedOrSticky = [];\n var breakInsideAvoidCount = 0;\n var elements = document.querySelectorAll(\"body *\");\n for (var k = 0; k < elements.length; k++) {\n var el = elements[k];\n var computed = window.getComputedStyle(el);\n if (computed.position === \"fixed\" || computed.position === \"sticky\") {\n var classPart = typeof el.className === \"string\" && el.className.trim() ? \".\" + el.className.trim().split(/\\s+/).join(\".\") : \"\";\n var selector = el.id ? \"#\" + el.id : el.tagName.toLowerCase() + classPart;\n fixedOrSticky.push({ selector: selector, position: computed.position });\n }\n if (computed.breakInside === \"avoid\" || computed.pageBreakInside === \"avoid\") {\n breakInsideAvoidCount++;\n }\n }\n\n return JSON.stringify({\n stylesheets_with_print_rules: Array.from(new Set(printSheets)),\n page_rules: pageRules,\n break_inside_avoid_count: breakInsideAvoidCount,\n fixed_or_sticky_elements: fixedOrSticky,\n unreadable_stylesheets: Array.from(new Set(unreadable))\n });\n})()\n"
|
|
19
|
+
|
|
20
|
+
PAGED_JS_SCRIPT: "(function () {\n var detected = !!(window.Paged || window.PagedPolyfill);\n var pages = document.querySelectorAll(\".pagedjs_page\").length;\n return JSON.stringify({ detected: detected, ready: pages > 0, pages: pages });\n})()\n"
|
|
21
|
+
|
|
22
|
+
def initialize: (tab: untyped) -> void
|
|
23
|
+
|
|
24
|
+
def call: () -> { page: untyped, fonts: untyped, print_media: untyped, paged_js: untyped }
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def collect: (untyped script) -> untyped
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# Maps an exception to the stable, machine-readable code used by --json/--json-stream/manifests
|
|
3
|
+
# and recipe results. Derived from the existing Bidi2pdf::Error hierarchy rather than duplicated
|
|
4
|
+
# beside it: a new failure mode gets a new subclass in lib/bidi2pdf.rb and a row here, never a
|
|
5
|
+
# bare string minted ad hoc.
|
|
6
|
+
#
|
|
7
|
+
# MAPPING is ordered most-specific-subclass first - #for walks it in order and returns the first
|
|
8
|
+
# match, so a subclass must appear before any of its ancestors.
|
|
9
|
+
module ErrorCodes
|
|
10
|
+
MAPPING: ::Hash[untyped, "MISSING_INPUT" | "MULTIPLE_INPUT_SOURCES" | "EMPTY_INPUT" | "INVALID_CONFIG" | "INVALID_PRINT_OPTION" | "INVALID_RECIPE" | "PDF_INSPECTION_UNAVAILABLE" | "BROWSER_LAUNCH_FAILED" | "COMMAND_TIMEOUT" | "BROWSER_DISCONNECTED" | "NAVIGATION_TIMEOUT" | "NAVIGATION_AUTH" | "NAVIGATION_NOT_FOUND" | "DNS_ERROR" | "NAVIGATION_FAILED" | "SELECTOR_NOT_FOUND" | "PAGE_NOT_AS_EXPECTED" | "SCRIPT_ERROR" | "STYLE_ERROR" | "PDF_GENERATION_FAILED" | "SCREENSHOT_FAILED" | "OUTPUT_WRITE_FAILED" | "INTERNAL_ERROR"]
|
|
11
|
+
|
|
12
|
+
DEFAULT_CODE: "INTERNAL_ERROR"
|
|
13
|
+
|
|
14
|
+
def self.for: (untyped exception) -> untyped
|
|
15
|
+
|
|
16
|
+
# Builds the {code:, message:, retryable:, hint:, details:} hash used as Result#error and in
|
|
17
|
+
# recipe action/assertion failures. Works for any StandardError, not only a Bidi2pdf::Error -
|
|
18
|
+
# an unexpected exception still gets INTERNAL_ERROR with retryable/hint defaulted safely.
|
|
19
|
+
def self.describe: (untyped exception, ?details: ::Hash[untyped, untyped]) -> { code: untyped, message: untyped, retryable: untyped, hint: untyped, details: untyped }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# Maps an ErrorCodes string to the process exit status the CLI reports. Centralized here so
|
|
3
|
+
# render/diagnose/run share one table instead of each re-deciding what a given failure is worth.
|
|
4
|
+
module ExitCodes
|
|
5
|
+
SUCCESS: 0
|
|
6
|
+
|
|
7
|
+
CLI_ERROR: 2
|
|
8
|
+
|
|
9
|
+
BROWSER_ERROR: 3
|
|
10
|
+
|
|
11
|
+
PAGE_NOT_AS_EXPECTED: 4
|
|
12
|
+
|
|
13
|
+
OUTPUT_ERROR: 6
|
|
14
|
+
|
|
15
|
+
INTERNAL_ERROR: 70
|
|
16
|
+
|
|
17
|
+
CODE_TO_EXIT: { "MISSING_INPUT" => untyped, "MULTIPLE_INPUT_SOURCES" => untyped, "EMPTY_INPUT" => untyped, "INVALID_CONFIG" => untyped, "INVALID_PRINT_OPTION" => untyped, "INVALID_RECIPE" => untyped, "PDF_INSPECTION_UNAVAILABLE" => untyped, "BROWSER_LAUNCH_FAILED" => untyped, "BROWSER_DISCONNECTED" => untyped, "COMMAND_TIMEOUT" => untyped, "NAVIGATION_FAILED" => untyped, "NAVIGATION_TIMEOUT" => untyped, "NAVIGATION_AUTH" => untyped, "NAVIGATION_NOT_FOUND" => untyped, "DNS_ERROR" => untyped, "SELECTOR_NOT_FOUND" => untyped, "PAGE_NOT_AS_EXPECTED" => untyped, "SCRIPT_ERROR" => untyped, "STYLE_ERROR" => untyped, "PDF_GENERATION_FAILED" => untyped, "SCREENSHOT_FAILED" => untyped, "OUTPUT_WRITE_FAILED" => untyped, "INTERNAL_ERROR" => untyped }
|
|
18
|
+
|
|
19
|
+
def self.for: (untyped code) -> untyped
|
|
20
|
+
end
|
|
21
|
+
end
|
data/sig/bidi2pdf/launcher.rbs
CHANGED
|
@@ -62,11 +62,20 @@ module Bidi2pdf
|
|
|
62
62
|
|
|
63
63
|
@chrome_args: untyped
|
|
64
64
|
|
|
65
|
+
@diagnose_runner: untyped
|
|
66
|
+
|
|
65
67
|
# rubocop:disable Metrics/ParameterLists
|
|
66
68
|
def initialize: (url: untyped, inputfile: untyped, output: untyped, cookies: untyped, headers: untyped, auth: untyped, ?headless: bool, ?port: ::Integer, ?wait_window_loaded: bool, ?wait_network_idle: bool, ?print_options: ::Hash[untyped, untyped], ?remote_browser_url: untyped?, ?network_log_format: ::Symbol, ?chrome_args: untyped) -> void
|
|
67
69
|
|
|
68
70
|
def launch: () -> untyped
|
|
69
71
|
|
|
72
|
+
# Like #launch, but navigates only (see SessionRunner#run_diagnose) and leaves the tab open -
|
|
73
|
+
# used by `bidi2pdf diagnose`, which has no PDF to produce. #stop closes it, same as it stops
|
|
74
|
+
# everything else #launch/#diagnose started.
|
|
75
|
+
#
|
|
76
|
+
# @return [Bidi2pdf::Bidi::BrowserTab] the navigated tab.
|
|
77
|
+
def diagnose: () -> untyped
|
|
78
|
+
|
|
70
79
|
def stop: () -> untyped
|
|
71
80
|
|
|
72
81
|
private
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# Builds the render manifest (--manifest FILE) from a completed Bidi2pdf::Result plus the
|
|
3
|
+
# input/browser/header context around it. Redacts secret-
|
|
4
|
+
# bearing headers and never records cookie values at all.
|
|
5
|
+
class Manifest
|
|
6
|
+
@result: untyped
|
|
7
|
+
|
|
8
|
+
@input: untyped
|
|
9
|
+
|
|
10
|
+
@headers: untyped
|
|
11
|
+
|
|
12
|
+
@navigation_duration_ms: untyped
|
|
13
|
+
|
|
14
|
+
@browser: untyped
|
|
15
|
+
|
|
16
|
+
SCHEMA_VERSION: 1
|
|
17
|
+
|
|
18
|
+
REDACT_HEADER_NAMES: ::Array["authorization" | "proxy-authorization" | "cookie" | "set-cookie" | "x-api-key" | "api-key"]
|
|
19
|
+
|
|
20
|
+
SENSITIVE_SUBSTRINGS: ::Array["token" | "secret" | "password" | "authorization" | "api-key" | "apikey"]
|
|
21
|
+
|
|
22
|
+
def initialize: (result: untyped, input: untyped, ?headers: ::Hash[untyped, untyped], ?navigation_duration_ms: untyped?, ?browser: ::Hash[untyped, untyped]) -> void
|
|
23
|
+
|
|
24
|
+
def self.redact: (untyped headers) -> untyped
|
|
25
|
+
|
|
26
|
+
def self.sensitive?: (untyped key) -> untyped
|
|
27
|
+
|
|
28
|
+
def to_h: () -> untyped
|
|
29
|
+
|
|
30
|
+
def to_json: (*untyped) -> untyped
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def output_section: () -> { path: untyped, pages: untyped, bytes: untyped, sha256: untyped }
|
|
35
|
+
|
|
36
|
+
def navigation_section: () -> untyped
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
module Notifications
|
|
3
|
+
# Emits one JSON object per line to an IO (stderr for --json-stream, docs/specs/llm-friendly-
|
|
4
|
+
# spec.md section 7) as a render progresses, by subscribing to the same Bidi2pdf::Notifications
|
|
5
|
+
# events Bidi2pdf::ResultCollector and LoggingSubscriber already use - no new instrumentation
|
|
6
|
+
# points needed. #emit_result writes the final "result" event once the caller has a Result.
|
|
7
|
+
class JsonSubscriber
|
|
8
|
+
@io: untyped
|
|
9
|
+
|
|
10
|
+
@start: untyped
|
|
11
|
+
|
|
12
|
+
@registered: untyped
|
|
13
|
+
|
|
14
|
+
SCHEMA_VERSION: 1
|
|
15
|
+
|
|
16
|
+
def initialize: (?io: untyped) -> void
|
|
17
|
+
|
|
18
|
+
def emit_result: (untyped result) -> untyped
|
|
19
|
+
|
|
20
|
+
def unsubscribe: () -> untyped
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def now_ms: () -> untyped
|
|
25
|
+
|
|
26
|
+
def elapsed_ms: () -> untyped
|
|
27
|
+
|
|
28
|
+
def subscribe: () -> untyped
|
|
29
|
+
|
|
30
|
+
def on_navigate: (untyped event) -> untyped
|
|
31
|
+
|
|
32
|
+
def on_page_loaded: (untyped _event) -> untyped
|
|
33
|
+
|
|
34
|
+
def on_network_idle: (untyped _event) -> untyped
|
|
35
|
+
|
|
36
|
+
def on_console: (untyped event) -> untyped
|
|
37
|
+
|
|
38
|
+
def on_print: (untyped _event) -> untyped
|
|
39
|
+
|
|
40
|
+
def on_screenshot: (untyped _event) -> untyped
|
|
41
|
+
|
|
42
|
+
def write: (untyped fields) -> untyped
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# Lazy, optional wrapper around the pdf-reader gem. pdf-reader is deliberately NOT a bidi2pdf
|
|
3
|
+
# runtime dependency - render works fine without it,
|
|
4
|
+
# just with `pages` left nil and a warning, and a recipe assertion that needs it fails
|
|
5
|
+
# INVALID_RECIPE at validation time rather than crashing mid-render. The official Docker images
|
|
6
|
+
# (docker/Dockerfile, docker/Dockerfile.slim) install it, so a render inside one of them - the
|
|
7
|
+
# environment an agent or CI job actually uses - always has page counts and PDF assertions
|
|
8
|
+
# available without anyone opting in by hand.
|
|
9
|
+
module PdfInspection
|
|
10
|
+
self.@available: untyped
|
|
11
|
+
|
|
12
|
+
def self.available?: () -> untyped
|
|
13
|
+
|
|
14
|
+
# @param bytes [String, nil] raw PDF bytes.
|
|
15
|
+
# @return [Integer, nil] page count, or nil when pdf-reader is unavailable, bytes is nil,
|
|
16
|
+
# or the PDF is malformed.
|
|
17
|
+
def self.page_count: (untyped bytes) -> untyped
|
|
18
|
+
|
|
19
|
+
# @param bytes [String, nil] raw PDF bytes.
|
|
20
|
+
# @return [String, nil] extracted text across all pages, joined with newlines.
|
|
21
|
+
def self.text: (untyped bytes) -> untyped
|
|
22
|
+
|
|
23
|
+
# Clears the memoized availability check - test-only, so a spec can exercise both branches
|
|
24
|
+
# of #available? regardless of load order elsewhere in the process.
|
|
25
|
+
def self.reset_for_testing!: () -> (untyped | nil)
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def self.load_pdf_reader: () -> untyped
|
|
30
|
+
|
|
31
|
+
def self.reader_for: (untyped bytes) -> untyped
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
class Recipe
|
|
3
|
+
# Reads a recipe file (YAML, or JSON when the extension is .json) into a plain Hash. Never
|
|
4
|
+
# launches a browser - a malformed file fails right here.
|
|
5
|
+
#
|
|
6
|
+
# Security: YAML is parsed with Psych.safe_load and no extra options. Its defaults already
|
|
7
|
+
# refuse both custom type tags (no arbitrary Ruby object instantiation) and aliases - the
|
|
8
|
+
# latter matters concretely: a small file using nested YAML anchors/aliases ("billion laughs")
|
|
9
|
+
# can expand to millions of elements in memory; confirmed live, a 273-byte such file produced
|
|
10
|
+
# 10 million array elements in under 2ms. Neither aliases nor a Date/Time value are used by any
|
|
11
|
+
# field a recipe defines, so neither is turned back on "just in case" - only what the format
|
|
12
|
+
# actually needs is permitted. MAX_BYTES bounds the file itself before it is even parsed.
|
|
13
|
+
module Loader
|
|
14
|
+
MAX_BYTES: 1048576
|
|
15
|
+
|
|
16
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
17
|
+
def self.load: (untyped path) -> untyped
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
class Recipe
|
|
3
|
+
# Runs a validated recipe's actions and assertions against a navigated tab. A thin dispatch
|
|
4
|
+
# over BrowserTab's own methods, not a parallel action/assertion class hierarchy - a step
|
|
5
|
+
# that needs something BrowserTab cannot do belongs there, not here.
|
|
6
|
+
class Runner
|
|
7
|
+
@recipe: untyped
|
|
8
|
+
|
|
9
|
+
@tab: untyped
|
|
10
|
+
|
|
11
|
+
@collector: untyped
|
|
12
|
+
|
|
13
|
+
@state: untyped
|
|
14
|
+
|
|
15
|
+
@pdf_bytes: untyped
|
|
16
|
+
|
|
17
|
+
# Raised on the first failing action/assertion; #entries carries every step run so far
|
|
18
|
+
# (successes and the failure itself), for the caller to report in the recipe result.
|
|
19
|
+
class StepFailure < StandardError
|
|
20
|
+
@entries: untyped
|
|
21
|
+
|
|
22
|
+
@cause: untyped
|
|
23
|
+
|
|
24
|
+
attr_reader entries: untyped
|
|
25
|
+
|
|
26
|
+
attr_reader cause: untyped
|
|
27
|
+
|
|
28
|
+
def initialize: (untyped entries, untyped cause) -> void
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_reader state: untyped
|
|
32
|
+
|
|
33
|
+
attr_accessor pdf_bytes: untyped
|
|
34
|
+
|
|
35
|
+
# @param recipe [Bidi2pdf::Recipe]
|
|
36
|
+
# @param tab [Bidi2pdf::Bidi::BrowserTab] a navigated tab (e.g. from Launcher#diagnose)
|
|
37
|
+
# @param collector [Bidi2pdf::ResultCollector] source of #console/#network_failures -
|
|
38
|
+
# exposed live, so assertions can read them mid-run (see ResultCollector's own docs)
|
|
39
|
+
def initialize: (recipe: untyped, tab: untyped, collector: untyped) -> void
|
|
40
|
+
|
|
41
|
+
# @return [Array<Hash>] one entry per action actually run
|
|
42
|
+
# @raise [StepFailure] on the first failing action
|
|
43
|
+
def run_actions: () -> untyped
|
|
44
|
+
|
|
45
|
+
# @return [Array<Hash>] one entry per assertion actually run
|
|
46
|
+
# @raise [StepFailure] on the first failing assertion
|
|
47
|
+
def run_assertions: () -> untyped
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
52
|
+
def run_steps: (untyped steps) { (untyped, untyped) -> untyped } -> untyped
|
|
53
|
+
|
|
54
|
+
def now_ms: () -> untyped
|
|
55
|
+
|
|
56
|
+
def elapsed_ms: (untyped start) -> untyped
|
|
57
|
+
|
|
58
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
59
|
+
def dispatch_action: (untyped name, untyped opts) -> untyped
|
|
60
|
+
|
|
61
|
+
def action_wait_for: (untyped opts) -> (nil | untyped)
|
|
62
|
+
|
|
63
|
+
def wait_for_condition: (untyped opts) -> ("document.querySelector('.pagedjs_page')" | ::String | untyped)
|
|
64
|
+
|
|
65
|
+
def action_click: (untyped opts) -> (nil | untyped)
|
|
66
|
+
|
|
67
|
+
def action_evaluate: (untyped opts) -> untyped
|
|
68
|
+
|
|
69
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
70
|
+
def dispatch_assertion: (untyped name, untyped step) -> untyped
|
|
71
|
+
|
|
72
|
+
def assertion_selector_exists: (untyped opts) -> ::Array[untyped | { selector: untyped }]
|
|
73
|
+
|
|
74
|
+
def assertion_text_present: (untyped opts) -> ::Array[untyped | { text: untyped }]
|
|
75
|
+
|
|
76
|
+
def assertion_no_console_errors: () -> ::Array[untyped]
|
|
77
|
+
|
|
78
|
+
# Filtering by resource type (a `types:` option) is not implemented: Bidi2pdf::Bidi::
|
|
79
|
+
# NetworkEvent carries an HTTP method, not a resource type, so there is nothing to filter
|
|
80
|
+
# by yet. Every captured failure counts.
|
|
81
|
+
def assertion_no_network_failures: () -> ::Array[untyped]
|
|
82
|
+
|
|
83
|
+
def assertion_fonts_loaded: () -> ::Array[untyped | nil]
|
|
84
|
+
|
|
85
|
+
def assertion_page_count: (untyped expected) -> ::Array[untyped | { expected: untyped, actual: untyped }]
|
|
86
|
+
|
|
87
|
+
def assertion_pdf_text_present: (untyped opts) -> ::Array[untyped | { text: untyped }]
|
|
88
|
+
|
|
89
|
+
def assertion_pdf_not_blank: () -> ::Array[untyped | { page_count: untyped, text_present: untyped }]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
class Recipe
|
|
3
|
+
# Structural validation against Schema::RECIPE itself, not a hand-duplicated parallel list of
|
|
4
|
+
# rules. Validator's other #check_* methods each enforce one *semantic* rule (an action name
|
|
5
|
+
# is known, a source key is truthy, a presence assertion is `true`) with a friendly, specific
|
|
6
|
+
# message - none of them enforce the schema's own `additionalProperties: false` at every
|
|
7
|
+
# level, so an extra, unrecognized key sitting alongside an otherwise-valid one used to pass
|
|
8
|
+
# silently even though `bidi2pdf schema recipe` would reject it (`{url: "...", stdin: false}`,
|
|
9
|
+
# `{wait_for: {...}, extra: 1}` - two action names in one step, `wait_for: {selector: "#x",
|
|
10
|
+
# bogus: "y"}`). #check_shape (see Validator) closes that whole class of gap generically by
|
|
11
|
+
# walking the schema the CLI already publishes, instead of re-describing "what's allowed" a
|
|
12
|
+
# second time in Ruby - the schema is the one place that description can't drift from itself.
|
|
13
|
+
#
|
|
14
|
+
# Deliberately not a general JSON Schema engine and not a gem dependency: only the keywords
|
|
15
|
+
# Schema::RECIPE actually uses (type, const, enum, required, additionalProperties, properties,
|
|
16
|
+
# oneOf, anyOf, items, minimum, maximum). Extend the keyword list only if a future schema
|
|
17
|
+
# branch genuinely needs one this doesn't cover yet.
|
|
18
|
+
module SchemaShape
|
|
19
|
+
TYPE_CHECKS: { "object" => untyped, "array" => untyped, "string" => untyped, "integer" => untyped, "number" => untyped, "boolean" => untyped, "null" => untyped }
|
|
20
|
+
|
|
21
|
+
CHECKS: ::Array[:const_ok? | :enum_ok? | :type_ok? | :one_of_ok? | :any_of_ok? | :required_ok? | :additional_properties_ok? | :properties_ok? | :items_ok? | :bounds_ok?]
|
|
22
|
+
|
|
23
|
+
# @return [Boolean] whether value satisfies schema
|
|
24
|
+
def self?.matches?: (untyped schema, untyped value) -> untyped
|
|
25
|
+
|
|
26
|
+
VIOLATION_CHECKS: ::Array[:oneof_violation | :anyof_violation | :required_violation | :additional_properties_violation | :const_violation | :enum_violation | :type_violation | :bounds_violation | :nested_violation]
|
|
27
|
+
|
|
28
|
+
# @return [Hash, nil] {path:, reason:} for the first violation a depth-first walk finds, or
|
|
29
|
+
# nil if value already satisfies schema. Not necessarily *the* most relevant violation
|
|
30
|
+
# when several exist at once - good enough to point an agent at the right neighborhood,
|
|
31
|
+
# not a substitute for reading `bidi2pdf schema recipe` for the exact shape.
|
|
32
|
+
def self?.first_violation: (untyped schema, untyped value, ?untyped path) -> (nil | untyped | { path: untyped, reason: "does not match the schema" })
|
|
33
|
+
|
|
34
|
+
def self?.const_ok?: (untyped schema, untyped value) -> untyped
|
|
35
|
+
|
|
36
|
+
def self?.enum_ok?: (untyped schema, untyped value) -> untyped
|
|
37
|
+
|
|
38
|
+
def self?.type_ok?: (untyped schema, untyped value) -> (true | untyped)
|
|
39
|
+
|
|
40
|
+
def self?.one_of_ok?: (untyped schema, untyped value) -> (true | untyped)
|
|
41
|
+
|
|
42
|
+
def self?.any_of_ok?: (untyped schema, untyped value) -> (true | untyped)
|
|
43
|
+
|
|
44
|
+
def self?.required_ok?: (untyped schema, untyped value) -> (true | untyped)
|
|
45
|
+
|
|
46
|
+
def self?.additional_properties_ok?: (untyped schema, untyped value) -> (true | untyped)
|
|
47
|
+
|
|
48
|
+
def self?.properties_ok?: (untyped schema, untyped value) -> (true | untyped)
|
|
49
|
+
|
|
50
|
+
def self?.items_ok?: (untyped schema, untyped value) -> (true | untyped)
|
|
51
|
+
|
|
52
|
+
def self?.bounds_ok?: (untyped schema, untyped value) -> (true | untyped)
|
|
53
|
+
|
|
54
|
+
def self?.display_path: (untyped path) -> untyped
|
|
55
|
+
|
|
56
|
+
# When zero branches match but exactly one is "plausible" (its own required key(s) are
|
|
57
|
+
# present, ignoring additionalProperties), recurse into that one branch for a specific
|
|
58
|
+
# reason/path instead of a bare "matched 0 of N" - this is what turns "actions[0] doesn't
|
|
59
|
+
# match any known action" into "actions[0].wait_for: unknown key(s): bogus".
|
|
60
|
+
def self?.oneof_violation: (untyped schema, untyped value, untyped path) -> (nil | untyped)
|
|
61
|
+
|
|
62
|
+
def self?.single_plausible_violation: (untyped schema, untyped value, untyped path) -> (nil | untyped)
|
|
63
|
+
|
|
64
|
+
def self?.anyof_violation: (untyped schema, untyped value, untyped path) -> (nil | { path: untyped, reason: ::String })
|
|
65
|
+
|
|
66
|
+
def self?.required_violation: (untyped schema, untyped value, untyped path) -> (nil | { path: untyped, reason: ::String })
|
|
67
|
+
|
|
68
|
+
def self?.additional_properties_violation: (untyped schema, untyped value, untyped path) -> (nil | { path: untyped, reason: ::String })
|
|
69
|
+
|
|
70
|
+
def self?.const_violation: (untyped schema, untyped value, untyped path) -> (nil | { path: untyped, reason: ::String })
|
|
71
|
+
|
|
72
|
+
def self?.enum_violation: (untyped schema, untyped value, untyped path) -> (nil | { path: untyped, reason: ::String })
|
|
73
|
+
|
|
74
|
+
def self?.type_violation: (untyped schema, untyped value, untyped path) -> (nil | { path: untyped, reason: ::String })
|
|
75
|
+
|
|
76
|
+
def self?.bounds_violation: (untyped schema, untyped value, untyped path) -> (nil | { path: untyped, reason: ::String })
|
|
77
|
+
|
|
78
|
+
def self?.nested_violation: (untyped schema, untyped value, untyped path) -> untyped
|
|
79
|
+
|
|
80
|
+
def self?.properties_violation: (untyped schema, untyped value, untyped path) -> (nil | untyped)
|
|
81
|
+
|
|
82
|
+
def self?.items_violation: (untyped schema, untyped value, untyped path) -> (nil | untyped)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|