diff --git a/validator/src/main/java/tech/allegro/schema/json2avro/validator/FileValidationOutput.java b/validator/src/main/java/tech/allegro/schema/json2avro/validator/FileValidationOutput.java new file mode 100644 index 0000000..091582a --- /dev/null +++ b/validator/src/main/java/tech/allegro/schema/json2avro/validator/FileValidationOutput.java @@ -0,0 +1,26 @@ +package tech.allegro.schema.json2avro.validator; + +import tech.allegro.schema.json2avro.validator.schema.ValidationOutput; +import tech.allegro.schema.json2avro.validator.schema.ValidatorException; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +class FileValidationOutput implements ValidationOutput { + + private final Path outputPath; + + FileValidationOutput(Path outputPath) { + this.outputPath = outputPath; + } + + @Override + public void write(byte[] output) { + try { + Files.write(outputPath, output); + } catch (IOException e) { + throw new ValidatorException("Error occurred when writing the output", e); + } + } +} diff --git a/validator/src/main/java/tech/allegro/schema/json2avro/validator/ValidatorOptions.java b/validator/src/main/java/tech/allegro/schema/json2avro/validator/ValidatorOptions.java index aa55bb1..68934b1 100644 --- a/validator/src/main/java/tech/allegro/schema/json2avro/validator/ValidatorOptions.java +++ b/validator/src/main/java/tech/allegro/schema/json2avro/validator/ValidatorOptions.java @@ -2,13 +2,15 @@ import com.beust.jcommander.Parameter; +import java.nio.file.Path; + public class ValidatorOptions { @Parameter(names = {"-s", "--schema"}, description = "Path to the schema file", required = true) - private String schemaPath; + private Path schemaPath; @Parameter(names = {"-i", "--input"}, description = "Path to the validated file", required = true) - private String inputPath; + private Path inputPath; @Parameter(names = {"-d", "--debug"}, description = "Enables logging in debug mode") private boolean debug = true; @@ -16,22 +18,25 @@ public class ValidatorOptions { @Parameter(names = {"-m", "--mode"}, description = "Validation mode. Supported: json2avro, avro2json, json2avro2json") private String mode = "json2avro"; + @Parameter(names = {"-o", "--output"}, description = "Path to the generated file (it will be created or it will be truncated if exists)") + private Path outputPath; + @Parameter(names = "--help", help = true, description = "Displays this help message") private boolean help; - public String getSchemaPath() { + public Path getSchemaPath() { return schemaPath; } - public void setSchemaPath(String schemaPath) { + public void setSchemaPath(Path schemaPath) { this.schemaPath = schemaPath; } - public String getInputPath() { + public Path getInputPath() { return inputPath; } - public void setInputPath(String inputPath) { + public void setInputPath(Path inputPath) { this.inputPath = inputPath; } @@ -43,6 +48,14 @@ public void setDebug(boolean debug) { this.debug = debug; } + public Path getOutputPath() { + return outputPath; + } + + public void setOutputPath(Path outputPath) { + this.outputPath = outputPath; + } + public boolean isHelp() { return help; } diff --git a/validator/src/main/java/tech/allegro/schema/json2avro/validator/ValidatorRunner.java b/validator/src/main/java/tech/allegro/schema/json2avro/validator/ValidatorRunner.java index 3fda16a..66433be 100644 --- a/validator/src/main/java/tech/allegro/schema/json2avro/validator/ValidatorRunner.java +++ b/validator/src/main/java/tech/allegro/schema/json2avro/validator/ValidatorRunner.java @@ -5,12 +5,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tech.allegro.schema.json2avro.validator.schema.ValidationMode; +import tech.allegro.schema.json2avro.validator.schema.ValidationOutput; import tech.allegro.schema.json2avro.validator.schema.ValidatorException; import tech.allegro.schema.json2avro.validator.schema.Validators; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Paths; +import java.nio.file.Path; public class ValidatorRunner { @@ -23,6 +24,7 @@ public static void run(ValidatorOptions options) { .withMode(getMode(options)) .withInput(readFile(options.getInputPath())) .withSchema(readFile(options.getSchemaPath())) + .withOutput(getOutput(options.getOutputPath())) .build() .validate(); } catch (IOException e) { @@ -46,8 +48,12 @@ private static ValidationMode getMode(ValidatorOptions options) { return ValidationMode.from(options.getMode()); } - private static byte[] readFile(String path) throws IOException { - return Files.readAllBytes(Paths.get(path)); + private static byte[] readFile(Path path) throws IOException { + return Files.readAllBytes(path); + } + + private static ValidationOutput getOutput(Path outputPath) { + return outputPath != null ? new FileValidationOutput(outputPath) : ValidationOutput.NO_OUTPUT; } private static void configureLogging(ValidatorOptions options) { diff --git a/validator/src/main/java/tech/allegro/schema/json2avro/validator/schema/ValidationOutput.java b/validator/src/main/java/tech/allegro/schema/json2avro/validator/schema/ValidationOutput.java new file mode 100644 index 0000000..c8330d5 --- /dev/null +++ b/validator/src/main/java/tech/allegro/schema/json2avro/validator/schema/ValidationOutput.java @@ -0,0 +1,8 @@ +package tech.allegro.schema.json2avro.validator.schema; + +public interface ValidationOutput { + + ValidationOutput NO_OUTPUT = output -> {}; + + void write(byte[] output); +} diff --git a/validator/src/main/java/tech/allegro/schema/json2avro/validator/schema/avro/AvroValidator.java b/validator/src/main/java/tech/allegro/schema/json2avro/validator/schema/avro/AvroValidator.java index 47bd65f..19cd11e 100644 --- a/validator/src/main/java/tech/allegro/schema/json2avro/validator/schema/avro/AvroValidator.java +++ b/validator/src/main/java/tech/allegro/schema/json2avro/validator/schema/avro/AvroValidator.java @@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory; import tech.allegro.schema.json2avro.converter.JsonAvroConverter; import tech.allegro.schema.json2avro.validator.schema.ValidationMode; +import tech.allegro.schema.json2avro.validator.schema.ValidationOutput; import tech.allegro.schema.json2avro.validator.schema.ValidationResult; import tech.allegro.schema.json2avro.validator.schema.Validator; import tech.allegro.schema.json2avro.validator.schema.ValidatorException; @@ -22,14 +23,17 @@ public class AvroValidator implements Validator { private final ValidationMode mode; + private final ValidationOutput output; + private final JsonAvroConverter converter; - public AvroValidator(byte[] schema, byte[] content, ValidationMode mode) { + public AvroValidator(byte[] schema, byte[] content, ValidationMode mode, ValidationOutput output) { converter = new JsonAvroConverter(); try { this.schema = new Schema.Parser().parse(new ByteArrayInputStream(schema)); this.content = content; this.mode = mode; + this.output = output; } catch (IOException e) { throw new ValidatorException("An unexpected error occurred when parsing the schema", e); } @@ -56,8 +60,9 @@ public ValidationResult validate() { private byte [] convertAvroToJson(byte [] avro) { try { logger.debug("Converting AVRO to JSON"); - byte [] json = converter.convertToJson(avro, schema);; + byte [] json = converter.convertToJson(avro, schema); logger.debug("Validation result: success. JSON: \n{}", new String(json)); + output.write(json); return json; } catch (RuntimeException e) { throw new ValidatorException("Error occurred when validating the document", e); @@ -69,6 +74,7 @@ public ValidationResult validate() { logger.debug("Converting JSON to AVRO"); byte [] avro = converter.convertToAvro(json, schema); logger.debug("Validation result: success. AVRO: \n{}", new String(avro)); + output.write(avro); return avro; } catch (RuntimeException e) { throw new ValidatorException("Error occurred when validating the document", e); @@ -87,6 +93,8 @@ public static class Builder { private ValidationMode mode = ValidationMode.JSON_TO_AVRO; + private ValidationOutput output = ValidationOutput.NO_OUTPUT; + public Builder withSchema(byte[] schema) { this.schema = schema; return this; @@ -102,8 +110,13 @@ public Builder withMode(ValidationMode mode) { return this; } + public Builder withOutput(ValidationOutput output) { + this.output = output; + return this; + } + public AvroValidator build() { - return new AvroValidator(schema, input, mode); + return new AvroValidator(schema, input, mode, output); } } } diff --git a/validator/src/test/groovy/tech/allegro/schema/json2avro/validator/schema/avro/AvroValidatorSpec.groovy b/validator/src/test/groovy/tech/allegro/schema/json2avro/validator/schema/avro/AvroValidatorSpec.groovy index 8e2dee4..2235cd8 100644 --- a/validator/src/test/groovy/tech/allegro/schema/json2avro/validator/schema/avro/AvroValidatorSpec.groovy +++ b/validator/src/test/groovy/tech/allegro/schema/json2avro/validator/schema/avro/AvroValidatorSpec.groovy @@ -4,14 +4,17 @@ import groovy.json.JsonSlurper import org.apache.avro.Schema import org.apache.avro.generic.GenericData import org.apache.avro.generic.GenericRecord -import tech.allegro.schema.json2avro.validator.schema.ValidationMode -import tech.allegro.schema.json2avro.validator.schema.ValidatorException import spock.lang.Specification import spock.lang.Subject +import tech.allegro.schema.json2avro.validator.FileValidationOutput +import tech.allegro.schema.json2avro.validator.schema.ValidationMode import tech.allegro.schema.json2avro.validator.schema.ValidationResult +import tech.allegro.schema.json2avro.validator.schema.ValidatorException import tech.allegro.schema.json2avro.validator.schema.Validators import tech.allegro.schema.json2avro.validator.test.AvroUtils +import java.nio.file.Files + import static tech.allegro.schema.json2avro.validator.test.ResourceUtils.readResource class AvroValidatorSpec extends Specification { @@ -100,4 +103,59 @@ class AvroValidatorSpec extends Specification { expect: json.name == "Bob" } + + def "should save conversion from JSON to AVRO in file"() { + + setup: + def tempDirectory = Files.createTempDirectory(getClass().simpleName) + def savedUserPath = tempDirectory.resolve("saved_user.avro") + + and: + validator = Validators.avro() + .withInput(readResource("user.json")) + .withSchema(avroSchema) + .withOutput(new FileValidationOutput(savedUserPath)) + .build() + + when: + validator.validate() + + then: + def actualOutput = Files.readAllBytes(savedUserPath) + Arrays.equals(actualOutput, readResource("user.avro")) + + cleanup: + Files.delete(savedUserPath) + Files.delete(tempDirectory) + } + + def "should save conversion from AVRO to JSON in file"() { + + setup: + def tempDirectory = Files.createTempDirectory(getClass().simpleName) + def savedUserPath = tempDirectory.resolve("saved_user.json") + + and: + validator = Validators.avro() + .withInput(readResource("user.avro")) + .withSchema(avroSchema) + .withMode(ValidationMode.AVRO_TO_JSON) + .withOutput(new FileValidationOutput(savedUserPath)) + .build() + + when: + validator.validate() + + then: + def actualUser = new JsonSlurper().parseText(Files.readString(savedUserPath)) + + expect: + actualUser.name == "Bob" + actualUser.age == 50 + actualUser.favoriteColor == "blue" + + cleanup: + Files.delete(savedUserPath) + Files.delete(tempDirectory) + } } diff --git a/validator/src/test/groovy/tech/allegro/schema/json2avro/validator/test/ResourceUtils.groovy b/validator/src/test/groovy/tech/allegro/schema/json2avro/validator/test/ResourceUtils.groovy index face860..9be9803 100644 --- a/validator/src/test/groovy/tech/allegro/schema/json2avro/validator/test/ResourceUtils.groovy +++ b/validator/src/test/groovy/tech/allegro/schema/json2avro/validator/test/ResourceUtils.groovy @@ -1,18 +1,19 @@ package tech.allegro.schema.json2avro.validator.test import java.nio.file.Files +import java.nio.file.Path import java.nio.file.Paths class ResourceUtils { - static String resource(String path) { + static Path resource(String path) { - new File(ResourceUtils.classLoader.getResource(path).toURI()).getAbsolutePath() + Paths.get(ResourceUtils.classLoader.getResource(path).toURI()) } static byte[] readResource(String path) { - String filePath = resource(path) - return Files.readAllBytes(Paths.get(filePath)) + Path filePath = resource(path) + return Files.readAllBytes(filePath) } } \ No newline at end of file