Skip to content

Commit

Permalink
chore: Change SARIF output format
Browse files Browse the repository at this point in the history
  • Loading branch information
rsenden committed Apr 5, 2024
1 parent 7512176 commit 9b1d84c
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ public static final class ActionStepDescriptor implements IActionIfSupplier {
private List<ActionStepSetDescriptor> set;
/** Optional write operations */
private List<ActionStepWriteDescriptor> write;
/** Optional forEach operation */
private ActionStepForEachDescriptor forEach;

/**
* This method is invoked by the parent element (which may either be another
Expand All @@ -253,38 +255,7 @@ public final void postLoad(ActionDescriptor action) {
if ( requests!=null ) { requests.forEach(d->d.postLoad(action)); }
if ( set!=null ) { set.forEach(d->d.postLoad(action)); }
if ( write!=null ) { write.forEach(d->d.postLoad(action)); }
}
}

/**
* This class describes a forEach element, allowing iteration over the output of
* the parent element, like the response of a REST request or the contents of a
* action parameter.
*/
@Reflectable @NoArgsConstructor
@Data
public static final class ActionStepForEachDescriptor implements IActionIfSupplier {
/** Optional if-expression, executing steps only if condition evaluates to true */
@JsonProperty("if") private SimpleExpression _if;
/** Optional break-expression, terminating forEach if condition evaluates to true */
private SimpleExpression breakIf;
/** Required name for this step element */
private String name;
/** Optional requests for which to embed the response in each forEach node */
private List<ActionStepRequestDescriptor> embed;
/** Steps to be repeated for each value */
@JsonProperty("do") private List<ActionStepDescriptor> _do;

/**
* This method is invoked by the {@link ActionStepDescriptor#postLoad()}
* method. It checks that required properties are set, then calls the postLoad() method for
* each sub-step.
*/
public final void postLoad(ActionDescriptor action) {
checkNotBlank("forEach name", name, this);
checkNotNull("forEach do", _do, this);
if ( embed!=null ) { embed.forEach(d->d.postLoad(action)); }
_do.forEach(d->d.postLoad(action));
if ( forEach!=null ) { forEach.postLoad(action); }
}
}

Expand Down Expand Up @@ -316,14 +287,12 @@ public void postLoad(ActionDescriptor action) {
()->"No value template found with name "+valueTemplate);
}
}
}

// Ideally, this should be an inner class on ActionStepRequestDescriptor, but this causes
// issues with native images; see https://github.com/formkiq/graalvm-annotations-processor/issues/11
// TODO: Add other operations like 'merge' for merging two ObjectNodes or ArrayNodes?
@Reflectable
public static enum ActionStepSetOperation {
replace, append;

// TODO: Add other operations like 'merge' for merging two ObjectNodes or ArrayNodes?
@Reflectable
public static enum ActionStepSetOperation {
replace, append;
}
}

/**
Expand All @@ -348,6 +317,45 @@ public void postLoad(ActionDescriptor action) {
}
}

/**
* This class describes a forEach element, allowing iteration over the output of
* a given input.
*/
@Reflectable @NoArgsConstructor
@Data
public static final class ActionStepForEachDescriptor implements IActionIfSupplier {
/** Processor that runs the forEach steps. This expression must evaluate to an
* IActionStepForEachProcessor instance. */
private SimpleExpression processor;
/** Optional if-expression, executing steps only if condition evaluates to true */
@JsonProperty("if") private SimpleExpression _if;
/** Optional break-expression, terminating forEach if condition evaluates to true */
private SimpleExpression breakIf;
/** Required name for this step element */
private String name;
/** Steps to be repeated for each value */
@JsonProperty("do") private List<ActionStepDescriptor> _do;

/**
* This method is invoked by the {@link ActionStepDescriptor#postLoad()}
* method. It checks that required properties are set, then calls the postLoad() method for
* each sub-step.
*/
public final void postLoad(ActionDescriptor action) {
checkNotBlank("forEach name", name, this);
checkNotNull("forEach do", _do, this);
_do.forEach(d->d.postLoad(action));
}

@FunctionalInterface
public static interface IActionStepForEachProcessor {
/** Implementations of this method should invoke the given function for every
* JsonNode to be processed, and terminate processing if the given function
* returns false. */
public void process(Function<JsonNode, Boolean> consumer);
}
}

/**
* This class describes a REST request.
*/
Expand Down Expand Up @@ -377,7 +385,7 @@ public static final class ActionStepRequestDescriptor implements IActionIfSuppli
/** Optional steps to be executed on request failure; if not declared, an exception will be thrown */
private List<ActionStepDescriptor> onFail;
/** Optional forEach block to be repeated for every response element */
private ActionStepForEachDescriptor forEach;
private ActionStepRequestForEachDescriptor forEach;

/**
* This method is invoked by {@link ActionStepDescriptor#postLoad()}
Expand All @@ -397,23 +405,51 @@ protected final void postLoad(ActionDescriptor action) {
forEach.postLoad(action);
}
}
}

// Ideally, this should be an inner class on ActionStepRequestDescriptor, but this causes
// issues with native images; see https://github.com/formkiq/graalvm-annotations-processor/issues/11
@Reflectable
public static enum ActionStepRequestType {
simple, paged
}

// Ideally, this should be an inner class on ActionStepRequestDescriptor, but this causes
// issues with native images; see https://github.com/formkiq/graalvm-annotations-processor/issues/11
@Reflectable @NoArgsConstructor
@Data
public static final class ActionStepRequestPagingProgressDescriptor {
private TemplateExpression prePageLoad;
private TemplateExpression postPageLoad;
private TemplateExpression postPageProcess;

/**
* This class describes a request forEach element, allowing iteration over the output of
* the parent element, like the response of a REST request or the contents of a
* action parameter.
*/
@Reflectable @NoArgsConstructor
@Data
public static final class ActionStepRequestForEachDescriptor implements IActionIfSupplier {
/** Optional if-expression, executing steps only if condition evaluates to true */
@JsonProperty("if") private SimpleExpression _if;
/** Optional break-expression, terminating forEach if condition evaluates to true */
private SimpleExpression breakIf;
/** Required name for this step element */
private String name;
/** Optional requests for which to embed the response in each forEach node */
private List<ActionStepRequestDescriptor> embed;
/** Steps to be repeated for each value */
@JsonProperty("do") private List<ActionStepDescriptor> _do;

/**
* This method is invoked by the {@link ActionStepDescriptor#postLoad()}
* method. It checks that required properties are set, then calls the postLoad() method for
* each sub-step.
*/
public final void postLoad(ActionDescriptor action) {
checkNotBlank("forEach name", name, this);
checkNotNull("forEach do", _do, this);
if ( embed!=null ) { embed.forEach(d->d.postLoad(action)); }
_do.forEach(d->d.postLoad(action));
}
}

@Reflectable
public static enum ActionStepRequestType {
simple, paged
}

@Reflectable @NoArgsConstructor
@Data
public static final class ActionStepRequestPagingProgressDescriptor {
private TemplateExpression prePageLoad;
private TemplateExpression postPageLoad;
private TemplateExpression postPageProcess;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionRequestTargetDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepForEachDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepForEachDescriptor.IActionStepForEachProcessor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestPagingProgressDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestType;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestDescriptor.ActionStepRequestForEachDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestDescriptor.ActionStepRequestPagingProgressDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepRequestDescriptor.ActionStepRequestType;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepSetDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepSetOperation;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepSetDescriptor.ActionStepSetOperation;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionStepWriteDescriptor;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionValidationException;
import com.fortify.cli.common.action.helper.ActionDescriptor.ActionValueTemplateDescriptor;
Expand Down Expand Up @@ -155,7 +157,7 @@ public final ActionRunner addRequestHelper(String name, IActionRequestHelper req
return this;
}

private IActionRequestHelper getRequestHelper(String name) {
public IActionRequestHelper getRequestHelper(String name) {
if ( StringUtils.isBlank(name) ) {
if ( requestHelpers.size()==1 ) {
return requestHelpers.values().iterator().next();
Expand Down Expand Up @@ -307,6 +309,7 @@ private final void processStep(ActionStepDescriptor step) {
processSupplier(step::getDebug, this::processDebugStep);
processSupplier(step::get_throw, this::processThrowStep);
processSupplier(step::getRequests, this::processRequestsStep);
processSupplier(step::getForEach, this::processForEachStep);
processAll(step::getSet, this::processSetStep);
processAll(step::getWrite, this::processWriteStep);
}
Expand Down Expand Up @@ -438,6 +441,19 @@ private void processThrowStep(TemplateExpression message) {
throw new RuntimeException(spelEvaluator.evaluate(message, localData, String.class));
}

private void processForEachStep(ActionStepForEachDescriptor forEach) {
spelEvaluator.evaluate(forEach.getProcessor(), localData, IActionStepForEachProcessor.class)
.process(node->processForEachStepNode(forEach, node));
}

private boolean processForEachStepNode(ActionStepForEachDescriptor forEach, JsonNode node) {
setDataValue(forEach.getName(), node);
if ( _if(forEach) ) {
processSteps(forEach.get_do());
}
return true;
}

private void processRequestsStep(List<ActionStepRequestDescriptor> requests) {
if ( requests!=null ) {
var requestsProcessor = new ActionStepRequestsProcessor();
Expand All @@ -452,7 +468,7 @@ private final void processResponse(ActionStepRequestDescriptor requestDescriptor
localData.set(name+"_raw", rawBody);
localData.set(name, body);
processOnResponse(requestDescriptor);
processForEach(requestDescriptor);
processRequestStepForEach(requestDescriptor);
}

private final void processFailure(ActionStepRequestDescriptor requestDescriptor, UnirestException e) {
Expand All @@ -467,34 +483,34 @@ private final void processOnResponse(ActionStepRequestDescriptor requestDescript
processSteps(onResponseSteps);
}

private final void processForEach(ActionStepRequestDescriptor requestDescriptor) {
private final void processRequestStepForEach(ActionStepRequestDescriptor requestDescriptor) {
var forEach = requestDescriptor.getForEach();
if ( forEach!=null ) {
var input = localData.get(requestDescriptor.getName());
if ( input!=null ) {
if ( input instanceof ArrayNode ) {
updateForEachTotalCount(forEach, (ArrayNode)input);
processForEachEmbed(forEach, (ArrayNode)input);
processForEach(forEach, (ArrayNode)input, this::processForEachEntryDo);
updateRequestStepForEachTotalCount(forEach, (ArrayNode)input);
processRequestStepForEachEmbed(forEach, (ArrayNode)input);
processRequestStepForEach(forEach, (ArrayNode)input, this::processRequestStepForEachEntryDo);
} else {
throw new ActionValidationException("forEach not supported on node type "+input.getNodeType());
}
}
}
}

private final void processForEachEmbed(ActionStepForEachDescriptor forEach, ArrayNode source) {
private final void processRequestStepForEachEmbed(ActionStepRequestForEachDescriptor forEach, ArrayNode source) {
var requestExecutor = new ActionStepRequestsProcessor();
processForEach(forEach, source, getForEachEntryEmbedProcessor(requestExecutor));
processRequestStepForEach(forEach, source, getRequestForEachEntryEmbedProcessor(requestExecutor));
requestExecutor.executeRequests();
}

@FunctionalInterface
private interface IForEachEntryProcessor {
void process(ActionStepForEachDescriptor forEach, JsonNode currentNode, ObjectNode newData);
private interface IRequestStepForEachEntryProcessor {
void process(ActionStepRequestForEachDescriptor forEach, JsonNode currentNode, ObjectNode newData);
}

private final void processForEach(ActionStepForEachDescriptor forEach, ArrayNode source, IForEachEntryProcessor entryProcessor) {
private final void processRequestStepForEach(ActionStepRequestForEachDescriptor forEach, ArrayNode source, IRequestStepForEachEntryProcessor entryProcessor) {
for ( int i = 0 ; i < source.size(); i++ ) {
var currentNode = source.get(i);
var newData = JsonHelper.shallowCopy(localData);
Expand All @@ -510,19 +526,19 @@ private final void processForEach(ActionStepForEachDescriptor forEach, ArrayNode
}
}

private void updateForEachTotalCount(ActionStepForEachDescriptor forEach, ArrayNode array) {
private void updateRequestStepForEachTotalCount(ActionStepRequestForEachDescriptor forEach, ArrayNode array) {
var totalCountName = String.format("total%sCount", StringUtils.capitalize(forEach.getName()));
var totalCount = localData.get(totalCountName);
if ( totalCount==null ) { totalCount = new IntNode(0); }
localData.put(totalCountName, totalCount.asInt()+array.size());
}

private void processForEachEntryDo(ActionStepForEachDescriptor forEach, JsonNode currentNode, ObjectNode newData) {
private void processRequestStepForEachEntryDo(ActionStepRequestForEachDescriptor forEach, JsonNode currentNode, ObjectNode newData) {
var processor = new ActionStepsProcessor(newData, this);
processor.processSteps(forEach.get_do());
}

private IForEachEntryProcessor getForEachEntryEmbedProcessor(ActionStepRequestsProcessor requestExecutor) {
private IRequestStepForEachEntryProcessor getRequestForEachEntryEmbedProcessor(ActionStepRequestsProcessor requestExecutor) {
return (forEach, currentNode, newData) -> {
if ( !currentNode.isObject() ) {
// TODO Improve exception message?
Expand Down Expand Up @@ -592,6 +608,7 @@ private void executeRequest(String target, List<ActionRequestDescriptor> request
}

public static interface IActionRequestHelper extends AutoCloseable {
public UnirestInstance getUnirestInstance();
public JsonNode transformInput(JsonNode input);
public void executePagedRequest(ActionRequestDescriptor requestDescriptor);
public void executeSimpleRequests(List<ActionRequestDescriptor> requestDescriptor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public static enum SSCFileTransferTokenType {
REPORT_FILE
}

private static final class SSCFileTransferTokenSupplier implements AutoCloseable, Supplier<String> {
public static final class SSCFileTransferTokenSupplier implements AutoCloseable, Supplier<String> {
private final UnirestInstance unirest;
private final String token;

Expand Down
Loading

0 comments on commit 9b1d84c

Please sign in to comment.