mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
# Description of Changes Currently when calculating the output file type for some tools, the system will get it wrong because it doesn't know about what the default parameters in tools are, so if it doesn't have a value for some key, it'll just bail out and say "it might not be compatible". This PR adds logic to `ToolIO` to read the default values set for the parameters if the tool has `ToolIOCase`s and takes them into account when figuring out the output type. I've built it with horrible Java reflection magic to avoid having to specify the default for params twice, which will make it impossible for the defaults to disagree with each other. This just runs once at startup so there's negligible performance impact. The change is easily tested with Change Parameters, which is just `add-password` behind the scenes but with the password params omitted (so Change Password is always PDF->PDF, never encrypted like Add Password). Also (somewhat hackily) fixes a bug I noticed where saving a Change Permissions step then leaving and returning to the pipeline will cause the step to be reloaded as Add Password. I've added a system to disambiguate tools which share the same endpoint (which is only these two currently). ## Currently <img width="455" height="135" alt="image" src="https://github.com/user-attachments/assets/c8867d6a-599b-4f21-a2db-4a1b6ac22d73" /> ## Now <img width="415" height="122" alt="image" src="https://github.com/user-attachments/assets/bd8d1bab-f00a-421b-8c91-5af0d2ad5335" />
336 lines
12 KiB
JSON
336 lines
12 KiB
JSON
{
|
|
"$comment": [
|
|
"Shared conformance cases for the tool I/O compatibility check.",
|
|
"Run by Java (ToolChainValidatorConformanceTest), the frontend (toolIOCompat.test.ts) and the",
|
|
"engine (test_tool_io_compat.py) against their own implementations, which must agree.",
|
|
"Specs are synthetic, so retyping a real endpoint never invalidates a case.",
|
|
"'expected' is matched on stepIndex/severity/code; messages are free text."
|
|
],
|
|
"specs": {
|
|
"pdfToPdf": { "accepts": ["PDF"], "produces": "PDF", "arity": "SISO", "cases": [] },
|
|
"pdfToPdfSplit": { "accepts": ["PDF"], "produces": "PDF", "arity": "SIMO", "cases": [] },
|
|
"pdfMerge": { "accepts": ["PDF"], "produces": "PDF", "arity": "MISO", "cases": [] },
|
|
"addPassword": { "accepts": ["PDF"], "produces": "PDF_ENCRYPTED", "arity": "SISO", "cases": [] },
|
|
"removePassword": { "accepts": ["PDF_ENCRYPTED"], "produces": "PDF", "arity": "SISO", "cases": [] },
|
|
"extractImages": { "accepts": ["PDF"], "produces": "IMAGE", "arity": "SIMO", "cases": [] },
|
|
"imageToPdf": { "accepts": ["IMAGE"], "produces": "PDF", "arity": "MISO", "cases": [] },
|
|
"getAttachments": { "accepts": ["PDF"], "produces": "ZIP", "arity": "SISO", "cases": [] },
|
|
"getInfo": { "accepts": ["PDF"], "produces": "JSON", "arity": "SISO", "cases": [] },
|
|
"validateOnly": { "accepts": ["PDF"], "produces": "NONE", "arity": "SISO", "cases": [] },
|
|
"anyToPdf": { "accepts": ["ANY"], "produces": "PDF", "arity": "SISO", "cases": [] },
|
|
"pdfToAny": { "accepts": ["PDF"], "produces": "ANY", "arity": "SISO", "cases": [] },
|
|
"pdfOrImage": { "accepts": ["PDF", "IMAGE"], "produces": "PDF", "arity": "SISO", "cases": [] },
|
|
"pdfToImageConfigurable": {
|
|
"accepts": ["PDF"],
|
|
"produces": "IMAGE",
|
|
"arity": "SIMO",
|
|
"cases": [
|
|
{
|
|
"when": [{ "param": "singleOrMultiple", "matches": ["single"] }],
|
|
"produces": "IMAGE",
|
|
"arity": "SISO"
|
|
}
|
|
]
|
|
},
|
|
"addPasswordConfigurable": {
|
|
"accepts": ["PDF"],
|
|
"produces": "PDF_ENCRYPTED",
|
|
"arity": "SISO",
|
|
"cases": [
|
|
{
|
|
"when": [{ "param": "password", "matches": [""] }],
|
|
"produces": "PDF",
|
|
"arity": "SISO"
|
|
}
|
|
]
|
|
},
|
|
"pdfToImageMixedCase": {
|
|
"accepts": ["PDF"],
|
|
"produces": "IMAGE",
|
|
"arity": "SIMO",
|
|
"cases": [
|
|
{
|
|
"when": [{ "param": "singleOrMultiple", "matches": [" SiNgLe "] }],
|
|
"produces": "IMAGE",
|
|
"arity": "SISO"
|
|
}
|
|
]
|
|
},
|
|
"addPasswordTwoKeys": {
|
|
"accepts": ["PDF"],
|
|
"produces": "PDF_ENCRYPTED",
|
|
"arity": "SISO",
|
|
"cases": [
|
|
{
|
|
"when": [
|
|
{ "param": "password", "matches": [""] },
|
|
{ "param": "ownerPassword", "matches": [""] }
|
|
],
|
|
"produces": "PDF",
|
|
"arity": "SISO"
|
|
}
|
|
]
|
|
},
|
|
"autoRotateShape": {
|
|
"accepts": ["PDF"],
|
|
"produces": "PDF",
|
|
"arity": "SISO",
|
|
"cases": [
|
|
{
|
|
"when": [{ "param": "dryRun", "matches": ["true"], "default": "false" }],
|
|
"produces": "JSON",
|
|
"arity": "SISO"
|
|
}
|
|
]
|
|
},
|
|
"changePermsShape": {
|
|
"accepts": ["PDF"],
|
|
"produces": "PDF_ENCRYPTED",
|
|
"arity": "SISO",
|
|
"cases": [
|
|
{
|
|
"when": [
|
|
{ "param": "password", "matches": [""], "default": "" },
|
|
{ "param": "ownerPassword", "matches": [""], "default": "" }
|
|
],
|
|
"produces": "PDF",
|
|
"arity": "SISO"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"cases": [
|
|
{
|
|
"name": "a plain pdf chain is clean",
|
|
"steps": [{ "spec": "pdfToPdf" }, { "spec": "pdfToPdf" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "an empty chain is clean",
|
|
"steps": [],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "a single step is never a transition",
|
|
"steps": [{ "spec": "extractImages" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "encrypting then editing is rejected",
|
|
"steps": [{ "spec": "addPassword" }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "encrypting then decrypting is allowed",
|
|
"steps": [{ "spec": "addPassword" }, { "spec": "removePassword" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "decrypting then editing is allowed",
|
|
"steps": [{ "spec": "removePassword" }, { "spec": "pdfToPdf" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "an ordinary tool cannot be handed an encrypted pdf from source",
|
|
"sourceFormat": "PDF_ENCRYPTED",
|
|
"steps": [{ "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 0, "severity": "ERROR", "code": "source-mismatch" }]
|
|
},
|
|
{
|
|
"name": "remove-password is the one thing an encrypted source can start with",
|
|
"sourceFormat": "PDF_ENCRYPTED",
|
|
"steps": [{ "spec": "removePassword" }, { "spec": "pdfToPdf" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "extracted images cannot feed a pdf tool",
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "extracted images can feed an image tool, and fan in",
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "imageToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-in" }]
|
|
},
|
|
{
|
|
"name": "splitting then a single-file tool fans out",
|
|
"steps": [{ "spec": "pdfToPdfSplit" }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-out" }]
|
|
},
|
|
{
|
|
"name": "splitting then merging fans in",
|
|
"steps": [{ "spec": "pdfToPdfSplit" }, { "spec": "pdfMerge" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-in" }]
|
|
},
|
|
{
|
|
"name": "an archive deliverable does not feed a pdf tool",
|
|
"steps": [{ "spec": "getAttachments" }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "a json report does not feed a pdf tool",
|
|
"steps": [{ "spec": "getInfo" }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "a step producing no file terminates the chain",
|
|
"steps": [{ "spec": "validateOnly" }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "a tool accepting anything swallows any upstream output",
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "anyToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-out" }]
|
|
},
|
|
{
|
|
"name": "a tool producing anything satisfies any downstream input",
|
|
"steps": [{ "spec": "pdfToAny" }, { "spec": "imageToPdf" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "a source of unknown type is not checked against step one",
|
|
"steps": [{ "spec": "imageToPdf" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "a multi-format tool accepts either branch",
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "pdfOrImage" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-out" }]
|
|
},
|
|
{
|
|
"name": "an unconfigured parameter-dependent output only warns",
|
|
"steps": [{ "spec": "addPasswordConfigurable" }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "WARN", "code": "output-uncertain" }]
|
|
},
|
|
{
|
|
"name": "configuring the parameter to the encrypting branch makes it an error",
|
|
"steps": [
|
|
{ "spec": "addPasswordConfigurable", "parameters": { "password": "hunter2" } },
|
|
{ "spec": "pdfToPdf" }
|
|
],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "configuring the parameter to the non-encrypting branch clears it",
|
|
"steps": [
|
|
{ "spec": "addPasswordConfigurable", "parameters": { "password": "" } },
|
|
{ "spec": "pdfToPdf" }
|
|
],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "a multi-condition case needs every condition to hold",
|
|
"steps": [
|
|
{
|
|
"spec": "addPasswordTwoKeys",
|
|
"parameters": { "password": "", "ownerPassword": "hunter2" }
|
|
},
|
|
{ "spec": "pdfToPdf" }
|
|
],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "a multi-condition case applies when every condition holds",
|
|
"steps": [
|
|
{ "spec": "addPasswordTwoKeys", "parameters": { "password": "", "ownerPassword": "" } },
|
|
{ "spec": "pdfToPdf" }
|
|
],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "a multi-condition case is unresolved when one condition is unconfigured",
|
|
"steps": [
|
|
{ "spec": "addPasswordTwoKeys", "parameters": { "password": "" } },
|
|
{ "spec": "pdfToPdf" }
|
|
],
|
|
"expected": [{ "stepIndex": 1, "severity": "WARN", "code": "output-uncertain" }]
|
|
},
|
|
{
|
|
"name": "the single branch of an arity case produces one file, so no fan-out",
|
|
"steps": [
|
|
{ "spec": "pdfToImageConfigurable", "parameters": { "singleOrMultiple": "single" } },
|
|
{ "spec": "imageToPdf" }
|
|
],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "the default branch of an arity case fans in",
|
|
"steps": [
|
|
{ "spec": "pdfToImageConfigurable", "parameters": { "singleOrMultiple": "multiple" } },
|
|
{ "spec": "imageToPdf" }
|
|
],
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-in" }]
|
|
},
|
|
{
|
|
"name": "a condition matches regardless of the casing and padding it was declared with",
|
|
"steps": [
|
|
{ "spec": "pdfToImageMixedCase", "parameters": { "singleOrMultiple": "SINGLE" } },
|
|
{ "spec": "imageToPdf" }
|
|
],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "a condition declared in mixed case still fails on a genuinely different value",
|
|
"steps": [
|
|
{ "spec": "pdfToImageMixedCase", "parameters": { "singleOrMultiple": "multiple" } },
|
|
{ "spec": "imageToPdf" }
|
|
],
|
|
"expected": [{ "stepIndex": 1, "severity": "INFO", "code": "fan-in" }]
|
|
},
|
|
{
|
|
"name": "an absent parameter takes its default, which here does not trigger the case",
|
|
"steps": [{ "spec": "autoRotateShape" }, { "spec": "pdfToPdf" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "an explicit value overrides the default and triggers the case",
|
|
"steps": [
|
|
{ "spec": "autoRotateShape", "parameters": { "dryRun": "true" } },
|
|
{ "spec": "pdfToPdf" }
|
|
],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "an absent parameter whose default triggers the case resolves to that branch",
|
|
"steps": [{ "spec": "changePermsShape" }, { "spec": "pdfToPdf" }],
|
|
"expected": []
|
|
},
|
|
{
|
|
"name": "setting the branch parameter away from its default flips the outcome",
|
|
"steps": [
|
|
{
|
|
"spec": "changePermsShape",
|
|
"parameters": { "password": "hunter2", "ownerPassword": "hunter2" }
|
|
},
|
|
{ "spec": "pdfToPdf" }
|
|
],
|
|
"expected": [{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" }]
|
|
},
|
|
{
|
|
"name": "an undeclared step warns and stops the chain being checked past it",
|
|
"steps": [{ "spec": "addPassword" }, { "spec": null }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "WARN", "code": "undeclared-operation" }]
|
|
},
|
|
{
|
|
"name": "the source format is not re-applied to a step that follows an undeclared one",
|
|
"sourceFormat": "PDF_ENCRYPTED",
|
|
"steps": [{ "spec": "removePassword" }, { "spec": null }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 1, "severity": "WARN", "code": "undeclared-operation" }]
|
|
},
|
|
{
|
|
"name": "an undeclared first step does not pass the source format on to the next step",
|
|
"sourceFormat": "PDF_ENCRYPTED",
|
|
"steps": [{ "spec": null }, { "spec": "pdfToPdf" }],
|
|
"expected": [{ "stepIndex": 0, "severity": "WARN", "code": "undeclared-operation" }]
|
|
},
|
|
{
|
|
"name": "every broken transition is reported, not just the first",
|
|
"steps": [{ "spec": "extractImages" }, { "spec": "pdfToPdf" }, { "spec": "imageToPdf" }],
|
|
"expected": [
|
|
{ "stepIndex": 1, "severity": "ERROR", "code": "format-mismatch" },
|
|
{ "stepIndex": 2, "severity": "ERROR", "code": "format-mismatch" }
|
|
]
|
|
}
|
|
]
|
|
}
|