Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8cfede0
[feature] sample schema for testing
marmoure Dec 23, 2025
135cf4c
[feature] validation view
marmoure Dec 24, 2025
fb36978
[feature] the web ui can take in remote url
marmoure Dec 29, 2025
0b94262
[feature] css improvments
marmoure Dec 30, 2025
ccd92a7
[feature] simple table for showing errors
marmoure Dec 30, 2025
3b4e86c
[refactor] code clean up
marmoure Dec 31, 2025
05a461d
[feature] clean up the error message from the vlidation results as we…
marmoure Dec 31, 2025
55f3d03
[feature] let the user know if the file is not UTF-8 valid
marmoure Dec 31, 2025
f0f01ab
[feature] include utf8 flag in tests
marmoure Dec 31, 2025
c28fa00
[feature] docker image
marmoure Jan 5, 2026
0b88db0
[feature] open api specs
marmoure Jan 6, 2026
4dcfbce
[bugfix] improve open api specs
marmoure Jan 6, 2026
9c2b950
[feature] introduce maven docker plugin
marmoure Jan 10, 2026
6faf892
[bugfix] mark all possible final variables as final
marmoure Jan 10, 2026
ab53721
[feature] improve poolingHttpClientConnectionManager config and build…
marmoure Jan 10, 2026
65ddd8f
[feature] clean up resources once the valdiation is completed
marmoure Jan 10, 2026
1cbf015
[feature] move inline css to external file
marmoure Jan 10, 2026
13461da
[feature] match the file path to the micronaut serverings
marmoure Jan 10, 2026
a9ec61b
[feature] show the api docs in the html view
marmoure Jan 10, 2026
32d913c
[bugfix] Return 404 for not found schema id
marmoure Jan 10, 2026
87cdd60
[bugifx] fix docker issues
marmoure Jan 12, 2026
6f9726b
[bugfix] Fix OpenAPI specs spelling mistakes and make sure the code m…
marmoure Jan 19, 2026
f169257
[feature] mont the schema dir from disk
marmoure Jan 19, 2026
0fb61a2
[bugfix] fix viaraible names in the velocity view
marmoure Jan 19, 2026
6f04bd2
[feature] instruction on how to run the docker image
marmoure Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,16 @@ mvn clean compile
```bash
mvn mn:run
```

## Using Docker:

```bash
mvn clean install
```
Use the absolute path to the schemas directory when running the docker container.

```bash
docker run -p 8080:8080 -v /path/to/schemas:/app/schemas:ro evolvedbinary/bbl-validator:1.0.0-SNAPSHOT
```


50 changes: 50 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@
<version>3.9.1</version>
</dependency>

<!-- Micronaut view render -->
<dependency>
<groupId>io.micronaut.views</groupId>
<artifactId>micronaut-views-core</artifactId>
<version>5.9.0</version>
</dependency>

<!-- Velocity template engine -->
<dependency>
<groupId>io.micronaut.views</groupId>
<artifactId>micronaut-views-velocity</artifactId>
<version>5.9.0</version>
</dependency>

<!-- Logback for logging -->
<dependency>
<groupId>ch.qos.logback</groupId>
Expand Down Expand Up @@ -226,6 +240,42 @@
<shared>true</shared>
</configuration>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.46.0</version>
<configuration>
<verbose>true</verbose>
<images>
<image>
<name>evolvedbinary/bbl-validator:%v</name>
<build>
<dockerFile>${project.build.outputDirectory}/Dockerfile</dockerFile>
<assembly>
<mode>dir</mode>
<inline>
<files>
<file>
<source>${project.build.directory}/${project.build.finalName}.jar</source>
<destName>bbl-validator-${project.version}.jar</destName>
</file>
</files>
</inline>
</assembly>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>default</id>
<phase>install</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public HttpResponse<Object> getSchema(@PathVariable("schema-id") final String sc
final String schema = schemaService.getSchema(schemaId);
if (schema == null) {
return HttpResponse
.badRequest()
.notFound()
.contentType(MediaType.APPLICATION_JSON_TYPE)
.body(new ErrorResponse(ErrorResponse.Code.SCHEMA_NOT_FOUND,"Schema not found with ID: " + schemaId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,18 @@ public HttpResponse<ResponseObject> validateCsv(@QueryValue("schema-id") final S
if (csvContent == null || csvContent.isEmpty()) {
return HttpResponse.badRequest().body(new ErrorResponse(ErrorResponse.Code.NO_CSV,"Empty CSV content"));
}
try {
final Path tempFile = fileDownloadService.saveContentToTemp(csvContent);
try {
LOG.trace("CSV content saved to: {}", tempFile);
return HttpResponse.ok(performValidation(tempFile, schemaId));
} finally {
Files.delete(tempFile);
final Path tempFile = fileDownloadService.saveContentToTemp(csvContent);
try {
LOG.trace("CSV content saved to: {}", tempFile);
return HttpResponse.ok(performValidation(tempFile, schemaId));
} finally {
Files.delete(tempFile);
}
} catch (final IOException e) {
LOG.error("Failed to save CSV content to temp file", e);
return HttpResponse.serverError().body(new ErrorResponse(ErrorResponse.Code.UNEXPECTED_ERROR,"Unable to store CSV: " + e.getMessage()));
}
} catch (final IOException e) {
LOG.error("Failed to save CSV content to temp file", e);
return HttpResponse.serverError().body(new ErrorResponse(ErrorResponse.Code.UNEXPECTED_ERROR,"Unable to store CSV: " + e.getMessage()));
}
}

/**
Expand Down Expand Up @@ -122,10 +122,6 @@ public HttpResponse<ResponseObject> validateParams(@QueryValue("schema-id") fina
private ResponseObject performValidation(final Path csvFile, final String schemaId) {
final CsvValidationService.ValidationResult result = csvValidationService.validateCsvFile(csvFile, schemaId);

if (result.hasErrorMessage()) {
return new ErrorResponse(ErrorResponse.Code.VALIDATION_ERROR,"An error occurred: " + result.getErrorMessage());
}

return new ValidationResponse(result.isValid(), result.getErrors(), result.getExecutionTimeMs());
return new ValidationResponse(result.isPassed(), result.getFailures(), result.getExecutionTime(), result.isUtf8Valid());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.evolvedbinary.bblValidator.controller;

import com.evolvedbinary.bblValidator.dto.ErrorResponse;
import com.evolvedbinary.bblValidator.dto.ValidationFailure;
import com.evolvedbinary.bblValidator.dto.ValidationResponse;
import com.evolvedbinary.bblValidator.service.CsvValidationService;
import com.evolvedbinary.bblValidator.service.FileDownloadService;
import com.evolvedbinary.bblValidator.service.SchemaService;
import io.micronaut.context.annotation.Value;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.views.View;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller("/views")
public class ValidationViewController {

@Inject
CsvValidationService csvValidationService;
@Inject
FileDownloadService fileDownloadService;
@Inject
SchemaService schemaService;

@Value("${api.version}")
String version;

@View("validate")
@Get("/validate")
public Map<String, Object> validate() {
final Map<String, Object> model = new HashMap<>();
model.put("version", version);
model.put("schemas", schemaService.listSchemas());
model.put("csvSource", "url");
return model;
}

@View("validate")
@Post(value = "/validate", consumes = MediaType.APPLICATION_FORM_URLENCODED)
public Map<String, Object> validateSubmit(@Body final Map<String, String> formData) {
final String schemaId = formData.get("schemaId");
final String csvSource = formData.get("csvSource");
final String csvUrl = formData.get("csvUrl");
final String csvContent = formData.get("csvContent");

final Map<String, Object> model = new HashMap<>();
model.put("version", version);
model.put("schemas", schemaService.listSchemas());
model.put("schemaId", schemaId);
model.put("csvSource", csvSource);
model.put("csvUrl", csvUrl);
model.put("csvContent", csvContent);


if ((csvContent == null || csvContent.isEmpty()) && (csvUrl == null || csvUrl.isEmpty())) {
model.put("error", new ErrorResponse(ErrorResponse.Code.NO_CSV, "Please provide either CSV content or CSV URL"));
return model;
}

final boolean isUrl = csvSource.equals("url");

try {
final Path tempFile = isUrl ? fileDownloadService.downloadToTemp(csvUrl) : fileDownloadService.saveContentToTemp(csvContent);
try {
final CsvValidationService.ValidationResult result = csvValidationService.validateCsvFile(tempFile, schemaId);
model.put("result", new ValidationResponse(result.isPassed(), result.getFailures(), result.getExecutionTime(), result.isUtf8Valid()));
model.put("errorsTable", getErrorsTable(result.getFailures()));
} finally {
Files.delete(tempFile);
}
} catch (final IOException e) {
model.put("error", new ErrorResponse(ErrorResponse.Code.UNEXPECTED_ERROR, "Internal error processing CSV: " + e.getMessage()));
}

return model;
}

private List<String> getErrorsTable(final List<ValidationFailure> failures) {
final List<String> table = new ArrayList<>();
for (final ValidationFailure failure : failures) {
table.add(failure.getMessage());
}
return table;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.evolvedbinary.bblValidator.dto;

import io.micronaut.serde.annotation.Serdeable;

/**
* Data transfer object representing a validation failure with location information.
*/
@Serdeable
public class ValidationFailure {

private final String message;
private final int line;
private final int column;

public ValidationFailure(final String message, final int line, final int column) {
this.message = message;
this.line = line;
this.column = column;
}

public String getMessage() {
return message;
}

public int getLine() {
return line;
}

public int getColumn() {
return column;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,33 @@
@Serdeable
public final class ValidationResponse implements ResponseObject {

private final boolean valid;
private final List<ValidationError> errors;
private final long executionTimeMs;

public ValidationResponse(final boolean valid,
final List<ValidationError> errors,
final long executionTimeMs) {
this.executionTimeMs = executionTimeMs;
this.valid = valid;
this.errors = errors != null ? errors : Collections.emptyList();
private final boolean passed;
private final List<ValidationFailure> failures;
private final long executionTime;
private final boolean utf8Valid;

public ValidationResponse(final boolean passed,
final List<ValidationFailure> failures,
final long executionTime,
final boolean utf8Valid) {
this.executionTime = executionTime;
this.passed = passed;
this.failures = failures != null ? failures : Collections.emptyList();
this.utf8Valid = utf8Valid;
}

public boolean isValid() {
return valid;
}
public boolean isPassed() {
return passed;
}

public List<ValidationError> getErrors() {
return errors;
public List<ValidationFailure> getFailures() {
return failures;
}

public long getExecutionTimeMs() {
return executionTimeMs;
public long getExecutionTime() {
return executionTime;
}

public boolean isUtf8Valid() { return utf8Valid; }

}
Loading