Skip to content

Commit c7c8385

Browse files
authored
fix: improve error handling for invalid input in processing endpoints (#71)
1 parent c8a2976 commit c7c8385

3 files changed

Lines changed: 47 additions & 20 deletions

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,13 @@ This dataset is the result of the extraction of software mentions from the set o
206206

207207
### Error handling
208208

209-
The processing endpoints (`processSoftwareText`, `annotateSoftwarePDF`, `annotateSoftwareXML`, `annotateSoftwareTEI`) **fail loud**: when an internal step fails — for instance the context classifier, or the parsing of an XML/TEI input — the service returns an HTTP `500` together with a descriptive error message, instead of silently returning an empty or partial `200` response. A `204` (no content) is reserved for the case where the input is valid but genuinely contains nothing to extract. Clients should therefore treat a `500` as a real processing failure to surface or retry, not as "no mentions found".
209+
The processing endpoints (`processSoftwareText`, `annotateSoftwarePDF`, `annotateSoftwareXML`, `annotateSoftwareTEI`) **fail loud** instead of silently returning an empty or partial `200` response, and distinguish *who* is at fault:
210+
211+
- **`400 Bad Request`** — the submitted document is invalid input the service cannot process: a malformed or unparseable PDF, publisher XML or TEI file (e.g. not well-formed XML, an empty upload, or a PDF with no extractable content). The response body carries a descriptive message.
212+
- **`500 Internal Server Error`** — a genuine internal failure of the pipeline (model inference, context classification, etc.) on otherwise-valid input.
213+
- **`204 No Content`** — the input is valid but genuinely contains nothing to extract.
214+
215+
Clients should treat a `400` as "fix the input and resubmit" and a `500` as a real server-side failure to surface or retry — neither should be read as "no mentions found".
210216

211217
### /service/processSoftwareText
212218

src/main/java/org/grobid/core/engines/SoftwareParser.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.grobid.core.engines.label.TaggingLabels;
2424
import org.grobid.core.engines.tagging.GrobidCRFEngine;
2525
import org.grobid.core.exceptions.GrobidException;
26+
import org.grobid.core.exceptions.GrobidExceptionStatus;
2627
import org.grobid.core.factory.GrobidFactory;
2728
import org.grobid.core.features.FeatureFactory;
2829
import org.grobid.core.features.FeaturesVectorSoftware;
@@ -825,7 +826,12 @@ public Pair<List<SoftwareEntity>, Document> processPDF(
825826
entities = SoftwareContextClassifier.getInstance(softwareConfiguration).classifyDocumentContexts(entities);
826827

827828
} catch (Exception e) {
828-
throw new GrobidException("Cannot process pdf file: " + file.getPath(), e);
829+
// preserve the underlying status (e.g. BAD_INPUT_DATA for a corrupt PDF raised by GROBID)
830+
// so that invalid input is still reported as such rather than downgraded to a generic error
831+
GrobidExceptionStatus status = (e instanceof GrobidException)
832+
? ((GrobidException) e).getStatus()
833+
: GrobidExceptionStatus.GENERAL;
834+
throw new GrobidException("Cannot process pdf file: " + file.getPath(), e, status);
829835
}
830836

831837
return Pair.of(entities, doc);
@@ -2487,27 +2493,25 @@ public int boostrapTrainingPDF(String inputDirectory,
24872493
public Triple<Optional<ArticleBiblio>, List<SoftwareEntity>, List<BibDataSet>> processXML(File file,
24882494
boolean disambiguate,
24892495
boolean addParagraphContext) throws IOException {
2490-
Triple<Optional<ArticleBiblio>, List<SoftwareEntity>, List<BibDataSet>> resultExtraction = null;
2496+
org.w3c.dom.Document document;
24912497
try {
24922498
String tei = processXML(file);
24932499
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
24942500
factory.setNamespaceAware(true);
24952501
DocumentBuilder builder = factory.newDocumentBuilder();
24962502
//tei = avoidDomParserAttributeBug(tei);
24972503

2498-
org.w3c.dom.Document document = builder.parse(new InputSource(new StringReader(tei)));
2504+
document = builder.parse(new InputSource(new StringReader(tei)));
24992505
//document.getDocumentElement().normalize();
2500-
2501-
resultExtraction = processTEIDocument(document, disambiguate, addParagraphContext);
2502-
25032506
//tei = restoreDomParserAttributeBug(tei);
25042507

25052508
} catch (final Exception exp) {
2506-
throw new GrobidException("An error occurred while processing the following XML file: "
2507-
+ file.getPath(), exp);
2509+
// a failure to transform/parse the input is a client error: the submitted XML is invalid
2510+
throw new GrobidException("An error occurred while parsing the following XML file: "
2511+
+ file.getPath(), exp, GrobidExceptionStatus.BAD_INPUT_DATA);
25082512
}
25092513

2510-
return resultExtraction;
2514+
return processTEIDocument(document, disambiguate, addParagraphContext);
25112515
}
25122516

25132517

@@ -2519,22 +2523,22 @@ public Triple<Optional<ArticleBiblio>, List<SoftwareEntity>, List<BibDataSet>> p
25192523
boolean disambiguate,
25202524
boolean addParagraphContext
25212525
) throws IOException {
2522-
Triple<Optional<ArticleBiblio>, List<SoftwareEntity>, List<BibDataSet>> resultExtraction = null;
2526+
org.w3c.dom.Document document;
25232527
try {
25242528
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
25252529
factory.setNamespaceAware(true);
25262530
DocumentBuilder builder = factory.newDocumentBuilder();
2527-
org.w3c.dom.Document document = builder.parse(file);
2531+
document = builder.parse(file);
25282532
//document.getDocumentElement().normalize();
2529-
resultExtraction = processTEIDocument(document, disambiguate, addParagraphContext);
25302533
//tei = restoreDomParserAttributeBug(tei);
25312534

25322535
} catch (final Exception exp) {
2533-
throw new GrobidException("An error occurred while processing the following TEI file: "
2534-
+ file.getPath(), exp);
2536+
// a failure to parse the input is a client error: the submitted TEI is invalid
2537+
throw new GrobidException("An error occurred while parsing the following TEI file: "
2538+
+ file.getPath(), exp, GrobidExceptionStatus.BAD_INPUT_DATA);
25352539
}
25362540

2537-
return resultExtraction;
2541+
return processTEIDocument(document, disambiguate, addParagraphContext);
25382542
}
25392543

25402544
/**

src/main/java/org/grobid/service/controller/SoftwareProcessFile.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import org.grobid.core.engines.Engine;
1616
import org.grobid.core.engines.SoftwareParser;
1717
import org.grobid.core.engines.config.GrobidAnalysisConfig;
18+
import org.grobid.core.exceptions.GrobidException;
19+
import org.grobid.core.exceptions.GrobidExceptionStatus;
1820
import org.grobid.core.factory.GrobidFactory;
1921
import org.grobid.core.layout.Page;
2022
import org.grobid.core.utilities.IOUtilities;
@@ -48,6 +50,21 @@ public class SoftwareProcessFile {
4850
public SoftwareProcessFile() {
4951
}
5052

53+
/**
54+
* Build the error response for a processing failure. Failures caused by invalid input
55+
* (a {@link GrobidException} flagged as {@link GrobidExceptionStatus#BAD_INPUT_DATA}, e.g. a
56+
* malformed/unparseable PDF, XML or TEI) are reported as HTTP 400; anything else is a genuine
57+
* internal error and reported as HTTP 500.
58+
*/
59+
private static Response buildErrorResponse(Exception exp) {
60+
Status httpStatus = Status.INTERNAL_SERVER_ERROR;
61+
if (exp instanceof GrobidException
62+
&& ((GrobidException) exp).getStatus() == GrobidExceptionStatus.BAD_INPUT_DATA) {
63+
httpStatus = Status.BAD_REQUEST;
64+
}
65+
return Response.status(httpStatus).entity(exp.getMessage()).build();
66+
}
67+
5168
/**
5269
* Uploads the origin PDF, process it and return PDF annotations for the software mention objects in JSON.
5370
*
@@ -142,7 +159,7 @@ public static Response processPDFAnnotation(final InputStream inputStream,
142159
response = Response.status(Status.SERVICE_UNAVAILABLE).build();
143160
} catch (Exception exp) {
144161
LOGGER.error("An unexpected exception occurs. ", exp);
145-
response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(exp.getMessage()).build();
162+
response = buildErrorResponse(exp);
146163
} finally {
147164
IOUtilities.removeTempFile(originFile);
148165
}
@@ -243,7 +260,7 @@ public static Response processPDFAnnotation(final InputStream inputStream,
243260
response = Response.status(Status.SERVICE_UNAVAILABLE).build();
244261
} catch (Exception exp) {
245262
LOGGER.error("An unexpected exception occurs. ", exp);
246-
response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(exp.getMessage()).build();
263+
response = buildErrorResponse(exp);
247264
} finally {
248265
IOUtilities.removeTempFile(tmpPdf);
249266
}
@@ -328,7 +345,7 @@ public static Response extractXML(final InputStream inputStream,
328345
response = Response.status(Status.SERVICE_UNAVAILABLE).build();
329346
} catch (Exception exp) {
330347
LOGGER.error("An unexpected exception occurs. ", exp);
331-
response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(exp.getMessage()).build();
348+
response = buildErrorResponse(exp);
332349
} finally {
333350
IOUtilities.removeTempFile(originFile);
334351
}
@@ -420,7 +437,7 @@ public static Response extractTEI(
420437
response = Response.status(Status.SERVICE_UNAVAILABLE).build();
421438
} catch (Exception exp) {
422439
LOGGER.error("An unexpected exception occurs. ", exp);
423-
response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(exp.getMessage()).build();
440+
response = buildErrorResponse(exp);
424441
} finally {
425442
IOUtilities.removeTempFile(originFile);
426443
}

0 commit comments

Comments
 (0)