Skip to content

Commit 902b9bc

Browse files
committed
[feature] add the validation service
1 parent c51cd0c commit 902b9bc

6 files changed

Lines changed: 245 additions & 41 deletions

File tree

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@
9797
<artifactId>jackson-databind</artifactId>
9898
<version>2.15.3</version>
9999
</dependency>
100+
101+
<!-- CSV validator -->
102+
<dependency>
103+
<groupId>uk.gov.nationalarchives</groupId>
104+
<artifactId>csv-validator-java-api</artifactId>
105+
<version>1.4.1</version>
106+
</dependency>
100107
</dependencies>
101108

102109
<build>

src/main/java/com/evolvedbinary/bblValidator/controller/ValidateController.java

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.evolvedbinary.bblValidator.controller;
22

3+
import com.evolvedbinary.bblValidator.dto.ValidationError;
34
import com.evolvedbinary.bblValidator.dto.ValidationForm;
45
import com.evolvedbinary.bblValidator.dto.ValidationResponse;
6+
import com.evolvedbinary.bblValidator.service.CsvValidationService;
57
import com.evolvedbinary.bblValidator.service.FileDownloadService;
68
import io.micronaut.http.MediaType;
79
import io.micronaut.http.annotation.Body;
@@ -15,16 +17,16 @@
1517

1618
import java.io.IOException;
1719
import java.nio.file.Path;
20+
import java.util.List;
1821

19-
/**
20-
* Controller for handling validation requests.
21-
*/
2222
@Controller("/validate")
2323
public class ValidateController {
2424

2525
private static final Logger LOG = LoggerFactory.getLogger(ValidateController.class);
2626
@Inject
2727
FileDownloadService fileDownloadService;
28+
@Inject
29+
CsvValidationService csvValidationService;
2830

2931
/**
3032
* Handles form URL encoded validation requests.
@@ -38,11 +40,10 @@ public ValidationResponse validateForm(@Body ValidationForm form) {
3840
try {
3941
Path downloadedFile = fileDownloadService.downloadToTemp(form.url());
4042
LOG.info("File downloaded to: {}", downloadedFile);
41-
// TODO: Perform validation with downloadedFile
42-
return new ValidationResponse(form.schemaId(), form.url(), true, "form handler");
43+
return performValidation(downloadedFile, form.schemaId());
4344
} catch (IOException e) {
4445
LOG.error("Failed to download file from URL: {}", form.url(), e);
45-
return new ValidationResponse(form.schemaId(), form.url(), false, "Download failed: " + e.getMessage());
46+
return createErrorResponse("Download failed: " + e.getMessage(), 0);
4647
}
4748
}
4849

@@ -55,16 +56,15 @@ public ValidationResponse validateForm(@Body ValidationForm form) {
5556
*/
5657
@Post
5758
@Consumes(MediaType.TEXT_CSV)
58-
public ValidationResponse validateCsv(@QueryValue("schema-id") String schemaId,
59+
public ValidationResponse validateCsv(@QueryValue("schema-id") String schemaId,
5960
@Body String csvContent) {
6061
try {
6162
Path tempFile = fileDownloadService.saveContentToTemp(csvContent, "uploaded-content.csv");
6263
LOG.info("CSV content saved to: {}", tempFile);
63-
// TODO: Perform validation with tempFile
64-
return new ValidationResponse(schemaId, "CSV content", true, "text handler");
64+
return performValidation(tempFile, schemaId);
6565
} catch (IOException e) {
6666
LOG.error("Failed to save CSV content to temp file", e);
67-
return new ValidationResponse(schemaId, "CSV content", false, "Failed to save content: " + e.getMessage());
67+
return createErrorResponse("Failed to save content: " + e.getMessage(), 0);
6868
}
6969
}
7070

@@ -76,17 +76,32 @@ public ValidationResponse validateCsv(@QueryValue("schema-id") String schemaId,
7676
* @return validation response
7777
*/
7878
@Post
79-
@Consumes(MediaType.ALL)
80-
public ValidationResponse validateParams(@QueryValue("schema-id") String schemaId,
79+
@Consumes(MediaType.ALL)
80+
public ValidationResponse validateParams(@QueryValue("schema-id") String schemaId,
8181
@QueryValue String url) {
8282
try {
8383
Path downloadedFile = fileDownloadService.downloadToTemp(url);
8484
LOG.info("File downloaded to: {}", downloadedFile);
85-
// TODO: Perform validation with downloadedFile
86-
return new ValidationResponse(schemaId, url, true, "query handler");
85+
return performValidation(downloadedFile, schemaId);
8786
} catch (IOException e) {
8887
LOG.error("Failed to download file from URL: {}", url, e);
89-
return new ValidationResponse(schemaId, url, false, "Download failed: " + e.getMessage());
88+
return createErrorResponse("Download failed: " + e.getMessage(), 0);
89+
}
90+
}
91+
92+
private ValidationResponse performValidation(Path csvFile, String schemaId) {
93+
CsvValidationService.ValidationResult result = csvValidationService.validateCsvFile(csvFile, schemaId);
94+
95+
if (result.hasErrorMessage()) {
96+
return createErrorResponse(result.getErrorMessage(), result.getExecutionTimeMs());
9097
}
98+
99+
return new ValidationResponse(result.isValid(), result.getErrors(), result.getExecutionTimeMs());
100+
}
101+
102+
private ValidationResponse createErrorResponse(String errorMessage, long executionTimeMs) {
103+
return new ValidationResponse(false,
104+
List.of(new ValidationError(errorMessage, 0, 0)),
105+
executionTimeMs);
91106
}
92107
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.evolvedbinary.bblValidator.dto;
2+
3+
import io.micronaut.serde.annotation.Serdeable;
4+
5+
@Serdeable
6+
public class ValidationError {
7+
8+
private String message;
9+
private int lineNumber;
10+
private int columnIndex;
11+
12+
public ValidationError() {
13+
}
14+
15+
public ValidationError(String message, int lineNumber, int columnIndex) {
16+
this.message = message;
17+
this.lineNumber = lineNumber;
18+
this.columnIndex = columnIndex;
19+
}
20+
21+
public String getMessage() {
22+
return message;
23+
}
24+
25+
public void setMessage(String message) {
26+
this.message = message;
27+
}
28+
29+
public int getLineNumber() {
30+
return lineNumber;
31+
}
32+
33+
public void setLineNumber(int lineNumber) {
34+
this.lineNumber = lineNumber;
35+
}
36+
37+
public int getColumnIndex() {
38+
return columnIndex;
39+
}
40+
41+
public void setColumnIndex(int columnIndex) {
42+
this.columnIndex = columnIndex;
43+
}
44+
}

src/main/java/com/evolvedbinary/bblValidator/dto/ValidationResponse.java

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,27 @@
22

33
import io.micronaut.serde.annotation.Serdeable;
44

5+
import java.util.ArrayList;
6+
import java.util.List;
7+
58
@Serdeable
69
public class ValidationResponse {
710

8-
private String schemaId;
9-
private String url;
1011
private boolean valid;
11-
private String source;
12+
private List<ValidationError> errors;
13+
private long executionTimeMs;
1214

1315
public ValidationResponse() {
16+
this.errors = new ArrayList<>();
1417
}
1518

16-
public ValidationResponse(String schemaId, String url, boolean valid, String source) {
17-
this.schemaId = schemaId;
18-
this.url = url;
19+
public ValidationResponse(boolean valid,
20+
List<ValidationError> errors, long executionTimeMs) {
21+
this.executionTimeMs = executionTimeMs;
1922
this.valid = valid;
20-
this.source = source;
21-
}
22-
23-
public String getSchemaId() {
24-
return schemaId;
25-
}
26-
27-
public void setSchemaId(String schemaId) {
28-
this.schemaId = schemaId;
29-
}
30-
31-
public String getUrl() {
32-
return url;
23+
this.errors = errors != null ? errors : new ArrayList<>();
3324
}
3425

35-
public void setUrl(String url) {
36-
this.url = url;
37-
}
3826

3927
public boolean isValid() {
4028
return valid;
@@ -44,11 +32,19 @@ public void setValid(boolean valid) {
4432
this.valid = valid;
4533
}
4634

47-
public String getSource() {
48-
return source;
35+
public List<ValidationError> getErrors() {
36+
return errors;
37+
}
38+
39+
public void setErrors(List<ValidationError> errors) {
40+
this.errors = errors;
41+
}
42+
43+
public long getExecutionTimeMs() {
44+
return executionTimeMs;
4945
}
5046

51-
public void setSource(String source) {
52-
this.source = source;
47+
public void setExecutionTimeMs(long executionTimeMs) {
48+
this.executionTimeMs = executionTimeMs;
5349
}
5450
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.evolvedbinary.bblValidator.service;
2+
3+
import com.evolvedbinary.bblValidator.dto.ValidationError;
4+
import jakarta.inject.Inject;
5+
import jakarta.inject.Singleton;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import uk.gov.nationalarchives.csv.validator.api.java.*;
9+
10+
import java.nio.charset.Charset;
11+
import java.nio.file.Path;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
import static uk.gov.nationalarchives.csv.validator.api.CsvValidator$.MODULE$;
16+
17+
18+
@Singleton
19+
public class CsvValidationService {
20+
21+
private static final Logger LOG = LoggerFactory.getLogger(CsvValidationService.class);
22+
private static final int DEFAULT_MAX_CHARS_PER_CELL = 8096;
23+
24+
@Inject
25+
private SchemaService schemaService;
26+
27+
public ValidationResult validateCsvFile(Path csvFilePath, String schemaId) {
28+
long startTime = System.currentTimeMillis();
29+
try {
30+
String schemaFilePath = String.valueOf(schemaService.getSchemaFilePath(schemaId));
31+
Charset csvEncoding = MODULE$.DEFAULT_ENCODING();
32+
boolean validateUtf8Encoding = csvEncoding.name().equals("UTF-8");
33+
Charset csvSchemaEncoding = MODULE$.DEFAULT_ENCODING();
34+
boolean failFast = false;
35+
List<Substitution> pathSubstitutions = new ArrayList<>();
36+
boolean enforceCaseSensitivePathChecks = false;
37+
boolean trace = false;
38+
boolean skipFileChecks = false;
39+
40+
CsvValidatorJavaBridge.ValidationRequest validationRequest = new CsvValidatorJavaBridge.ValidationRequest(
41+
csvFilePath.toString(), csvEncoding, validateUtf8Encoding, schemaFilePath,
42+
csvSchemaEncoding, true, failFast, pathSubstitutions,
43+
enforceCaseSensitivePathChecks, trace, null, skipFileChecks, DEFAULT_MAX_CHARS_PER_CELL);
44+
45+
CsvValidatorJavaBridge.ValidationResult result = CsvValidatorJavaBridge.validate(validationRequest);
46+
List<FailMessage> errors = result.errors();
47+
long executionTime = System.currentTimeMillis() - startTime;
48+
return processValidationMessages(errors, executionTime);
49+
50+
} catch (Exception e) {
51+
long executionTime = System.currentTimeMillis() - startTime;
52+
LOG.error("Error validating CSV file: {}", csvFilePath, e);
53+
return ValidationResult.error("Validation failed: " + e.getMessage(), executionTime);
54+
}
55+
}
56+
57+
58+
private ValidationResult processValidationMessages(List<FailMessage> messages, long executionTimeMs) {
59+
if (messages.isEmpty()) {
60+
LOG.info("CSV validation successful - no errors ({}ms)", executionTimeMs);
61+
return ValidationResult.success(executionTimeMs);
62+
}
63+
64+
List<ValidationError> errors = new ArrayList<>();
65+
66+
for (FailMessage message : messages) {
67+
ValidationError error = new ValidationError(
68+
message.getMessage(),
69+
message.getLineNumber(),
70+
message.getColumnIndex() + 1 // Add 1 for user display
71+
);
72+
errors.add(error);
73+
LOG.info("Validation error at line {}, column {}: {}",
74+
message.getLineNumber(), message.getColumnIndex(), message.getMessage());
75+
}
76+
77+
LOG.info("CSV validation completed - Valid: false, Errors: {} ({}ms)", errors.size(), executionTimeMs);
78+
return new ValidationResult(false, errors, executionTimeMs);
79+
}
80+
81+
82+
public static class ValidationResult {
83+
private final boolean valid;
84+
private final List<ValidationError> errors;
85+
private final String errorMessage;
86+
private final long executionTimeMs;
87+
88+
public ValidationResult(boolean valid, List<ValidationError> errors, long executionTimeMs) {
89+
this.valid = valid;
90+
this.errors = errors;
91+
this.errorMessage = null;
92+
this.executionTimeMs = executionTimeMs;
93+
}
94+
95+
private ValidationResult(String errorMessage, long executionTimeMs) {
96+
this.valid = false;
97+
this.errors = new ArrayList<>();
98+
this.errorMessage = errorMessage;
99+
this.executionTimeMs = executionTimeMs;
100+
}
101+
102+
public static ValidationResult success(long executionTimeMs) {
103+
return new ValidationResult(true, new ArrayList<>(), executionTimeMs);
104+
}
105+
106+
public static ValidationResult error(String errorMessage, long executionTimeMs) {
107+
return new ValidationResult(errorMessage, executionTimeMs);
108+
}
109+
110+
public boolean isValid() {
111+
return valid;
112+
}
113+
114+
public List<ValidationError> getErrors() {
115+
return errors;
116+
}
117+
118+
public String getErrorMessage() {
119+
return errorMessage;
120+
}
121+
122+
public boolean hasErrorMessage() {
123+
return errorMessage != null;
124+
}
125+
126+
public long getExecutionTimeMs() {
127+
return executionTimeMs;
128+
}
129+
}
130+
}

src/main/java/com/evolvedbinary/bblValidator/service/SchemaService.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class SchemaService {
2828

2929
private final List<SchemaInfo> schemas = new ArrayList<>();
3030
private final Map<String, String> schemaContents = new HashMap<>();
31+
private final Map<String, Path> schemaFilePaths = new HashMap<>();
3132
private final ObjectMapper objectMapper = new ObjectMapper();
3233

3334
@PostConstruct
@@ -90,6 +91,7 @@ private void loadSchemaMetadata(Path metadataPath) {
9091
if (Files.exists(schemaFilePath)) {
9192
String schemaContent = Files.readString(schemaFilePath, StandardCharsets.UTF_8);
9293
schemaContents.put(schemaInfo.getId(), schemaContent);
94+
schemaFilePaths.put(schemaInfo.getId(), schemaFilePath);
9395
schemas.add(schemaInfo);
9496
LOG.debug("Loaded schema: {}", schemaInfo.getId());
9597
} else {
@@ -113,5 +115,15 @@ public String getSchema(String schemaId) throws Exception {
113115

114116
return content;
115117
}
118+
119+
public Path getSchemaFilePath(String schemaId) throws Exception {
120+
Path filePath = schemaFilePaths.get(schemaId);
121+
122+
if (filePath == null) {
123+
throw new Exception("Schema file path not found: " + schemaId);
124+
}
125+
126+
return filePath;
127+
}
116128
}
117129

0 commit comments

Comments
 (0)