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 8, 2024
1 parent 7512176 commit faef5fb
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 162 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, merge;
}
}

/**
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 @@ -369,6 +372,15 @@ private JsonNode getTargetValueForSetStep(ActionStepSetDescriptor set, JsonNode
throw new IllegalStateException("Cannot append value to existing value of type "+result.getNodeType());
}
((ArrayNode)result).add(value);
} else if ( op==ActionStepSetOperation.merge ) {
result = localData.get(name);
if ( result==null ) {
result = objectMapper.createObjectNode();
}
if ( !result.isObject() || !value.isObject() ) {
throw new IllegalStateException(String.format("Only ObjectNodes can be merged (existing value type: %s, type of value to be merged: %s) ", result.getNodeType(), value.getNodeType()));
}
((ObjectNode)result).setAll((ObjectNode)value);
}
return result;
}
Expand Down Expand Up @@ -438,6 +450,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 +477,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 +492,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 +535,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 +617,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
Loading

0 comments on commit faef5fb

Please sign in to comment.