diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index ea44a9eca..3727cbaf9 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -97,6 +97,9 @@ public class CliContext { @JsonProperty("locations") private Map locations = new HashMap(); + @JsonProperty("outputStyle") + private String outputStyle = null; + // TODO: Mark what goes here? private List bundleValidationRules = new ArrayList<>(); @@ -517,6 +520,14 @@ public class CliContext { this.showTimes = showTimes; } + public String getOutputStyle() { + return outputStyle; + } + + public void setOutputStyle(String outputStyle) { + this.outputStyle = outputStyle; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -553,12 +564,13 @@ public class CliContext { Objects.equals(showTimes, that.showTimes) && mode == that.mode && Objects.equals(locale, that.locale) && + Objects.equals(outputStyle, that.outputStyle) && Objects.equals(locations, that.locations); } @Override public int hashCode() { - return Objects.hash(doNative, anyExtensionsAllowed, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, noInvariants, wantInvariantsInMessages, map, output, htmlOutput, txServer, sv, txLog, mapLog, lang, fhirpath, snomedCT, targetVer, igs, questionnaireMode, profiles, sources, mode, locale, locations, crumbTrails, showTimes, allowExampleUrls); + return Objects.hash(doNative, anyExtensionsAllowed, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, noInvariants, wantInvariantsInMessages, map, output, htmlOutput, txServer, sv, txLog, mapLog, lang, fhirpath, snomedCT, targetVer, igs, questionnaireMode, profiles, sources, mode, locale, locations, crumbTrails, showTimes, allowExampleUrls, outputStyle); } @Override @@ -593,6 +605,7 @@ public class CliContext { ", mode=" + mode + ", securityChecks=" + securityChecks + ", crumbTrails=" + crumbTrails + + ", outputStyle=" + outputStyle + ", allowExampleUrls=" + allowExampleUrls + ", showTimes=" + showTimes + ", locale='" + locale + '\'' + diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/DefaultRenderer.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/DefaultRenderer.java new file mode 100644 index 000000000..3cb6f70af --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/DefaultRenderer.java @@ -0,0 +1,72 @@ +package org.hl7.fhir.validation.cli.renderers; + +import org.hl7.fhir.r5.model.OperationOutcome; +import org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity; +import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.validation.ValidationMessage; + +public class DefaultRenderer extends ValidationOutputRenderer { + + @Override + public void render(OperationOutcome oo) { + int error = 0; + int warn = 0; + int info = 0; + String file = ToolingExtensions.readStringExtension(oo, ToolingExtensions.EXT_OO_FILE); + + for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) { + if (issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL || issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR) + error++; + else if (issue.getSeverity() == OperationOutcome.IssueSeverity.WARNING) + warn++; + else + info++; + } + + if (moreThanOne) { + System.out.print("-- "); + System.out.print(file); + System.out.print(" --"); + System.out.println(Utilities.padLeft("", '-', Integer.max(38, file.length() + 6))); + } + System.out.println((error == 0 ? "Success" : "*FAILURE*") + ": " + Integer.toString(error) + " errors, " + Integer.toString(warn) + " warnings, " + Integer.toString(info) + " notes"); + for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) { + System.out.println(getIssueSummary(issue)); + ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg"); + if (vm != null && vm.sliceText != null && (crumbTrails || vm.isCriticalSignpost())) { + for (String s : vm.sliceText) { + System.out.println(" slice info: "+s); + } + } + } + if (moreThanOne) { + System.out.print("---"); + System.out.print(Utilities.padLeft("", '-', file.length())); + System.out.print("---"); + System.out.println(Utilities.padLeft("", '-', Integer.max(38, file.length() + 6))); + System.out.println(); + } + } + + private String getIssueSummary(OperationOutcome.OperationOutcomeIssueComponent issue) { + String loc; + if (issue.hasExpression()) { + int line = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_LINE, -1); + int col = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_COL, -1); + loc = " @ "+issue.getExpression().get(0).asStringValue() + (line >= 0 && col >= 0 ? " (line " + Integer.toString(line) + ", col" + Integer.toString(col) + ")" : ""); + } else if (issue.hasLocation()) { + loc = " @ "+issue.getLocation().get(0).asStringValue(); + } else { + int line = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_LINE, -1); + int col = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_COL, -1); + if (issue.getSeverity() == IssueSeverity.INFORMATION && (line == -1 || col == -1)) { + loc = ""; + } else { + loc = " @ "+"line " + Integer.toString(line) + ", col" + Integer.toString(col); + } + } + return " " + issue.getSeverity().getDisplay() + loc + ": " + issue.getDetails().getText(); + } + +} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/ESLintCompactRenderer.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/ESLintCompactRenderer.java new file mode 100644 index 000000000..a8aa21061 --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/ESLintCompactRenderer.java @@ -0,0 +1,18 @@ +package org.hl7.fhir.validation.cli.renderers; + +import org.hl7.fhir.r5.model.OperationOutcome; +import org.hl7.fhir.r5.utils.ToolingExtensions; + +public class ESLintCompactRenderer extends ValidationOutputRenderer { + + @Override + public void render(OperationOutcome oo) { + String file = ToolingExtensions.readStringExtension(oo, ToolingExtensions.EXT_OO_FILE); + for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) { + int line = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_LINE, -1); + int col = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_COL, -1); + System.out.println(file+": line " + Integer.toString(line) + ", col" + Integer.toString(col)+", "+issue.getSeverity().getDisplay()+" - "+issue.getDetails().getText()); + } + } + +} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/ValidationOutputRenderer.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/ValidationOutputRenderer.java new file mode 100644 index 000000000..067844e47 --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/renderers/ValidationOutputRenderer.java @@ -0,0 +1,27 @@ +package org.hl7.fhir.validation.cli.renderers; + +import org.hl7.fhir.r5.model.OperationOutcome; + +public abstract class ValidationOutputRenderer { + + protected boolean crumbTrails; + protected boolean moreThanOne; + + public boolean isCrumbTrails() { + return crumbTrails; + } + + public void setCrumbTrails(boolean crumbTrails) { + this.crumbTrails = crumbTrails; + } + + public void start(boolean moreThanOne) { + this.moreThanOne = moreThanOne; + } + + public abstract void render(OperationOutcome op); + + public void finish() { + } + +} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index c829567bd..7a3dd680a 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -26,6 +26,9 @@ import org.hl7.fhir.validation.IgLoader; import org.hl7.fhir.validation.ValidationEngine; import org.hl7.fhir.validation.ValidationRecord; import org.hl7.fhir.validation.cli.model.*; +import org.hl7.fhir.validation.cli.renderers.DefaultRenderer; +import org.hl7.fhir.validation.cli.renderers.ESLintCompactRenderer; +import org.hl7.fhir.validation.cli.renderers.ValidationOutputRenderer; import org.hl7.fhir.validation.cli.utils.EngineMode; import org.hl7.fhir.validation.cli.utils.VersionSourceInformation; @@ -96,20 +99,32 @@ public class ValidationService { long start = System.currentTimeMillis(); List records = new ArrayList<>(); Resource r = validator.validate(cliContext.getSources(), cliContext.getProfiles(), records); - int ec = 0; MemoryMXBean mbean = ManagementFactory.getMemoryMXBean(); System.out.println("Done. " + validator.getContext().clock().report()+". Memory = "+Utilities.describeSize(mbean.getHeapMemoryUsage().getUsed()+mbean.getNonHeapMemoryUsage().getUsed())); System.out.println(); + ValidationOutputRenderer renderer = makeValidationOutputRenderer(cliContext); + renderer.setCrumbTrails(validator.isCrumbTrails()); + + int ec = 0; if (cliContext.getOutput() == null) { - if (r instanceof Bundle) - for (Bundle.BundleEntryComponent e : ((Bundle) r).getEntry()) - ec = ec + displayOperationOutcome((OperationOutcome) e.getResource(), ((Bundle) r).getEntry().size() > 1, validator.isCrumbTrails()) + ec; - else if (r == null) { + if (r instanceof Bundle) { + renderer.start(((Bundle) r).getEntry().size() > 1); + for (Bundle.BundleEntryComponent e : ((Bundle) r).getEntry()) { + OperationOutcome op = (OperationOutcome) e.getResource(); + ec = ec + countErrors(op); + renderer.render(op); + } + renderer.finish(); + } else if (r == null) { ec = ec + 1; System.out.println("No output from validation - nothing to validate"); } else { - ec = displayOperationOutcome((OperationOutcome) r, false, validator.isCrumbTrails()); + renderer.start(false); + OperationOutcome op = (OperationOutcome) r; + ec = countErrors(op); + renderer.render((OperationOutcome) r); + renderer.finish(); } } else { IParser x; @@ -131,6 +146,30 @@ public class ValidationService { System.exit(ec > 0 ? 1 : 0); } + private int countErrors(OperationOutcome oo) { + int error = 0; + for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) { + if (issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL || issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR) + error++; + } + return error; + } + + private ValidationOutputRenderer makeValidationOutputRenderer(CliContext cliContext) { + String style = cliContext.getOutputStyle(); + // adding to this list? + // Must document the option at https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator#UsingtheFHIRValidator-ManagingOutput + // if you're going to make a PR, document the link where the outputstyle is documented, along with a sentence that describes it, in the PR notes + if (Utilities.noString(style)) { + return new DefaultRenderer(); + } else if (Utilities.existsInList(style, "eslint-compact")) { + return new ESLintCompactRenderer(); + } else { + System.out.println("Unknown output style '"+style+"'"); + return new DefaultRenderer(); + } + } + public void convertSources(CliContext cliContext, ValidationEngine validator) throws Exception { System.out.println(" ...convert"); validator.convert(cliContext.getSources().get(0), cliContext.getOutput()); @@ -270,67 +309,6 @@ public class ValidationService { return sessionId; } - public int displayOperationOutcome(OperationOutcome oo, boolean hasMultiples, boolean crumbs) { - int error = 0; - int warn = 0; - int info = 0; - String file = ToolingExtensions.readStringExtension(oo, ToolingExtensions.EXT_OO_FILE); - - for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) { - if (issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL || issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR) - error++; - else if (issue.getSeverity() == OperationOutcome.IssueSeverity.WARNING) - warn++; - else - info++; - } - - if (hasMultiples) { - System.out.print("-- "); - System.out.print(file); - System.out.print(" --"); - System.out.println(Utilities.padLeft("", '-', Integer.max(38, file.length() + 6))); - } - System.out.println((error == 0 ? "Success" : "*FAILURE*") + ": " + Integer.toString(error) + " errors, " + Integer.toString(warn) + " warnings, " + Integer.toString(info) + " notes"); - for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) { - System.out.println(getIssueSummary(issue)); - ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg"); - if (vm != null && vm.sliceText != null && (crumbs || vm.isCriticalSignpost())) { - for (String s : vm.sliceText) { - System.out.println(" slice info: "+s); - } - } - } - if (hasMultiples) { - System.out.print("---"); - System.out.print(Utilities.padLeft("", '-', file.length())); - System.out.print("---"); - System.out.println(Utilities.padLeft("", '-', Integer.max(38, file.length() + 6))); - System.out.println(); - } - return error; - } - - private String getIssueSummary(OperationOutcome.OperationOutcomeIssueComponent issue) { - String loc; - if (issue.hasExpression()) { - int line = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_LINE, -1); - int col = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_COL, -1); - loc = " @ "+issue.getExpression().get(0).asStringValue() + (line >= 0 && col >= 0 ? " (line " + Integer.toString(line) + ", col" + Integer.toString(col) + ")" : ""); - } else if (issue.hasLocation()) { - loc = " @ "+issue.getLocation().get(0).asStringValue(); - } else { - int line = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_LINE, -1); - int col = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_COL, -1); - if (issue.getSeverity() == IssueSeverity.INFORMATION && (line == -1 || col == -1)) { - loc = ""; - } else { - loc = " @ "+(line >= 0 && col >= 0 ? "line " + Integer.toString(line) + ", col" + Integer.toString(col) : "??"); - } - } - return " " + issue.getSeverity().getDisplay() + loc + ": " + issue.getDetails().getText(); - } - public String determineVersion(CliContext cliContext) throws Exception { return determineVersion(cliContext, null); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index d6a6a955d..66648b67c 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -61,6 +61,7 @@ public class Params { public static final String VERBOSE = "-verbose"; public static final String SHOW_TIMES = "-show-times"; public static final String ALLOW_EXAMPLE_URLS = "-allow-example-urls"; + public static final String OUTPUT_STYLE = "-output-style"; /** * Checks the list of passed in params to see if it contains the passed in param. @@ -203,6 +204,8 @@ public class Params { } } else if (args[i].equals(SHOW_TIMES)) { cliContext.setShowTimes(true); + } else if (args[i].equals(OUTPUT_STYLE)) { + cliContext.setOutputStyle(args[++i]); } else if (args[i].equals(SCAN)) { cliContext.setMode(EngineMode.SCAN); } else if (args[i].equals(TERMINOLOGY)) {