mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-03 20:04:28 +01:00
fix(api): return 204 No Content on failed PDF filters; add OpenAPI responses and safe resource handling (#4406)
# Description of Changes - **What was changed** - Added explicit `@ApiResponses` for filter endpoints with `200` (PDF passed) and `204` (did not pass) including `@Content` and media types. - Replaced ambiguous `null` returns with `ResponseEntity.noContent().build()` when a filter condition is not met. - Ensured `PDDocument` is properly closed using try-with-resources in relevant endpoints. - Consolidated comparison logic into a reusable, type-safe `compare<T extends Comparable<T>>()` helper for page count, page size, file size, and rotation checks. - Minor cleanup and consistency improvements across filter endpoints. - **Why the change was made** - To return correct HTTP semantics (avoid ambiguous `null` responses) and improve API reliability for clients consuming these endpoints. - To document expected responses clearly in the OpenAPI spec for better consumer tooling and DX. - To prevent potential resource leaks by consistently closing `PDDocument`. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
parent
5e281fa002
commit
f48f80927a
@ -15,6 +15,9 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -42,6 +45,16 @@ public class FilterController {
|
||||
@Operation(
|
||||
summary = "Checks if a PDF contains set text, returns true if does",
|
||||
description = "Input:PDF Output:Boolean Type:SISO")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "PDF passed filter",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_PDF_VALUE)),
|
||||
@ApiResponse(
|
||||
responseCode = "204",
|
||||
description = "PDF did not pass filter",
|
||||
content = @Content())
|
||||
})
|
||||
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
@ -54,148 +67,184 @@ public class FilterController {
|
||||
pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/filter-contains-image")
|
||||
@Operation(
|
||||
summary = "Checks if a PDF contains an image",
|
||||
description = "Input:PDF Output:Boolean Type:SISO")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "PDF passed filter",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_PDF_VALUE)),
|
||||
@ApiResponse(
|
||||
responseCode = "204",
|
||||
description = "PDF did not pass filter",
|
||||
content = @Content())
|
||||
})
|
||||
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String pageNumber = request.getPageNumbers();
|
||||
|
||||
PDDocument pdfDocument = pdfDocumentFactory.load(inputFile);
|
||||
if (PdfUtils.hasImages(pdfDocument, pageNumber))
|
||||
try (PDDocument pdfDocument = pdfDocumentFactory.load(inputFile)) {
|
||||
if (PdfUtils.hasImages(pdfDocument, pageNumber)) {
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/filter-page-count")
|
||||
@Operation(
|
||||
summary = "Checks if a PDF is greater, less or equal to a setPageCount",
|
||||
description = "Input:PDF Output:Boolean Type:SISO")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "PDF passed filter",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_PDF_VALUE)),
|
||||
@ApiResponse(
|
||||
responseCode = "204",
|
||||
description = "PDF did not pass filter",
|
||||
content = @Content())
|
||||
})
|
||||
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
int pageCount = request.getPageCount();
|
||||
String comparator = request.getComparator();
|
||||
// Load the PDF
|
||||
PDDocument document = pdfDocumentFactory.load(inputFile);
|
||||
int actualPageCount = document.getNumberOfPages();
|
||||
// Perform the comparison
|
||||
boolean valid =
|
||||
switch (comparator) {
|
||||
case "Greater" -> actualPageCount > pageCount;
|
||||
case "Equal" -> actualPageCount == pageCount;
|
||||
case "Less" -> actualPageCount < pageCount;
|
||||
default ->
|
||||
throw ExceptionUtils.createInvalidArgumentException(
|
||||
"comparator", comparator);
|
||||
};
|
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||
return null;
|
||||
boolean valid;
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
|
||||
int actualPageCount = document.getNumberOfPages();
|
||||
valid = compare(actualPageCount, pageCount, comparator);
|
||||
}
|
||||
|
||||
return valid
|
||||
? WebResponseUtils.multiPartFileToWebResponse(inputFile)
|
||||
: ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/filter-page-size")
|
||||
@Operation(
|
||||
summary = "Checks if a PDF is of a certain size",
|
||||
description = "Input:PDF Output:Boolean Type:SISO")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "PDF passed filter",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_PDF_VALUE)),
|
||||
@ApiResponse(
|
||||
responseCode = "204",
|
||||
description = "PDF did not pass filter",
|
||||
content = @Content())
|
||||
})
|
||||
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String standardPageSize = request.getStandardPageSize();
|
||||
String comparator = request.getComparator();
|
||||
|
||||
// Load the PDF
|
||||
PDDocument document = pdfDocumentFactory.load(inputFile);
|
||||
|
||||
final boolean valid;
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
|
||||
PDPage firstPage = document.getPage(0);
|
||||
PDRectangle actualPageSize = firstPage.getMediaBox();
|
||||
|
||||
// Calculate the area of the actual page size
|
||||
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
|
||||
|
||||
// Get the standard size and calculate its area
|
||||
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
|
||||
float standardArea = standardSize.getWidth() * standardSize.getHeight();
|
||||
|
||||
// Perform the comparison
|
||||
boolean valid =
|
||||
switch (comparator) {
|
||||
case "Greater" -> actualArea > standardArea;
|
||||
case "Equal" -> actualArea == standardArea;
|
||||
case "Less" -> actualArea < standardArea;
|
||||
default ->
|
||||
throw ExceptionUtils.createInvalidArgumentException(
|
||||
"comparator", comparator);
|
||||
};
|
||||
valid = compare(actualArea, standardArea, comparator);
|
||||
}
|
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||
return null;
|
||||
return valid
|
||||
? WebResponseUtils.multiPartFileToWebResponse(inputFile)
|
||||
: ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/filter-file-size")
|
||||
@Operation(
|
||||
summary = "Checks if a PDF is a set file size",
|
||||
description = "Input:PDF Output:Boolean Type:SISO")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "PDF passed filter",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_PDF_VALUE)),
|
||||
@ApiResponse(
|
||||
responseCode = "204",
|
||||
description = "PDF did not pass filter",
|
||||
content = @Content())
|
||||
})
|
||||
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
long fileSize = request.getFileSize();
|
||||
String comparator = request.getComparator();
|
||||
|
||||
// Get the file size
|
||||
|
||||
long actualFileSize = inputFile.getSize();
|
||||
boolean valid = compare(actualFileSize, fileSize, comparator);
|
||||
|
||||
// Perform the comparison
|
||||
boolean valid =
|
||||
switch (comparator) {
|
||||
case "Greater" -> actualFileSize > fileSize;
|
||||
case "Equal" -> actualFileSize == fileSize;
|
||||
case "Less" -> actualFileSize < fileSize;
|
||||
default ->
|
||||
throw ExceptionUtils.createInvalidArgumentException(
|
||||
"comparator", comparator);
|
||||
};
|
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||
return null;
|
||||
return valid
|
||||
? WebResponseUtils.multiPartFileToWebResponse(inputFile)
|
||||
: ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/filter-page-rotation")
|
||||
@Operation(
|
||||
summary = "Checks if a PDF is of a certain rotation",
|
||||
description = "Input:PDF Output:Boolean Type:SISO")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "PDF passed filter",
|
||||
content = @Content(mediaType = MediaType.APPLICATION_PDF_VALUE)),
|
||||
@ApiResponse(
|
||||
responseCode = "204",
|
||||
description = "PDF did not pass filter",
|
||||
content = @Content())
|
||||
})
|
||||
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
int rotation = request.getRotation();
|
||||
String comparator = request.getComparator();
|
||||
|
||||
// Load the PDF
|
||||
PDDocument document = pdfDocumentFactory.load(inputFile);
|
||||
|
||||
// Get the rotation of the first page
|
||||
boolean valid;
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
|
||||
PDPage firstPage = document.getPage(0);
|
||||
int actualRotation = firstPage.getRotation();
|
||||
valid = compare(actualRotation, rotation, comparator);
|
||||
}
|
||||
|
||||
// Perform the comparison
|
||||
boolean valid =
|
||||
switch (comparator) {
|
||||
case "Greater" -> actualRotation > rotation;
|
||||
case "Equal" -> actualRotation == rotation;
|
||||
case "Less" -> actualRotation < rotation;
|
||||
return valid
|
||||
? WebResponseUtils.multiPartFileToWebResponse(inputFile)
|
||||
: ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two values based on the provided comparator.
|
||||
*
|
||||
* @param <T> The type of the values being compared.
|
||||
* @param actual The actual value.
|
||||
* @param expected The expected value.
|
||||
* @param comparator The comparator to use (e.g., "Greater", "Less", "Equal").
|
||||
* @return True if the comparison is valid, false otherwise.
|
||||
*/
|
||||
private static <T extends Comparable<T>> boolean compare(
|
||||
T actual, T expected, String comparator) {
|
||||
return switch (comparator) {
|
||||
case "Greater" -> actual.compareTo(expected) > 0;
|
||||
case "Equal" -> actual.compareTo(expected) == 0;
|
||||
case "Less" -> actual.compareTo(expected) < 0;
|
||||
default ->
|
||||
throw ExceptionUtils.createInvalidArgumentException(
|
||||
"comparator", comparator);
|
||||
throw ExceptionUtils.createInvalidArgumentException("comparator", comparator);
|
||||
};
|
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user