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 { storeExtractedTextForFile } from "../utils/documentText";
|
||||
import { generateLiquidityForecast } from "../utils/liquidityForecast";
|
||||
import { getUnsupportedPdfCharacterMessage } from "../utils/pdfCharacterError";
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(isoWeek)
|
||||
dayjs.extend(isBetween)
|
||||
@@ -134,7 +135,15 @@ export default async function functionRoutes(server: FastifyInstance) {
|
||||
return pdf // Fastify wandelt automatisch in JSON
|
||||
} catch (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 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) {
|
||||
case 400:
|
||||
title = "Anfrage fehlerhaft"
|
||||
|
||||
Reference in New Issue
Block a user