bidi2pdf 0.1.15 → 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 +22 -2
- data/README.md +196 -11
- data/docker/Dockerfile +4 -0
- data/docker/Dockerfile.slim +5 -1
- 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/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/version.rb +1 -1
- data/lib/bidi2pdf.rb +90 -8
- 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/version.rbs +1 -1
- data/sig/bidi2pdf.rbs +66 -0
- metadata +31 -1
|
@@ -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
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
class Recipe
|
|
3
|
+
# Validates a loaded recipe against the schema `bidi2pdf schema recipe` publishes, before any
|
|
4
|
+
# browser is launched - `bidi2pdf run --validate` runs exactly this and nothing else. Every
|
|
5
|
+
# failure is Bidi2pdf::InvalidRecipeError (or Bidi2pdf::PdfInspectionUnavailableError for a
|
|
6
|
+
# PDF assertion without pdf-reader installed), details carrying a `path` an agent can act on
|
|
7
|
+
# directly.
|
|
8
|
+
class Validator
|
|
9
|
+
@recipe: untyped
|
|
10
|
+
|
|
11
|
+
def initialize: (untyped recipe) -> void
|
|
12
|
+
|
|
13
|
+
def validate!: () -> untyped
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def check_version: () -> (nil | untyped)
|
|
18
|
+
|
|
19
|
+
def check_top_level_keys: () -> (nil | untyped)
|
|
20
|
+
|
|
21
|
+
def check_source: () -> untyped
|
|
22
|
+
|
|
23
|
+
def check_steps: (untyped section, untyped steps, untyped known) -> untyped
|
|
24
|
+
|
|
25
|
+
# Mirrors Schema::RECIPE_ACTIONS' own wait_for oneOf: exactly one of selector/paged_js/script.
|
|
26
|
+
# --validate runs this check itself rather than relying on Runner#wait_for_condition, whose
|
|
27
|
+
# own "needs one of" error only fires mid-run (after a browser is already launched) and does
|
|
28
|
+
# not catch "more than one given" at all - it just silently prefers paged_js, then selector.
|
|
29
|
+
def check_wait_for_conditions: () -> untyped
|
|
30
|
+
|
|
31
|
+
# Mirrors Schema::RECIPE_ASSERT's own const: true for the 4 presence-only assertions -
|
|
32
|
+
# Runner never reads the value beside them, so anything but `true` is misleading rather than
|
|
33
|
+
# merely unusual (see Recipe::PRESENCE_ONLY_ASSERTIONS' own comment).
|
|
34
|
+
def check_presence_assertions: () -> untyped
|
|
35
|
+
|
|
36
|
+
def check_pdf_assertions_need_pdf_inspection: () -> (nil | untyped)
|
|
37
|
+
|
|
38
|
+
def check_output: () -> (nil | untyped)
|
|
39
|
+
|
|
40
|
+
def check_print_options: () -> untyped
|
|
41
|
+
|
|
42
|
+
def symbolize: (untyped hash) -> untyped
|
|
43
|
+
|
|
44
|
+
# Runs last, deliberately: everything above gives a friendlier, more specific message for
|
|
45
|
+
# the case it already knows about (an unknown action name, a non-true presence assertion, a
|
|
46
|
+
# real PrintParametersValidator error, ...), so this only ever fires for a shape violation
|
|
47
|
+
# none of them catches - an extra/unrecognized key sitting alongside an otherwise-valid one
|
|
48
|
+
# (SchemaShape's own doc comment has the concrete examples this closes).
|
|
49
|
+
def check_shape: () -> (nil | untyped)
|
|
50
|
+
|
|
51
|
+
def fail!: (untyped reason, path: untyped) -> untyped
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# A recipe is a rendering contract: the waits and preparation a page needs before it is printed,
|
|
3
|
+
# and the properties the resulting PDF must have - not a general browser-automation script.
|
|
4
|
+
# `bidi2pdf run recipe.yml` is Loader -> (an invalid recipe stops here, no browser ever
|
|
5
|
+
# launches) -> Validator -> Runner.
|
|
6
|
+
class Recipe
|
|
7
|
+
@data: untyped
|
|
8
|
+
|
|
9
|
+
@path: untyped
|
|
10
|
+
|
|
11
|
+
KNOWN_TOP_LEVEL_KEYS: ::Array["version" | "source" | "browser" | "headers" | "cookies" | "auth" | "actions" | "assert" | "print" | "output"]
|
|
12
|
+
|
|
13
|
+
KNOWN_ACTIONS: ::Array["wait_for" | "click" | "evaluate" | "inject_script" | "inject_style" | "set_viewport" | "wait_network_idle"]
|
|
14
|
+
|
|
15
|
+
PAGE_ASSERTIONS: ::Array["selector_exists" | "text_present" | "no_console_errors" | "no_network_failures" | "fonts_loaded"]
|
|
16
|
+
|
|
17
|
+
PDF_ASSERTIONS: ::Array["page_count" | "pdf_text_present" | "pdf_not_blank"]
|
|
18
|
+
|
|
19
|
+
KNOWN_ASSERTIONS: untyped
|
|
20
|
+
|
|
21
|
+
KNOWN_OUTPUTS: ::Array["pdf" | "manifest" | "screenshot"]
|
|
22
|
+
|
|
23
|
+
# These four assertions are pure presence checks - Runner never reads the value beside them
|
|
24
|
+
# (see #assertion_no_console_errors and friends), so a bare key is really what's being
|
|
25
|
+
# asserted. `false`/anything but `true` is therefore misleading, not merely unusual: schema
|
|
26
|
+
# recipe encodes this as `const: true` and Validator#check_presence_assertions enforces it too.
|
|
27
|
+
PRESENCE_ONLY_ASSERTIONS: ::Array["no_console_errors" | "no_network_failures" | "fonts_loaded" | "pdf_not_blank"]
|
|
28
|
+
|
|
29
|
+
attr_reader data: untyped
|
|
30
|
+
|
|
31
|
+
attr_reader path: untyped
|
|
32
|
+
|
|
33
|
+
def self.load: (untyped path) -> untyped
|
|
34
|
+
|
|
35
|
+
def initialize: (untyped data, ?path: untyped?) -> void
|
|
36
|
+
|
|
37
|
+
def validate!: () -> untyped
|
|
38
|
+
|
|
39
|
+
def actions: () -> untyped
|
|
40
|
+
|
|
41
|
+
def assertions: () -> untyped
|
|
42
|
+
|
|
43
|
+
def source: () -> untyped
|
|
44
|
+
|
|
45
|
+
def browser_options: () -> untyped
|
|
46
|
+
|
|
47
|
+
def headers: () -> untyped
|
|
48
|
+
|
|
49
|
+
def cookies: () -> untyped
|
|
50
|
+
|
|
51
|
+
def auth: () -> untyped
|
|
52
|
+
|
|
53
|
+
def print_options: () -> untyped
|
|
54
|
+
|
|
55
|
+
def output: () -> untyped
|
|
56
|
+
|
|
57
|
+
def needs_pdf?: () -> untyped
|
|
58
|
+
|
|
59
|
+
def self.step_name: (untyped step) -> untyped
|
|
60
|
+
|
|
61
|
+
def step_name: (untyped step) -> untyped
|
|
62
|
+
|
|
63
|
+
# The value beside a step's name - a Hash of options (`wait_for: {selector: ..., timeout:
|
|
64
|
+
# ...}`), a bare scalar (`page_count: 2`, `no_console_errors: true`), or nil.
|
|
65
|
+
def step_value: (untyped step) -> (untyped | nil)
|
|
66
|
+
|
|
67
|
+
# step_value coerced to a Hash of options - most actions/assertions take this shape; a bare
|
|
68
|
+
# scalar step (page_count: 2, no_console_errors: true) has no options of its own to offer here,
|
|
69
|
+
# so callers that need the scalar itself use #step_value directly.
|
|
70
|
+
def step_options: (untyped step) -> untyped
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# Structured result of a CLI command (render/diagnose/run) - the shape behind --json output, a
|
|
3
|
+
# render manifest, and the final "result" event of --json-stream. Construct via .success/
|
|
4
|
+
# .failure rather than .new directly so every Result is unambiguously one or the other.
|
|
5
|
+
class Result
|
|
6
|
+
@command: untyped
|
|
7
|
+
|
|
8
|
+
@ok: untyped
|
|
9
|
+
|
|
10
|
+
@output: untyped
|
|
11
|
+
|
|
12
|
+
@bytes: untyped
|
|
13
|
+
|
|
14
|
+
@sha256: untyped
|
|
15
|
+
|
|
16
|
+
@pages: untyped
|
|
17
|
+
|
|
18
|
+
@duration_ms: untyped
|
|
19
|
+
|
|
20
|
+
@navigation: untyped
|
|
21
|
+
|
|
22
|
+
@console: untyped
|
|
23
|
+
|
|
24
|
+
@network_failures: untyped
|
|
25
|
+
|
|
26
|
+
@warnings: untyped
|
|
27
|
+
|
|
28
|
+
@error: untyped
|
|
29
|
+
|
|
30
|
+
@metadata: untyped
|
|
31
|
+
|
|
32
|
+
SCHEMA_VERSION: 1
|
|
33
|
+
|
|
34
|
+
attr_reader command: untyped
|
|
35
|
+
|
|
36
|
+
attr_reader output: untyped
|
|
37
|
+
|
|
38
|
+
attr_reader bytes: untyped
|
|
39
|
+
|
|
40
|
+
attr_reader sha256: untyped
|
|
41
|
+
|
|
42
|
+
attr_reader pages: untyped
|
|
43
|
+
|
|
44
|
+
attr_reader duration_ms: untyped
|
|
45
|
+
|
|
46
|
+
attr_reader navigation: untyped
|
|
47
|
+
|
|
48
|
+
attr_reader console: untyped
|
|
49
|
+
|
|
50
|
+
attr_reader network_failures: untyped
|
|
51
|
+
|
|
52
|
+
attr_reader warnings: untyped
|
|
53
|
+
|
|
54
|
+
attr_reader error: untyped
|
|
55
|
+
|
|
56
|
+
attr_reader metadata: untyped
|
|
57
|
+
|
|
58
|
+
# rubocop:disable-next Metrics/ParameterLists, Naming/MethodParameterName
|
|
59
|
+
def initialize: (command: untyped, ok: untyped, ?output: untyped?, ?bytes: untyped?, ?sha256: untyped?, ?pages: untyped?, ?duration_ms: untyped?, ?navigation: untyped?, ?console: untyped, ?network_failures: untyped, ?warnings: untyped, ?error: untyped?, ?metadata: ::Hash[untyped, untyped]) -> void
|
|
60
|
+
|
|
61
|
+
def self.success: (**untyped) -> untyped
|
|
62
|
+
|
|
63
|
+
def self.failure: (error: untyped, **untyped) -> untyped
|
|
64
|
+
|
|
65
|
+
def ok?: () -> untyped
|
|
66
|
+
|
|
67
|
+
def to_h: () -> untyped
|
|
68
|
+
|
|
69
|
+
def to_json: (*untyped) -> untyped
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# Builds a Bidi2pdf::Result by subscribing to the gem's own instrumentation events
|
|
3
|
+
# (Bidi2pdf::Notifications) around a block that does the actual rendering, rather than changing
|
|
4
|
+
# what Launcher/SessionRunner/BrowserTab return - Launcher#launch keeps returning the raw base64
|
|
5
|
+
# PDF when no output file is given, exactly as the programmatic API already promises (see
|
|
6
|
+
# spec/acceptance/launcher_spec.rb).
|
|
7
|
+
class ResultCollector
|
|
8
|
+
@requested_url: untyped
|
|
9
|
+
|
|
10
|
+
@command: untyped
|
|
11
|
+
|
|
12
|
+
@output: untyped
|
|
13
|
+
|
|
14
|
+
@console: untyped
|
|
15
|
+
|
|
16
|
+
@network_events: untyped
|
|
17
|
+
|
|
18
|
+
@pdf_bytes: untyped
|
|
19
|
+
|
|
20
|
+
# Capture the Proc #subscribe actually stored (it converts &handler into one), so
|
|
21
|
+
# #unsubscribe below can remove exactly this registration - passing the handler positionally
|
|
22
|
+
# rather than as a second &block, since Notifications#unsubscribe(pattern, block = nil)
|
|
23
|
+
# clears *every* subscriber for that pattern when block is nil/omitted.
|
|
24
|
+
@registered: untyped
|
|
25
|
+
|
|
26
|
+
@navigation_event: untyped
|
|
27
|
+
|
|
28
|
+
# #pdf_bytes is the raw, decoded PDF (nil if nothing was printed) - not part of Result#to_h
|
|
29
|
+
# (which only carries bytesize/sha256/pages), but what a stdout-output render (--output -)
|
|
30
|
+
# writes to $stdout, and what a caller can hand to Bidi2pdf::PdfInspection itself.
|
|
31
|
+
#
|
|
32
|
+
# #console/#network_failures are exposed live (not only via the Result #around eventually
|
|
33
|
+
# returns) so a caller running inside the block - Recipe::Runner's assertions - can read them
|
|
34
|
+
# before the block itself finishes.
|
|
35
|
+
attr_reader requested_url: untyped
|
|
36
|
+
|
|
37
|
+
# #pdf_bytes is the raw, decoded PDF (nil if nothing was printed) - not part of Result#to_h
|
|
38
|
+
# (which only carries bytesize/sha256/pages), but what a stdout-output render (--output -)
|
|
39
|
+
# writes to $stdout, and what a caller can hand to Bidi2pdf::PdfInspection itself.
|
|
40
|
+
#
|
|
41
|
+
# #console/#network_failures are exposed live (not only via the Result #around eventually
|
|
42
|
+
# returns) so a caller running inside the block - Recipe::Runner's assertions - can read them
|
|
43
|
+
# before the block itself finishes.
|
|
44
|
+
attr_reader navigation_event: untyped
|
|
45
|
+
|
|
46
|
+
# #pdf_bytes is the raw, decoded PDF (nil if nothing was printed) - not part of Result#to_h
|
|
47
|
+
# (which only carries bytesize/sha256/pages), but what a stdout-output render (--output -)
|
|
48
|
+
# writes to $stdout, and what a caller can hand to Bidi2pdf::PdfInspection itself.
|
|
49
|
+
#
|
|
50
|
+
# #console/#network_failures are exposed live (not only via the Result #around eventually
|
|
51
|
+
# returns) so a caller running inside the block - Recipe::Runner's assertions - can read them
|
|
52
|
+
# before the block itself finishes.
|
|
53
|
+
attr_reader pdf_bytes: untyped
|
|
54
|
+
|
|
55
|
+
# #pdf_bytes is the raw, decoded PDF (nil if nothing was printed) - not part of Result#to_h
|
|
56
|
+
# (which only carries bytesize/sha256/pages), but what a stdout-output render (--output -)
|
|
57
|
+
# writes to $stdout, and what a caller can hand to Bidi2pdf::PdfInspection itself.
|
|
58
|
+
#
|
|
59
|
+
# #console/#network_failures are exposed live (not only via the Result #around eventually
|
|
60
|
+
# returns) so a caller running inside the block - Recipe::Runner's assertions - can read them
|
|
61
|
+
# before the block itself finishes.
|
|
62
|
+
attr_reader console: untyped
|
|
63
|
+
|
|
64
|
+
def initialize: (requested_url: untyped, command: untyped, ?output: untyped?) -> void
|
|
65
|
+
|
|
66
|
+
# Runs the block with the collector subscribed, then returns a Bidi2pdf::Result - success with
|
|
67
|
+
# whatever was captured, or failure describing whatever the block raised. Never itself raises.
|
|
68
|
+
def around: () { () -> untyped } -> untyped
|
|
69
|
+
|
|
70
|
+
def network_failures: () -> untyped
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def now_ms: () -> untyped
|
|
75
|
+
|
|
76
|
+
def elapsed_ms: (untyped start) -> untyped
|
|
77
|
+
|
|
78
|
+
def subscribe: () -> untyped
|
|
79
|
+
|
|
80
|
+
def unsubscribe: () -> untyped
|
|
81
|
+
|
|
82
|
+
# Kept for its #duration (used by Manifest's navigation.duration_ms); Result itself doesn't
|
|
83
|
+
# surface navigation timing separately from the overall duration_ms.
|
|
84
|
+
def on_navigate: (untyped event) -> untyped
|
|
85
|
+
|
|
86
|
+
def on_console: (untyped event) -> untyped
|
|
87
|
+
|
|
88
|
+
def on_network_event: (untyped event) -> (nil | untyped)
|
|
89
|
+
|
|
90
|
+
def on_print: (untyped event) -> untyped
|
|
91
|
+
|
|
92
|
+
# rubocop:disable-next Naming/MethodParameterName
|
|
93
|
+
def build_result: (ok: untyped, duration_ms: untyped, ?error: untyped?) -> untyped
|
|
94
|
+
|
|
95
|
+
def navigation_info: () -> { requested_url: untyped, final_url: untyped, status: untyped }
|
|
96
|
+
|
|
97
|
+
# Best-effort external approximation of what BrowserTab#send(:correlated_navigation_response)
|
|
98
|
+
# already computes internally (not exposed publicly): among the network events this collector
|
|
99
|
+
# observed, the ones carrying a real BiDi "navigation" id are main-frame document requests: the
|
|
100
|
+
# one with the latest start_timestamp among those sharing the most recent such navigation id is
|
|
101
|
+
# the final hop of that navigation, and its own #url is therefore the page actually reached.
|
|
102
|
+
def final_navigation_event: () -> (nil | untyped)
|
|
103
|
+
|
|
104
|
+
def warnings: () -> (::Array[untyped] | ::Array["pages requires the pdf-reader gem"])
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Bidi2pdf
|
|
2
|
+
# JSON Schema documents behind `bidi2pdf schema <kind>` - how an agent discovers the shape of
|
|
3
|
+
# --json/--manifest/--json-stream/a recipe file without reading prose documentation. These are
|
|
4
|
+
# the source of truth for those shapes; a change to Result/Diagnose's payload/Recipe::Runner's
|
|
5
|
+
# result shape/Manifest/JsonSubscriber/Recipe's own fields should update the matching schema
|
|
6
|
+
# here in the same change. Kind names match the `command` field a response already carries
|
|
7
|
+
# (`"command": "diagnose"` -> `bidi2pdf schema diagnose`), not the Ruby class names behind them.
|
|
8
|
+
module Schema
|
|
9
|
+
ERROR: { "type" => ::Array["object" | "null"], "required" => ::Array["code" | "message" | "retryable"], "properties" => { "code" => { "type" => "string" }, "message" => { "type" => "string" }, "retryable" => { "type" => "boolean" }, "hint" => { "type" => ::Array["string" | "null"] }, "details" => { "type" => "object" } } }
|
|
10
|
+
|
|
11
|
+
NAVIGATION: { "type" => ::Array["object" | "null"], "properties" => { "requested_url" => { "type" => ::Array["string" | "null"] }, "final_url" => { "type" => ::Array["string" | "null"] }, "status" => { "type" => ::Array["integer" | "null"] } } }
|
|
12
|
+
|
|
13
|
+
CONSOLE: { "type" => "array", "items" => { "type" => "object", "properties" => { "level" => { "type" => "string" }, "text" => { "type" => "string" } } } }
|
|
14
|
+
|
|
15
|
+
NETWORK_FAILURES: { "type" => "array", "items" => { "type" => "object", "properties" => { "url" => { "type" => "string" }, "method" => { "type" => ::Array["string" | "null"] }, "status" => { "type" => ::Array["integer" | "null"] }, "state" => { "type" => "string" } } } }
|
|
16
|
+
|
|
17
|
+
WARNINGS: { "type" => "array", "items" => { "type" => "string" } }
|
|
18
|
+
|
|
19
|
+
RENDER: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf::Result (render)", "description" => "--json output of `bidi2pdf render`, and the final \"result\" event of its --json-stream", "type" => "object", "required" => ::Array["schema_version" | "ok" | "command" | "console" | "network_failures" | "warnings"], "properties" => { "schema_version" => { "const" => 1 }, "ok" => { "type" => "boolean" }, "command" => { "const" => "render" }, "output" => { "type" => ::Array["string" | "null"] }, "bytes" => { "type" => ::Array["integer" | "null"] }, "sha256" => { "type" => ::Array["string" | "null"] }, "pages" => { "type" => ::Array["integer" | "null"], "description" => "null when the pdf-reader gem is unavailable" }, "duration_ms" => { "type" => ::Array["integer" | "null"] }, "navigation" => untyped, "console" => untyped, "network_failures" => untyped, "warnings" => untyped, "error" => untyped } }
|
|
20
|
+
|
|
21
|
+
DIAGNOSE: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "bidi2pdf diagnose result", "description" => "--json output of `bidi2pdf diagnose`, and the final \"result\" event of its --json-stream. page/fonts/print_media/paged_js are Bidi2pdf::Diagnose's own parsed script results, loosely shaped here rather than over-specified.", "type" => "object", "required" => ::Array["schema_version" | "ok" | "command" | "console" | "network_failures" | "warnings"], "properties" => { "schema_version" => { "const" => 1 }, "ok" => { "type" => "boolean" }, "command" => { "const" => "diagnose" }, "page" => { "type" => ::Array["object" | "null"] }, "console" => untyped, "network_failures" => untyped, "fonts" => { "type" => ::Array["object" | "null"] }, "print_media" => { "type" => ::Array["object" | "null"] }, "paged_js" => { "type" => ::Array["object" | "null"] }, "screenshot" => { "type" => ::Array["string" | "null"], "description" => "path written, when --screenshot was given" }, "pdf" => { "type" => ::Array["object" | "null"], "description" => "present when --pdf was given", "properties" => { "path" => { "type" => "string" }, "pages" => { "type" => ::Array["integer" | "null"] }, "not_blank" => { "type" => ::Array["boolean" | "null"] } } }, "warnings" => untyped, "error" => untyped } }
|
|
22
|
+
|
|
23
|
+
RUN_STEP: { "type" => "object", "required" => ::Array["index" | "type" | "ok"], "properties" => { "index" => { "type" => "integer" }, "type" => { "type" => "string" }, "ok" => { "type" => "boolean" }, "duration_ms" => { "type" => "integer" }, "details" => { "type" => "object" } } }
|
|
24
|
+
|
|
25
|
+
RUN: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "bidi2pdf run result", "description" => "--json output of `bidi2pdf run recipe.yml`, and the final \"result\" event of its --json-stream", "type" => "object", "required" => ::Array["schema_version" | "ok" | "command" | "recipe" | "actions" | "assertions" | "warnings"], "properties" => { "schema_version" => { "const" => 1 }, "ok" => { "type" => "boolean" }, "command" => { "const" => "run" }, "recipe" => { "type" => "string", "description" => "the recipe file path given on the command line" }, "actions" => { "type" => "array", "items" => untyped }, "assertions" => { "type" => "array", "items" => untyped }, "assigned" => { "type" => "object", "description" => "values captured by evaluate ... assign:, keyed by name" }, "output" => { "type" => ::Array["object" | "null"], "description" => "null when the run failed", "properties" => { "pdf" => { "type" => ::Array["string" | "null"] }, "manifest" => { "type" => ::Array["string" | "null"] }, "screenshot" => { "type" => ::Array["string" | "null"] }, "bytes" => { "type" => ::Array["integer" | "null"] }, "sha256" => { "type" => ::Array["string" | "null"] }, "pages" => { "type" => ::Array["integer" | "null"] } } }, "duration_ms" => { "type" => ::Array["integer" | "null"] }, "warnings" => untyped, "error" => untyped } }
|
|
26
|
+
|
|
27
|
+
MANIFEST: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf render manifest", "description" => "--manifest FILE output", "type" => "object", "required" => ::Array["schema_version" | "bidi2pdf_version" | "created_at" | "input" | "output" | "navigation" | "render"], "properties" => { "schema_version" => { "const" => 1 }, "bidi2pdf_version" => { "type" => "string" }, "created_at" => { "type" => "string", "format" => "date-time" }, "input" => { "type" => "object", "required" => ::Array["type"], "properties" => { "type" => { "type" => "string", "enum" => ::Array["url" | "html_file" | "stdin"] }, "url" => { "type" => ::Array["string" | "null"] } } }, "output" => { "type" => "object", "properties" => { "path" => { "type" => ::Array["string" | "null"] }, "pages" => { "type" => ::Array["integer" | "null"] }, "bytes" => { "type" => ::Array["integer" | "null"] }, "sha256" => { "type" => ::Array["string" | "null"] } } }, "browser" => { "type" => "object" }, "navigation" => untyped, "render" => { "type" => "object", "properties" => { "duration_ms" => { "type" => ::Array["integer" | "null"] } } }, "console" => untyped, "network_failures" => untyped, "warnings" => untyped, "headers" => { "type" => "object", "description" => "present only when custom headers were given; secret-bearing values are redacted" } } }
|
|
28
|
+
|
|
29
|
+
EVENT: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf --json-stream event", "description" => "one JSON object per stderr line while --json-stream is active. In structured mode (--json/--output -), the last line is always {event: \"result\", result: <the matching command's own schema - see \"result\"'s own oneOf>}. In human mode (--json-stream given alone), only progress events are emitted - there is no synthetic final \"result\" event, since there is no structured result being built; the human-readable log is the completion/failure signal.", "type" => "object", "required" => ::Array["schema_version" | "t_ms" | "event"], "properties" => { "schema_version" => { "const" => 1 }, "t_ms" => { "type" => "integer", "description" => "milliseconds since the render started" }, "event" => { "type" => "string", "enum" => ::Array["navigate" | "page_loaded" | "network_idle" | "console" | "print" | "screenshot" | "result"] }, "url" => { "type" => "string", "description" => "present on a navigate event" }, "level" => { "type" => "string", "description" => "present on a console event" }, "text" => { "type" => "string", "description" => "present on a console event" }, "result" => { "description" => "present only on the final \"result\" event (structured mode only); shape depends on which command produced this stream - each branch's own \"command\" value disambiguates", "oneOf" => ::Array[untyped] } } }
|
|
30
|
+
|
|
31
|
+
RECIPE_SOURCE: { "description" => "exactly one of url, file, stdin", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["url"], "properties" => { "url" => { "type" => "string" } }, "additionalProperties" => false } | { "type" => "object", "required" => ::Array["file"], "properties" => { "file" => { "type" => "string" } }, "additionalProperties" => false } | { "type" => "object", "required" => ::Array["stdin"], "properties" => { "stdin" => { "const" => true } }, "additionalProperties" => false }] }
|
|
32
|
+
|
|
33
|
+
RECIPE_ACTIONS: { "description" => "one of the 7 known actions - a thin dispatch over BrowserTab's own methods, see README's Agent and Automation Usage section", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["wait_for"], "additionalProperties" => false, "properties" => { "wait_for" => { "type" => "object", "description" => "exactly one of selector, paged_js, script; timeout is always optional", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["selector"], "additionalProperties" => false, "properties" => { "selector" => { "type" => "string" }, "timeout" => { "type" => "number" } } } | { "type" => "object", "required" => ::Array["paged_js"], "additionalProperties" => false, "properties" => { "paged_js" => { "type" => "boolean" }, "timeout" => { "type" => "number" } } } | { "type" => "object", "required" => ::Array["script"], "additionalProperties" => false, "properties" => { "script" => { "type" => "string" }, "timeout" => { "type" => "number" } } }] } } } | { "type" => "object", "required" => ::Array["click"], "additionalProperties" => false, "properties" => { "click" => { "type" => "object", "required" => ::Array["selector"], "additionalProperties" => false, "properties" => { "selector" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["evaluate"], "additionalProperties" => false, "properties" => { "evaluate" => { "type" => "object", "required" => ::Array["script"], "additionalProperties" => false, "properties" => { "script" => { "type" => "string" }, "assign" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["inject_script"], "additionalProperties" => false, "properties" => { "inject_script" => { "type" => "object", "additionalProperties" => false, "properties" => { "url" => { "type" => "string" }, "content" => { "type" => "string" }, "id" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["inject_style"], "additionalProperties" => false, "properties" => { "inject_style" => { "type" => "object", "additionalProperties" => false, "properties" => { "url" => { "type" => "string" }, "content" => { "type" => "string" }, "id" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["set_viewport"], "additionalProperties" => false, "properties" => { "set_viewport" => { "type" => "object", "required" => ::Array["width" | "height"], "additionalProperties" => false, "properties" => { "width" => { "type" => "number" }, "height" => { "type" => "number" }, "device_pixel_ratio" => { "type" => "number" } } } } } | { "type" => "object", "required" => ::Array["wait_network_idle"], "additionalProperties" => false, "properties" => { "wait_network_idle" => { "type" => "object", "additionalProperties" => false, "properties" => { "timeout" => { "type" => "number" } } } } }] }
|
|
34
|
+
|
|
35
|
+
RECIPE_ASSERT: { "description" => "one of the 8 known assertions. The presence-only ones (no_console_errors, no_network_failures, fonts_loaded, pdf_not_blank) never have their value read at run time - Recipe::Runner only checks the key is there - so they must be written as `true`; `false` or any other value is rejected as misleading rather than silently ignored. The last 3 assertions also require the pdf-reader gem at --validate time - a runtime fact this schema cannot express, see the top-level description", "oneOf" => ::Array[{ "type" => "object", "required" => ::Array["selector_exists"], "additionalProperties" => false, "properties" => { "selector_exists" => { "type" => "object", "required" => ::Array["selector"], "additionalProperties" => false, "properties" => { "selector" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["text_present"], "additionalProperties" => false, "properties" => { "text_present" => { "type" => "object", "required" => ::Array["text"], "additionalProperties" => false, "properties" => { "text" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["no_console_errors"], "additionalProperties" => false, "properties" => { "no_console_errors" => { "const" => true } } } | { "type" => "object", "required" => ::Array["no_network_failures"], "additionalProperties" => false, "properties" => { "no_network_failures" => { "const" => true } } } | { "type" => "object", "required" => ::Array["fonts_loaded"], "additionalProperties" => false, "properties" => { "fonts_loaded" => { "const" => true } } } | { "type" => "object", "required" => ::Array["page_count"], "additionalProperties" => false, "properties" => { "page_count" => { "oneOf" => ::Array[{ "type" => "integer" } | { "type" => "object", "properties" => { "min" => { "type" => "integer" }, "max" => { "type" => "integer" } }, "additionalProperties" => false }] } } } | { "type" => "object", "required" => ::Array["pdf_text_present"], "additionalProperties" => false, "properties" => { "pdf_text_present" => { "type" => "object", "required" => ::Array["text"], "additionalProperties" => false, "properties" => { "text" => { "type" => "string" } } } } } | { "type" => "object", "required" => ::Array["pdf_not_blank"], "additionalProperties" => false, "properties" => { "pdf_not_blank" => { "const" => true } } }] }
|
|
36
|
+
|
|
37
|
+
RECIPE_PRINT: { "type" => "object", "description" => "the same options `bidi2pdf render`'s own print flags map to; validated by Bidi::Commands::PrintParametersValidator", "properties" => { "background" => { "type" => "boolean" }, "shrinkToFit" => { "type" => "boolean" }, "orientation" => { "type" => "string", "enum" => ::Array["portrait" | "landscape"] }, "scale" => { "type" => "number", "minimum" => ::Float, "maximum" => ::Float }, "pageRanges" => { "type" => "array", "items" => { "type" => ::Array["integer" | "string"] } }, "margin" => { "type" => "object", "properties" => { "top" => { "type" => "number", "minimum" => 0 }, "bottom" => { "type" => "number", "minimum" => 0 }, "left" => { "type" => "number", "minimum" => 0 }, "right" => { "type" => "number", "minimum" => 0 } } }, "page" => { "type" => "object", "properties" => { "format" => { "type" => "string" }, "width" => { "type" => "number", "minimum" => ::Float }, "height" => { "type" => "number", "minimum" => ::Float } } } } }
|
|
38
|
+
|
|
39
|
+
RECIPE: { "$schema" => "https://json-schema.org/draft/2020-12/schema", "title" => "Bidi2pdf recipe", "description" => "input to `bidi2pdf run recipe.yml` (YAML or JSON). This schema is meant to be sufficient on its own to generate a recipe `--validate` will accept, with one exception JSON Schema cannot express: a page_count/pdf_text_present/pdf_not_blank assertion also requires the pdf-reader gem to be installed at validation time (a runtime environment fact, not a document-shape one) - `--validate` still catches a violation, with error code PDF_INSPECTION_UNAVAILABLE.", "type" => "object", "required" => ::Array["version" | "source" | "output"], "additionalProperties" => false, "properties" => { "version" => { "const" => 1 }, "source" => untyped, "browser" => { "type" => "object", "properties" => { "headless" => { "type" => "boolean" } } }, "headers" => { "type" => "object" }, "cookies" => { "type" => "object" }, "auth" => { "type" => "object", "properties" => { "username" => { "type" => "string" }, "password" => { "type" => "string" } } }, "actions" => { "type" => "array", "items" => untyped }, "assert" => { "type" => "array", "items" => untyped }, "print" => untyped, "output" => { "type" => "object", "description" => "at least one of pdf, manifest, screenshot", "anyOf" => ::Array[{ "required" => ::Array["pdf"] } | { "required" => ::Array["manifest"] } | { "required" => ::Array["screenshot"] }], "properties" => { "pdf" => { "type" => "string" }, "manifest" => { "type" => "string" }, "screenshot" => { "type" => "string" } } } } }
|
|
40
|
+
|
|
41
|
+
ALL: { "render" => untyped, "diagnose" => untyped, "run" => untyped, "manifest" => untyped, "recipe" => untyped, "event" => untyped }
|
|
42
|
+
|
|
43
|
+
def self.for: (untyped kind) -> untyped
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -67,6 +67,17 @@ module Bidi2pdf
|
|
|
67
67
|
|
|
68
68
|
def run: () -> untyped
|
|
69
69
|
|
|
70
|
+
# Like #run, but stops after navigation (and the configured waits) instead of printing, and
|
|
71
|
+
# leaves the tab open for the caller to inspect - used by `bidi2pdf diagnose`, which has no PDF
|
|
72
|
+
# to produce. The caller is responsible for #close_all once done.
|
|
73
|
+
#
|
|
74
|
+
# @return [Bidi2pdf::Bidi::BrowserTab] the navigated tab.
|
|
75
|
+
def run_diagnose: () -> untyped
|
|
76
|
+
|
|
77
|
+
# Closes what #run_diagnose opened. #run's own run_flow already does this itself (in its
|
|
78
|
+
# ensure), so this is only ever needed after #run_diagnose.
|
|
79
|
+
def close_all: () -> untyped
|
|
80
|
+
|
|
70
81
|
private
|
|
71
82
|
|
|
72
83
|
def setup_browser: () -> untyped
|
|
@@ -77,6 +88,12 @@ module Bidi2pdf
|
|
|
77
88
|
|
|
78
89
|
def add_basic_auth: () -> (nil | untyped)
|
|
79
90
|
|
|
91
|
+
# Navigate + wait only, no log_network_traffic/print side effects - #run_flow above
|
|
92
|
+
# intentionally keeps its own inline sequence (where log_network_traffic sits between the two
|
|
93
|
+
# waits) untouched rather than sharing this, since that render-only file-writing side effect
|
|
94
|
+
# has no place in a diagnose flow.
|
|
95
|
+
def navigate_and_wait: () -> untyped
|
|
96
|
+
|
|
80
97
|
# rubocop: disable Metrics/AbcSize
|
|
81
98
|
def run_flow: () -> untyped
|
|
82
99
|
|
data/sig/bidi2pdf/version.rbs
CHANGED
data/sig/bidi2pdf.rbs
CHANGED
|
@@ -11,13 +11,30 @@ module Bidi2pdf
|
|
|
11
11
|
|
|
12
12
|
PAPER_FORMATS_CM: { letter: { width: ::Float, height: ::Float }, legal: { width: ::Float, height: ::Float }, tabloid: { width: ::Float, height: ::Float }, ledger: { width: ::Float, height: ::Float }, a0: { width: ::Float, height: ::Float }, a1: { width: ::Float, height: ::Float }, a2: { width: ::Float, height: ::Float }, a3: { width: ::Float, height: ::Float }, a4: { width: ::Float, height: ::Float }, a5: { width: ::Float, height: ::Float }, a6: { width: ::Float, height: ::Float } }
|
|
13
13
|
|
|
14
|
+
# Base class for every error this gem raises. Carries the machine-readable surface used by
|
|
15
|
+
# --json/--json-stream/manifests/recipe results (see Bidi2pdf::ErrorCodes): #retryable? and
|
|
16
|
+
# #hint are overridden per subclass below where a useful default exists; #details is set at
|
|
17
|
+
# raise time for structured, serializable context (e.g. which selector, which assertion).
|
|
14
18
|
class Error < StandardError
|
|
19
|
+
@details: untyped
|
|
20
|
+
|
|
21
|
+
attr_reader details: untyped
|
|
22
|
+
|
|
23
|
+
def initialize: (?untyped? message, ?details: ::Hash[untyped, untyped]) -> void
|
|
24
|
+
|
|
25
|
+
def retryable?: () -> false
|
|
26
|
+
|
|
27
|
+
def hint: () -> nil
|
|
15
28
|
end
|
|
16
29
|
|
|
17
30
|
class SessionNotStartedError < Error
|
|
31
|
+
def retryable?: () -> true
|
|
32
|
+
|
|
33
|
+
def hint: () -> "Check Chrome/chromedriver are installed and reachable, or that --remote-browser-url points at a running instance"
|
|
18
34
|
end
|
|
19
35
|
|
|
20
36
|
class WebsocketError < Error
|
|
37
|
+
def retryable?: () -> true
|
|
21
38
|
end
|
|
22
39
|
|
|
23
40
|
class ClientError < WebsocketError
|
|
@@ -39,6 +56,9 @@ module Bidi2pdf
|
|
|
39
56
|
end
|
|
40
57
|
|
|
41
58
|
class CmdTimeoutError < ClientError
|
|
59
|
+
def retryable?: () -> true
|
|
60
|
+
|
|
61
|
+
def hint: () -> "Raise --default-timeout"
|
|
42
62
|
end
|
|
43
63
|
|
|
44
64
|
class PrintError < Error
|
|
@@ -70,15 +90,61 @@ module Bidi2pdf
|
|
|
70
90
|
attr_reader url: untyped
|
|
71
91
|
|
|
72
92
|
def initialize: (untyped url, ?untyped? message) -> void
|
|
93
|
+
|
|
94
|
+
def hint: () -> "Pass --auth user:pass, or check the credentials are still valid"
|
|
73
95
|
end
|
|
74
96
|
|
|
75
97
|
class NavigationTimeoutError < NavigationError
|
|
98
|
+
def retryable?: () -> true
|
|
99
|
+
|
|
100
|
+
def hint: () -> "Raise --default-timeout, or add --wait-network-idle if the page loads data after the load event"
|
|
76
101
|
end
|
|
77
102
|
|
|
78
103
|
class NavigationNotFoundError < NavigationError
|
|
104
|
+
def hint: () -> "Check the URL is correct"
|
|
79
105
|
end
|
|
80
106
|
|
|
81
107
|
class NavigationDNSError < NavigationError
|
|
108
|
+
@url: untyped
|
|
109
|
+
|
|
110
|
+
attr_reader url: untyped
|
|
111
|
+
|
|
112
|
+
def initialize: (?untyped? url, ?untyped? message) -> void
|
|
113
|
+
|
|
114
|
+
def retryable?: () -> true
|
|
115
|
+
|
|
116
|
+
def hint: () -> "Check the hostname is correct and reachable from this machine/container"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class MissingInputError < Error
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class MultipleInputSourcesError < Error
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class EmptyInputError < Error
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class InvalidConfigError < Error
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class InvalidPrintOptionError < Error
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class InvalidRecipeError < Error
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class SelectorNotFoundError < Error
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class PageNotAsExpectedError < Error
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class OutputWriteError < Error
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
class PdfInspectionUnavailableError < Error
|
|
147
|
+
def hint: () -> "Install the pdf-reader gem, or drop assertions/fields that need it"
|
|
82
148
|
end
|
|
83
149
|
|
|
84
150
|
attr_accessor self.default_timeout: untyped
|