Skip to content

Commit

Permalink
Feature/in memory sarif writing (#363)
Browse files Browse the repository at this point in the history
* Updated Sarif production to allow for writing the SARIF to a string.
* Small adjustments to get rid of some static analysis warnings.

---------

Co-authored-by: David Waltermire <[email protected]>
  • Loading branch information
wandmagic and david-waltermire authored Feb 27, 2025
1 parent 78a1b1c commit 1eb440a
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.schemastore.json.sarif.x210.ToolComponent;

import java.io.IOException;
import java.io.StringWriter;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
Expand Down Expand Up @@ -234,37 +235,28 @@ private void addConstraintValidationFinding(@NonNull ConstraintValidationFinding
}

/**
* Write the collection of findings to the provided output file.
* Generate a SARIF document based on the collected findings.
*
* @param outputFile
* the path to the output file to write to
* @param bindingContext
* the context used to access Metaschema module information based on
* Java class bindings
* @param outputUri
* the URI to use as the base for relative paths in the SARIF document
* @return the generated SARIF document
* @throws IOException
* if an error occurred while writing the SARIF file
* if an error occurred while generating the SARIF document
*/
public void write(
@NonNull Path outputFile,
@NonNull IBindingContext bindingContext) throws IOException {

URI output = ObjectUtils.notNull(outputFile.toUri());

@NonNull
private Sarif generateSarif(@NonNull URI outputUri) throws IOException {
Sarif sarif = new Sarif();
sarif.setVersion("2.1.0");

Run run = new Run();

sarif.addRun(run);

Artifact artifact = new Artifact();

artifact.setLocation(getArtifactRecord(getSource()).generateArtifactLocation(output));

artifact.setLocation(getArtifactRecord(getSource()).generateArtifactLocation(outputUri));
run.addArtifact(artifact);

for (IResult result : results) {
result.generateResults(output).forEach(run::addResult);
result.generateResults(outputUri).forEach(run::addResult);
}

IVersionInfo toolVersion = getToolVersion();
Expand All @@ -285,6 +277,48 @@ public void write(
run.setTool(tool);
}

return sarif;
}

/**
* Write the collection of findings to a string in SARIF format.
*
* @param bindingContext
* the context used to access Metaschema module information based on
* Java class bindings
* @return the SARIF document as a string
* @throws IOException
* if an error occurred while generating the SARIF document
*/
@NonNull
public String writeToString(@NonNull IBindingContext bindingContext) throws IOException {
bindingContext.registerModule(SarifModule.class);
try (StringWriter writer = new StringWriter()) {
bindingContext.newSerializer(Format.JSON, Sarif.class)
.disableFeature(SerializationFeature.SERIALIZE_ROOT)
.serialize(generateSarif(getSource()), writer);
return ObjectUtils.notNull(writer.toString());
}
}

/**
* Write the collection of findings to the provided output file.
*
* @param outputFile
* the path to the output file to write to
* @param bindingContext
* the context used to access Metaschema module information based on
* Java class bindings
* @throws IOException
* if an error occurred while writing the SARIF file
*/
public void write(
@NonNull Path outputFile,
@NonNull IBindingContext bindingContext) throws IOException {

URI output = ObjectUtils.notNull(outputFile.toUri());
Sarif sarif = generateSarif(output);

bindingContext.registerModule(SarifModule.class);
bindingContext.newSerializer(Format.JSON, Sarif.class)
.disableFeature(SerializationFeature.SERIALIZE_ROOT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,94 @@ class SarifValidationHandlerTest {
public final JUnit5Mockery context = new JUnit5Mockery();

@Test
void testValid() throws IOException {
void testWriteToString() throws IOException {
IVersionInfo versionInfo = context.mock(IVersionInfo.class);
IConstraint constraintA = ObjectUtils.notNull(context.mock(IConstraint.class, "constraintA"));
INodeItem node = ObjectUtils.notNull(context.mock(INodeItem.class));
IResourceLocation location = context.mock(IResourceLocation.class);

Path sourceFile = ObjectUtils.requireNonNull(Paths.get(".", "source.json").toAbsolutePath());

Set<String> helpUrls = Set.of("https://example.com/test");
Set<String> helpMarkdown = Set.of("**help text**");

context.checking(new Expectations() {
{ // NOPMD - intentional
allowing(versionInfo).getName();
will(returnValue("test"));
allowing(versionInfo).getVersion();
will(returnValue("0.0.0"));

allowing(constraintA).getLevel();
will(returnValue(IConstraint.Level.ERROR));
allowing(constraintA).getId();
will(returnValue(null));
allowing(constraintA).getFormalName();
will(returnValue("a formal name"));
allowing(constraintA).getDescription();
will(returnValue(MarkupLine.fromMarkdown("a description")));
allowing(constraintA).getProperties();
will(returnValue(
Map.ofEntries(
Map.entry(SarifValidationHandler.SARIF_HELP_URL_KEY, helpUrls),
Map.entry(SarifValidationHandler.SARIF_HELP_MARKDOWN_KEY, helpMarkdown))));
allowing(constraintA).getPropertyValues(SarifValidationHandler.SARIF_HELP_URL_KEY);
will(returnValue(helpUrls));
allowing(constraintA).getPropertyValues(SarifValidationHandler.SARIF_HELP_TEXT_KEY);
will(returnValue(Set.of()));
allowing(constraintA).getPropertyValues(SarifValidationHandler.SARIF_HELP_MARKDOWN_KEY);
will(returnValue(helpMarkdown));

allowing(node).getLocation();
will(returnValue(location));
allowing(node).getBaseUri();
will(returnValue(sourceFile.toUri()));
allowing(node).getMetapath();
will(returnValue("/node/child"));

allowing(location).getLine();
will(returnValue(42));
allowing(location).getColumn();
will(returnValue(0));
allowing(location).getByteOffset();
will(returnValue(1024L));
allowing(location).getCharOffset();
will(returnValue(2048L));
}
});

SarifValidationHandler handler
= new SarifValidationHandler(ObjectUtils.notNull(sourceFile.toUri()), versionInfo);

handler.addFinding(ConstraintValidationFinding.builder(constraintA, node)
.kind(IValidationFinding.Kind.FAIL)
.build());

String sarifOutput = handler.writeToString(IBindingContext.newInstance());

Path sarifSchema = Paths.get("modules/sarif/sarif-schema-2.1.0.json");

try (Reader schemaReader = Files.newBufferedReader(sarifSchema, StandardCharsets.UTF_8)) {
JsonNode schemaNode = new OrgJsonNode(new JSONObject(new JSONTokener(schemaReader)));

JsonNode instanceNode = new OrgJsonNode(new JSONObject(sarifOutput));

Validator.Result result
= new ValidatorFactory().withDialect(new Dialects.Draft2020Dialect()).validate(schemaNode, instanceNode);
StringJoiner sj = new StringJoiner("\n");
for (dev.harrel.jsonschema.Error finding : result.getErrors()) {
sj.add(String.format("[%s]%s %s for schema '%s'",
finding.getInstanceLocation(),
finding.getKeyword() == null ? "" : " " + finding.getKeyword() + ":",
finding.getError(),
finding.getSchemaLocation()));
}
assertTrue(result.isValid(), () -> "SARIF output failed schema validation. Errors:\n" + sj.toString());
}
}

@Test
void testWriteToFile() throws IOException {

IVersionInfo versionInfo = context.mock(IVersionInfo.class);
IConstraint constraintA = ObjectUtils.notNull(context.mock(IConstraint.class, "constraintA"));
Expand Down

0 comments on commit 1eb440a

Please sign in to comment.