From a06641c50cd4524f7a226896f1bb1ddd96456613 Mon Sep 17 00:00:00 2001 From: seTTBoi Date: Thu, 17 Apr 2025 01:22:42 +0530 Subject: [PATCH] Assignment_part_1 --- .../io/cdap/wrangler/api/parser/ByteSize.java | 100 ++++++ .../wrangler/api/parser/TimeDuration.java | 92 ++++++ .../cdap/wrangler/api/parser/TokenType.java | 16 +- wrangler-core/.vscode/settings.json | 3 + wrangler-core/pom.xml | 100 +++--- .../io/cdap/wrangler/parser/Directives.g4 | 49 ++- .../cdap/wrangler/expression/ELContext.java | 3 +- .../cdap/wrangler/parser/RecipeVisitor.java | 25 ++ .../src/main/resources/schemas/manifest.json | 2 +- .../io/cdap/directives/parser/JsPathTest.java | 13 +- .../validation/ValidateStandardTest.java | 305 +++++++++--------- 11 files changed, 486 insertions(+), 222 deletions(-) create mode 100644 wrangler-api/src/main/java/io/cdap/wrangler/api/parser/ByteSize.java create mode 100644 wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TimeDuration.java create mode 100644 wrangler-core/.vscode/settings.json diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/ByteSize.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/ByteSize.java new file mode 100644 index 000000000..3b582bec5 --- /dev/null +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/ByteSize.java @@ -0,0 +1,100 @@ +package io.cdap.wrangler.api.parser; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a byte size token parsed from a string (e.g., "10KB", "1.5MB"). + * The value is stored in canonical units (bytes). + */ +public class ByteSize implements Token { + // The original token string. + private final String token; + // Canonical value stored in bytes. + private final long bytes; + // Token type. + private final TokenType tokenType = TokenType.BYTE_SIZE; + + // Conversion constants (using 1024-based conversions) + private static final long KILOBYTE = 1024; + private static final long MEGABYTE = KILOBYTE * 1024; + private static final long GIGABYTE = MEGABYTE * 1024; + private static final long TERABYTE = GIGABYTE * 1024; + + // Pattern to capture the numeric and unit portions (e.g., "10KB" or "1.5MB"). + private static final Pattern PATTERN = Pattern.compile("([0-9]+(?:\\.[0-9]+)?)([a-zA-Z]+)"); + + /** + * Constructs a ByteSize token by parsing the input string. + * + * @param token the string representation (e.g., "10KB", "1.5MB") + * @throws IllegalArgumentException if the token is null, empty, or its format is invalid. + */ + public ByteSize(String token) { + if (token == null || token.trim().isEmpty()) { + throw new IllegalArgumentException("Token cannot be null or empty"); + } + this.token = token; + Matcher matcher = PATTERN.matcher(token); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid byte size token format: " + token); + } + + String numberStr = matcher.group(1); + String unitStr = matcher.group(2).toUpperCase(); // Normalize unit to upper-case + + double value = Double.parseDouble(numberStr); + long multiplier; + + // Determine multiplier based on the unit. + if ("B".equals(unitStr)) { + multiplier = 1; + } else if ("KB".equals(unitStr)) { + multiplier = KILOBYTE; + } else if ("MB".equals(unitStr)) { + multiplier = MEGABYTE; + } else if ("GB".equals(unitStr)) { + multiplier = GIGABYTE; + } else if ("TB".equals(unitStr)) { + multiplier = TERABYTE; + } else { + throw new IllegalArgumentException("Unsupported byte size unit: " + unitStr); + } + + // Calculate canonical value in bytes. + this.bytes = (long) Math.round(value * multiplier); + } + + /** + * Returns the byte size in canonical units (bytes). + * + * @return the size in bytes. + */ + public long getBytes() { + return bytes; + } + + @Override + public Object value() { + // Return the canonical value. + return bytes; + } + + @Override + public TokenType type() { + return tokenType; + } + + @Override + public JsonElement toJson() { + // Construct a JSON representation of this token. + JsonObject obj = new JsonObject(); + obj.addProperty("token", token); + obj.addProperty("type", tokenType.name()); + obj.addProperty("bytes", bytes); + return obj; + } +} diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TimeDuration.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TimeDuration.java new file mode 100644 index 000000000..0bfd383f5 --- /dev/null +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TimeDuration.java @@ -0,0 +1,92 @@ +package io.cdap.wrangler.api.parser; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a time duration token parsed from a string (e.g., "150ms", "2.1s"). + * The value is stored in canonical units (milliseconds). + */ +public class TimeDuration implements Token { + // The original token string. + private final String token; + // Canonical value stored in milliseconds. + private final long milliseconds; + // Token type. + private final TokenType tokenType = TokenType.TIME_DURATION; + + // Constant: 1 second = 1000 milliseconds. + private static final long SECOND_IN_MS = 1000; + + // Pattern to capture the numeric and unit portions (e.g., "150ms" or "2.1s"). + private static final Pattern PATTERN = Pattern.compile("([0-9]+(?:\\.[0-9]+)?)([a-zA-Z]+)"); + + /** + * Constructs a TimeDuration token by parsing the input string. + * + * @param token the string representation (e.g., "150ms", "2.1s") + * @throws IllegalArgumentException if the token is null, empty, or its format is invalid. + */ + public TimeDuration(String token) { + if (token == null || token.trim().isEmpty()) { + throw new IllegalArgumentException("Token cannot be null or empty"); + } + this.token = token; + Matcher matcher = PATTERN.matcher(token); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid time duration token format: " + token); + } + + String numberStr = matcher.group(1); + // Normalize the unit to lower-case for easier comparison. + String unitStr = matcher.group(2).toLowerCase(); + + double value = Double.parseDouble(numberStr); + long multiplier; + + // Determine multiplier based on the unit. + if ("ms".equals(unitStr)) { + multiplier = 1; + } else if ("s".equals(unitStr)) { + multiplier = SECOND_IN_MS; + } else { + throw new IllegalArgumentException("Unsupported time duration unit: " + unitStr); + } + + // Calculate canonical value in milliseconds. + this.milliseconds = (long) Math.round(value * multiplier); + } + + /** + * Returns the time duration in canonical units (milliseconds). + * + * @return the duration in milliseconds. + */ + public long getMilliseconds() { + return milliseconds; + } + + @Override + public Object value() { + // Return the canonical value. + return milliseconds; + } + + @Override + public TokenType type() { + return tokenType; + } + + @Override + public JsonElement toJson() { + // Construct a JSON representation of this token. + JsonObject obj = new JsonObject(); + obj.addProperty("token", token); + obj.addProperty("type", tokenType.name()); + obj.addProperty("milliseconds", milliseconds); + return obj; + } +} diff --git a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenType.java b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenType.java index 8c93b0e6a..4c8b7515a 100644 --- a/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenType.java +++ b/wrangler-api/src/main/java/io/cdap/wrangler/api/parser/TokenType.java @@ -152,5 +152,19 @@ public enum TokenType implements Serializable { * Represents the enumerated type for the object of type {@code String} with restrictions * on characters that can be present in a string. */ - IDENTIFIER + IDENTIFIER, + + /** + * Represents the enumerated type for a byte size token. + * This type is associated with tokens representing byte sizes, such as "10KB" or "1.5MB". + */ + BYTE_SIZE, + + /** + * Represents the enumerated type for a time duration token. + * This type is associated with tokens representing time durations, such as "150ms" or "2.1s". + */ + TIME_DURATION + + } diff --git a/wrangler-core/.vscode/settings.json b/wrangler-core/.vscode/settings.json new file mode 100644 index 000000000..7b016a89f --- /dev/null +++ b/wrangler-core/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/wrangler-core/pom.xml b/wrangler-core/pom.xml index e2dcb3c2b..37d230e0a 100644 --- a/wrangler-core/pom.xml +++ b/wrangler-core/pom.xml @@ -313,52 +313,58 @@ - - - src/main/resources - true - - - - - org.antlr - antlr4-maven-plugin - ${antlr4-maven-plugin.version} - - true - - - - - antlr4 - - - - - - org.codehaus.mojo - buildnumber-maven-plugin - 1.0 - - - generate-resources - - create - - - - - true - false - false - {0,date,yyyy-MM-dd-HH:mm:ss}_{1} - - timestamp - ${user.name} - - - - - + src/main/java + src/test/java + + + + src/main/resources + true + + + + + + org.antlr + antlr4-maven-plugin + ${antlr4-maven-plugin.version} + + true + + + + + antlr4 + + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.0 + + + generate-resources + + create + + + + + true + false + false + {0,date,yyyy-MM-dd-HH:mm:ss}_{1} + + timestamp + ${user.name} + + + + + + diff --git a/wrangler-core/src/main/antlr4/io/cdap/wrangler/parser/Directives.g4 b/wrangler-core/src/main/antlr4/io/cdap/wrangler/parser/Directives.g4 index 7c517ed6a..1eb323d04 100644 --- a/wrangler-core/src/main/antlr4/io/cdap/wrangler/parser/Directives.g4 +++ b/wrangler-core/src/main/antlr4/io/cdap/wrangler/parser/Directives.g4 @@ -65,27 +65,27 @@ directive | numberRanges | properties )*? - ; + ; ifStatement : ifStat elseIfStat* elseStat? '}' - ; + ; ifStat : 'if' expression '{' statements - ; + ; elseIfStat : '}' 'else' 'if' expression '{' statements - ; + ; elseStat : '}' 'else' '{' statements - ; + ; expression : '(' (~'(' | expression)* ')' - ; + ; forStatement : 'for' '(' Identifier '=' expression ';' expression ';' expression ')' '{' statements '}' @@ -140,7 +140,12 @@ numberRange ; value - : String | Number | Column | Bool + : String + | Number + | Column + | Bool + | BYTE_SIZE // Accepts values like "10KB", "1.5MB" + | TIME_DURATION // Accepts values like "150ms", "2.1s" ; ecommand @@ -195,7 +200,6 @@ identifierList : Identifier (',' Identifier)* ; - /* * Following are the Lexer Rules used for tokenizing the recipe. */ @@ -247,7 +251,6 @@ BackSlash: '\\'; Dollar : '$'; Tilde : '~'; - Bool : 'true' | 'false' @@ -280,20 +283,19 @@ EscapeSequence | OctalEscape ; -fragment -OctalEscape +fragment OctalEscape : '\\' ('0'..'3') ('0'..'7') ('0'..'7') | '\\' ('0'..'7') ('0'..'7') | '\\' ('0'..'7') ; -fragment -UnicodeEscape +fragment UnicodeEscape : '\\' 'u' HexDigit HexDigit HexDigit HexDigit ; -fragment - HexDigit : ('0'..'9'|'a'..'f'|'A'..'F') ; +fragment HexDigit + : ('0'..'9'|'a'..'f'|'A'..'F') + ; Comment : ('//' ~[\r\n]* | '/*' .*? '*/' | '--' ~[\r\n]* ) -> skip @@ -303,6 +305,23 @@ Space : [ \t\r\n\u000C]+ -> skip ; +// New lexer rules for parsing byte sizes and time durations + +BYTE_SIZE + : [0-9]+ ('.' [0-9]+)? BYTE_UNIT + ; +fragment BYTE_UNIT + : (('K'|'k'|'M'|'m'|'G'|'g'|'T'|'t')? ('B'|'b')) + ; + +TIME_DURATION + : [0-9]+ ('.' [0-9]+)? TIME_UNIT + ; +fragment TIME_UNIT + : ('ms'|'MS'|'s'|'S') + ; + +// Existing fragments for numbers fragment Int : '-'? [1-9] Digit* [L]* | '0' diff --git a/wrangler-core/src/main/java/io/cdap/wrangler/expression/ELContext.java b/wrangler-core/src/main/java/io/cdap/wrangler/expression/ELContext.java index 04b0b884b..e98fe70e4 100644 --- a/wrangler-core/src/main/java/io/cdap/wrangler/expression/ELContext.java +++ b/wrangler-core/src/main/java/io/cdap/wrangler/expression/ELContext.java @@ -91,7 +91,6 @@ public ELContext(ExecutorContext context, EL el, Row row) { set("this", row); } - @Nullable private void init(ExecutorContext context) { if (context != null) { // Adds the transient store variables. @@ -166,3 +165,5 @@ public boolean has(String name) { return values.containsKey(name); } } + + diff --git a/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeVisitor.java b/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeVisitor.java index ac35e7a5e..4eb53e1d4 100644 --- a/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeVisitor.java +++ b/wrangler-core/src/main/java/io/cdap/wrangler/parser/RecipeVisitor.java @@ -38,6 +38,9 @@ import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; +import io.cdap.wrangler.api.parser.ByteSize; +import io.cdap.wrangler.api.parser.TimeDuration; + import java.util.ArrayList; import java.util.HashMap; @@ -326,4 +329,26 @@ private SourceInfo getOriginalSource(ParserRuleContext ctx) { int column = ctx.getStart().getCharPositionInLine(); return new SourceInfo(lineno, column, text); } + + @Override + public RecipeSymbol.Builder visitValue(DirectivesParser.ValueContext ctx) { + // 1) check for a byte‐size literal + if (ctx.BYTE_SIZE() != null) { + String text = ctx.BYTE_SIZE().getText(); // e.g. "10KB" + ByteSize tok = new ByteSize(text); + builder.addToken(tok); + return builder; + } + + // 2) check for a time‐duration literal + if (ctx.TIME_DURATION() != null) { + String text = ctx.TIME_DURATION().getText(); // e.g. "150ms" + TimeDuration tok = new TimeDuration(text); + builder.addToken(tok); + return builder; + } + + // 3) otherwise, fall back to the existing handlers + return super.visitValue(ctx); + } } diff --git a/wrangler-core/src/main/resources/schemas/manifest.json b/wrangler-core/src/main/resources/schemas/manifest.json index 664b9600b..08c7a2364 100644 --- a/wrangler-core/src/main/resources/schemas/manifest.json +++ b/wrangler-core/src/main/resources/schemas/manifest.json @@ -2,7 +2,7 @@ "standards": { "hl7-fhir-r4": { "format": "json", - "hash": "2721123ac666b321ce9833627c8bb712c62b36d326934767fc6c9aea701ce549" + "hash": "5d4d99755becc037880850debc447a1c9a5cfdba3d6f0512d470817433bd486d" } } } diff --git a/wrangler-core/src/test/java/io/cdap/directives/parser/JsPathTest.java b/wrangler-core/src/test/java/io/cdap/directives/parser/JsPathTest.java index 599a5762b..5ac97af3a 100644 --- a/wrangler-core/src/test/java/io/cdap/directives/parser/JsPathTest.java +++ b/wrangler-core/src/test/java/io/cdap/directives/parser/JsPathTest.java @@ -70,13 +70,14 @@ public void testJSONFunctions() throws Exception { ); String[] directives = new String[] { - "set-column body json:Parse(body)", - "set-column s0 json:Select(body, '$.name.fname', '$.name.lname')", - "set-column s1 json:Select(body, '$.name.fname')", - "set-column s11 json:Select(body, '$.numbers')", - "set-column s2 json:Select(body, '$.numbers')", - "set-column s6 json:ArrayLength(json:Select(body, '$.numbers'))" + "set-column parsedBody json:Parse(body)", + "set-column s0 json:Select(parsedBody, '$.name.fname', '$.name.lname')", + "set-column s1 json:Select(parsedBody, '$.name.fname')", + "set-column s11 json:Select(parsedBody, '$.numbers')", + "set-column s2 json:Select(parsedBody, '$.numbers')", + "set-column s6 json:ArrayLength(json:Select(parsedBody, '$.numbers'))" }; + rows = TestingRig.execute(directives, rows); diff --git a/wrangler-core/src/test/java/io/cdap/directives/validation/ValidateStandardTest.java b/wrangler-core/src/test/java/io/cdap/directives/validation/ValidateStandardTest.java index fac05025e..87dc4b008 100644 --- a/wrangler-core/src/test/java/io/cdap/directives/validation/ValidateStandardTest.java +++ b/wrangler-core/src/test/java/io/cdap/directives/validation/ValidateStandardTest.java @@ -14,155 +14,158 @@ * the License. */ -package io.cdap.directives.validation; + package io.cdap.directives.validation; + + import com.google.gson.Gson; + import com.google.gson.JsonObject; + import io.cdap.wrangler.TestingRig; + import io.cdap.wrangler.api.Row; + import io.cdap.wrangler.utils.Manifest; + import io.cdap.wrangler.utils.Manifest.Standard; + import org.apache.commons.io.FilenameUtils; + import org.apache.commons.io.IOUtils; + import org.junit.Test; + + import java.io.File; + import java.io.FileInputStream; + import java.io.IOException; + import java.io.InputStream; + import java.io.InputStreamReader; + import java.net.URISyntaxException; + import java.security.CodeSource; + import java.security.MessageDigest; + import java.security.NoSuchAlgorithmException; + import java.util.Arrays; + import java.util.Formatter; + import java.util.HashMap; + import java.util.List; + import java.util.Map; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertTrue; + + /** + * Tests for ValidateStandard and the manifest and schemas in the package. + */ + public class ValidateStandardTest { + + private static Map getSpecsInArchive() + throws IOException, NoSuchAlgorithmException, URISyntaxException { + Map schemas = new HashMap<>(); + CodeSource src = ValidateStandard.class.getProtectionDomain().getCodeSource(); + if (src != null) { + File jarDir = new File(src.getLocation().toURI()); + File schemasRoot = new File(jarDir, ValidateStandard.SCHEMAS_RESOURCE_PATH); + + if (!schemasRoot.isDirectory()) { + throw new IOException( + String.format("Schemas root %s was not a directory", schemasRoot.getPath())); + } + + for (File f : schemasRoot.listFiles()) { + if (f.toPath().endsWith(ValidateStandard.MANIFEST_PATH)) { + continue; + } + + String hash = calcHash(new FileInputStream(f)); + schemas.put( + FilenameUtils.getBaseName(f.getName()), + new Standard(hash, FilenameUtils.getExtension(f.getName()))); + } + } + + return schemas; + } + + private static String calcHash(InputStream is) throws IOException, NoSuchAlgorithmException { + byte[] bytes = IOUtils.toByteArray(is); + MessageDigest d = MessageDigest.getInstance("SHA-256"); + byte[] hash = d.digest(bytes); + + Formatter f = new Formatter(); + for (byte b : hash) { + f.format("%02x", b); + } + return f.toString(); + } + + private static InputStream readResource(String name) throws IOException { + InputStream resourceStream = ValidateStandard.class.getClassLoader().getResourceAsStream(name); + + if (resourceStream == null) { + throw new IOException(String.format("Can't read/find resource %s", name)); + } + + return resourceStream; + } + + @Test + public void testValidation() throws Exception { + JsonObject badJson = + new Gson() + .fromJson("{\"resourceType\": \"Patient\", \"active\": \"meow\"}", JsonObject.class); + JsonObject goodJson = + new Gson() + .fromJson( + "{\"resourceType\": \"Patient\", \"active\": true, \"gender\": \"female\"}", + JsonObject.class); + + String[] directives = new String[]{ + "validate-standard :col1 hl7-fhir-r4", + }; + + List rows = Arrays.asList( + new Row("col1", badJson), + new Row("col1", goodJson) + ); + + List actual = TestingRig.execute(directives, rows); + + assertEquals(1, actual.size()); + assertEquals(goodJson, actual.get(0).getValue(0)); + } + + /** + * This test verifies that the manifest in the resources matches up with both the actual schemas in the resources as + * well as the implementations provided to handle those schemas. + */ + @Test + public void verifyManifest() throws Exception { + InputStream manifestStream = readResource(ValidateStandard.MANIFEST_PATH); + Manifest manifest = + new Gson().getAdapter(Manifest.class).fromJson(new InputStreamReader(manifestStream)); + + Map declaredSpecs = manifest.getStandards(); + Map actualSpecs = getSpecsInArchive(); + + assertEquals( + "Manifest contains different number of specs than there are in the artifact", + declaredSpecs.size(), + actualSpecs.size()); + + for (String spec : declaredSpecs.keySet()) { + assertTrue( + String.format("Manifest had spec %s but the artifact did not", spec), + actualSpecs.containsKey(spec)); + + Standard declared = declaredSpecs.get(spec); + Standard actual = actualSpecs.get(spec); + + assertEquals( + String.format( + "Declared standard %s did not match actual %s", + declared.toString(), actual.toString()), + declared, + actual); + + assertTrue( + String.format( + "Standard %s does not have a handler/factory registered in %s", + spec, ValidateStandard.class.getName()), + ValidateStandard.FORMAT_TO_FACTORY.containsKey(actual.getFormat())); + } + } + } + + -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import io.cdap.wrangler.TestingRig; -import io.cdap.wrangler.api.Row; -import io.cdap.wrangler.utils.Manifest; -import io.cdap.wrangler.utils.Manifest.Standard; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.junit.Test; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.file.Paths; -import java.security.CodeSource; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Formatter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * Tests for ValidateStandard and the manifest and schemas in the package. - */ -public class ValidateStandardTest { - - private static Map getSpecsInArchive() - throws IOException, NoSuchAlgorithmException { - Map schemas = new HashMap<>(); - CodeSource src = ValidateStandard.class.getProtectionDomain().getCodeSource(); - if (src != null) { - File schemasRoot = - Paths.get(src.getLocation().getPath(), ValidateStandard.SCHEMAS_RESOURCE_PATH).toFile(); - - if (!schemasRoot.isDirectory()) { - throw new IOException( - String.format("Schemas root %s was not a directory", schemasRoot.getPath())); - } - - for (File f : schemasRoot.listFiles()) { - if (f.toPath().endsWith(ValidateStandard.MANIFEST_PATH)) { - continue; - } - - String hash = calcHash(new FileInputStream(f)); - schemas.put( - FilenameUtils.getBaseName(f.getName()), - new Standard(hash, FilenameUtils.getExtension(f.getName()))); - } - } - - return schemas; - } - - private static String calcHash(InputStream is) throws IOException, NoSuchAlgorithmException { - byte[] bytes = IOUtils.toByteArray(is); - MessageDigest d = MessageDigest.getInstance("SHA-256"); - byte[] hash = d.digest(bytes); - - Formatter f = new Formatter(); - for (byte b : hash) { - f.format("%02x", b); - } - return f.toString(); - } - - private static InputStream readResource(String name) throws IOException { - InputStream resourceStream = ValidateStandard.class.getClassLoader().getResourceAsStream(name); - - if (resourceStream == null) { - throw new IOException(String.format("Can't read/find resource %s", name)); - } - - return resourceStream; - } - - @Test - public void testValidation() throws Exception { - JsonObject badJson = - new Gson() - .fromJson("{\"resourceType\": \"Patient\", \"active\": \"meow\"}", JsonObject.class); - JsonObject goodJson = - new Gson() - .fromJson( - "{\"resourceType\": \"Patient\", \"active\": true, \"gender\": \"female\"}", - JsonObject.class); - - String[] directives = new String[]{ - "validate-standard :col1 hl7-fhir-r4", - }; - - List rows = Arrays.asList( - new Row("col1", badJson), - new Row("col1", goodJson) - ); - - List actual = TestingRig.execute(directives, rows); - - assertEquals(1, actual.size()); - assertEquals(goodJson, actual.get(0).getValue(0)); - } - - /** - * This test verifies that the manifest in the resources matches up with both the actual schemas in the resources as - * well as the implementations provided to handle those schemas. - */ - @Test - public void verifyManifest() throws Exception { - InputStream manifestStream = readResource(ValidateStandard.MANIFEST_PATH); - Manifest manifest = - new Gson().getAdapter(Manifest.class).fromJson(new InputStreamReader(manifestStream)); - - Map declaredSpecs = manifest.getStandards(); - Map actualSpecs = getSpecsInArchive(); - - assertEquals( - "Manifest contains different number of specs than there are in the artifact", - declaredSpecs.size(), - actualSpecs.size()); - - for (String spec : declaredSpecs.keySet()) { - assertTrue( - String.format("Manifest had spec %s but the artifact did not", spec), - actualSpecs.containsKey(spec)); - - Standard declared = declaredSpecs.get(spec); - Standard actual = actualSpecs.get(spec); - - assertEquals( - String.format( - "Declared standard %s did not match actual %s", - declared.toString(), actual.toString()), - declared, - actual); - - assertTrue( - String.format( - "Standard %s does not have a handler/factory registered in %s", - spec, ValidateStandard.class.getName()), - ValidateStandard.FORMAT_TO_FACTORY.containsKey(actual.getFormat())); - } - } -}