Improve unsupported PDF character errors
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 30s
Build and Push Docker Images / build-frontend (push) Successful in 1m12s
Build and Push Docker Images / build-website (push) Successful in 18s
Build and Push Docker Images / build-docs (push) Successful in 17s
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 30s
Build and Push Docker Images / build-frontend (push) Successful in 1m12s
Build and Push Docker Images / build-website (push) Successful in 18s
Build and Push Docker Images / build-docs (push) Successful in 17s
This commit is contained in:
@@ -25,6 +25,7 @@ import { s3 } from "../utils/s3";
|
|||||||
import { secrets } from "../utils/secrets";
|
import { secrets } from "../utils/secrets";
|
||||||
import { storeExtractedTextForFile } from "../utils/documentText";
|
import { storeExtractedTextForFile } from "../utils/documentText";
|
||||||
import { generateLiquidityForecast } from "../utils/liquidityForecast";
|
import { generateLiquidityForecast } from "../utils/liquidityForecast";
|
||||||
|
import { getUnsupportedPdfCharacterMessage } from "../utils/pdfCharacterError";
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(isoWeek)
|
dayjs.extend(isoWeek)
|
||||||
dayjs.extend(isBetween)
|
dayjs.extend(isBetween)
|
||||||
@@ -134,7 +135,15 @@ export default async function functionRoutes(server: FastifyInstance) {
|
|||||||
return pdf // Fastify wandelt automatisch in JSON
|
return pdf // Fastify wandelt automatisch in JSON
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
reply.code(500).send({ error: "Failed to create PDF" })
|
const unsupportedCharacterMessage = getUnsupportedPdfCharacterMessage(err, body.data)
|
||||||
|
if (unsupportedCharacterMessage) {
|
||||||
|
return reply.code(422).send({
|
||||||
|
error: unsupportedCharacterMessage,
|
||||||
|
code: "UNSUPPORTED_PDF_CHARACTER",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.code(500).send({ error: "Failed to create PDF" })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
79
backend/src/utils/pdfCharacterError.ts
Normal file
79
backend/src/utils/pdfCharacterError.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
const PDF_CHARACTER_ERROR_PATTERN = /WinAnsi cannot encode "[\s\S]+?" \(0x([0-9a-f]+)\)/i
|
||||||
|
|
||||||
|
const DOCUMENT_FIELD_LABELS: Record<string, string> = {
|
||||||
|
title: "Dokumenttitel",
|
||||||
|
description: "Dokumentbeschreibung",
|
||||||
|
startText: "Einleitungstext",
|
||||||
|
endText: "Schlusstext",
|
||||||
|
text: "Bezeichnung",
|
||||||
|
descriptionText: "Beschreibung",
|
||||||
|
unit: "Einheit",
|
||||||
|
}
|
||||||
|
|
||||||
|
type CharacterLocation = {
|
||||||
|
label: string
|
||||||
|
characterPosition: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelForPath = (path: Array<string | number>, root: any): string => {
|
||||||
|
if (path[0] === "rows" && typeof path[1] === "number") {
|
||||||
|
const row = root?.rows?.[path[1]]
|
||||||
|
const position = row?.pos ? `Position ${row.pos}` : `Dokumentzeile ${path[1] + 1}`
|
||||||
|
const field = DOCUMENT_FIELD_LABELS[String(path[path.length - 1])] || String(path[path.length - 1])
|
||||||
|
return `${position}, ${field}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const field = String(path[path.length - 1])
|
||||||
|
return DOCUMENT_FIELD_LABELS[field] || field
|
||||||
|
}
|
||||||
|
|
||||||
|
const findCharacterLocations = (
|
||||||
|
value: unknown,
|
||||||
|
unsupportedCharacter: string,
|
||||||
|
root: any,
|
||||||
|
path: Array<string | number> = [],
|
||||||
|
): CharacterLocation[] => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
const characters = Array.from(value)
|
||||||
|
return characters.flatMap((character, index) =>
|
||||||
|
character === unsupportedCharacter
|
||||||
|
? [{ label: labelForPath(path, root), characterPosition: index + 1 }]
|
||||||
|
: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.flatMap((entry, index) =>
|
||||||
|
findCharacterLocations(entry, unsupportedCharacter, root, [...path, index])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
return Object.entries(value).flatMap(([key, entry]) =>
|
||||||
|
findCharacterLocations(entry, unsupportedCharacter, root, [...path, key])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getUnsupportedPdfCharacterMessage = (error: unknown, documentData: any): string | null => {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
|
const match = errorMessage.match(PDF_CHARACTER_ERROR_PATTERN)
|
||||||
|
if (!match) return null
|
||||||
|
|
||||||
|
const numericCodePoint = Number.parseInt(match[1], 16)
|
||||||
|
if (!Number.isSafeInteger(numericCodePoint) || numericCodePoint > 0x10FFFF) return null
|
||||||
|
|
||||||
|
const unsupportedCharacter = String.fromCodePoint(numericCodePoint)
|
||||||
|
const [location] = findCharacterLocations(documentData, unsupportedCharacter, documentData)
|
||||||
|
const codePoint = numericCodePoint
|
||||||
|
.toString(16)
|
||||||
|
.toUpperCase()
|
||||||
|
.padStart(4, "0")
|
||||||
|
const locationText = location
|
||||||
|
? ` Fundstelle: ${location.label}, Zeichen ${location.characterPosition}.`
|
||||||
|
: ""
|
||||||
|
|
||||||
|
return `Das Zeichen „${unsupportedCharacter}“ (Unicode U+${codePoint}) wird in PDF-Dokumenten nicht unterstützt.${locationText} Bitte ersetze oder entferne das Zeichen.`
|
||||||
|
}
|
||||||
27
backend/tests/pdfCharacterError.test.ts
Normal file
27
backend/tests/pdfCharacterError.test.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import assert from "node:assert/strict"
|
||||||
|
import test from "node:test"
|
||||||
|
import { getUnsupportedPdfCharacterMessage } from "../src/utils/pdfCharacterError"
|
||||||
|
|
||||||
|
test("reports unsupported characters with the document position and character offset", () => {
|
||||||
|
// pdf-lib displays supplementary Unicode characters as a private-use glyph,
|
||||||
|
// while the hexadecimal value still contains the original code point.
|
||||||
|
const error = new Error('WinAnsi cannot encode "" (0x1f600)')
|
||||||
|
const documentData = {
|
||||||
|
rows: [
|
||||||
|
{ pos: "1", text: "Montage" },
|
||||||
|
{ pos: "2.3", descriptionText: "Gerät 😀 montieren" },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
getUnsupportedPdfCharacterMessage(error, documentData),
|
||||||
|
"Das Zeichen „😀“ (Unicode U+1F600) wird in PDF-Dokumenten nicht unterstützt. Fundstelle: Position 2.3, Beschreibung, Zeichen 7. Bitte ersetze oder entferne das Zeichen."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns null for unrelated PDF errors", () => {
|
||||||
|
assert.equal(
|
||||||
|
getUnsupportedPdfCharacterMessage(new Error("Background PDF is invalid"), {}),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -45,6 +45,11 @@ export default defineNuxtPlugin(() => {
|
|||||||
let title = "Fehler"
|
let title = "Fehler"
|
||||||
let description = "Ein unerwarteter Fehler ist aufgetreten."
|
let description = "Ein unerwarteter Fehler ist aufgetreten."
|
||||||
|
|
||||||
|
if (status === 422 && response._data?.code === "UNSUPPORTED_PDF_CHARACTER") {
|
||||||
|
title = "Nicht unterstütztes Zeichen"
|
||||||
|
description = response._data.error
|
||||||
|
}
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 400:
|
case 400:
|
||||||
title = "Anfrage fehlerhaft"
|
title = "Anfrage fehlerhaft"
|
||||||
|
|||||||
Reference in New Issue
Block a user