Portal Community

Contents

  1. How Options Work
  2. IDirectiveOption Interface
  3. Option Syntax
  4. Built-in Options Reference
  5. Cache Options
  6. Combining Multiple Options
  7. Creating a Custom Option

How Options Work

Options are declared in the expression after the path, prefixed with @. They are applied by the orchestrator after the directive resolves the raw value, in this order:

  1. PopulateConstraints — each option records its constraints (Required, NotEmpty, DefaultValue) into FieldConstraints
  2. Apply — each option transforms the resolved value
  3. FormatOutput — the final FormattedValue string is produced
ICacheMarkerOption is special Options implementing ICacheMarkerOption (@cache, @cache-thread, @cache-process) are handled by the orchestrator before directive invocation. A cache hit returns immediately, skipping the directive entirely.

IDirectiveOption Interface

public interface IDirectiveOption
{
    string OptionName { get; }                          // token after @
    bool IsSensitive { get; }                            // mask value in logs

    void PopulateConstraints(FieldConstraints constraints); // set Required/NotEmpty/Default
    object? Apply(object? resolvedValue);               // transform the value
    string? FormatOutput(object? finalValue);            // produce FormattedValue string
}

public interface ICacheMarkerOption : IDirectiveOption
{
    CacheScope Scope { get; }
    string BuildCacheKey(EvaluationRequest request);
}

Option Syntax

PatternExampleNotes
Simple flag@uppercaseNo parameter
With parameter@default:N/AParameter after colon
With format string@date:yyyy-MM-ddFormat string as parameter
With scope@cache-threadHyphen-separated qualifier

Multiple options in a single expression — space-separated, applied left to right:

Trim, then uppercase, then cache at thread scope
{@ $var.status @trim @uppercase @cache-thread }

Built-in Options Reference

Transform Options

OptionEffect on ValueExample
@uppercaseConverts string to upper case"hello" → "HELLO"
@lowercaseConverts string to lower case"HELLO" → "hello"
@trimRemoves leading/trailing whitespace" hi " → "hi"
@jsonJSON-serializes the value{ id: 1 } → '{"id":1}'
@base64Base64-encodes the string representation"abc" → "YWJj"
@date:formatFormats a DateTime value using the given format string@date:yyyy-MM-dd

Validation Options

OptionBehavior
@requiredSets FieldConstraints.IsRequired = true. If value is null, returns ValidationError.
@not-emptySets FieldConstraints.MustNotBeEmpty = true. If value is empty string, returns ValidationError.
@default:valueSets FieldConstraints.DefaultValue. Returns the default when resolved value is null or empty.

Output Shape Options

OptionBehavior
@typedSets OutputFieldName = "value" on FieldConstraints — tells the assignment layer to use the raw Value property (typed) rather than FormattedValue (string).

Cache Options

Cache options implement ICacheMarkerOption. They must appear in the expression — they cannot be programmatically added.

OptionScopeInvalidated whenUse for
@cacheNodeCurrent node execution endsValues constant within a node run (e.g., config lookups)
@cache-threadThreadWorkflow thread completesValues constant for entire workflow execution (e.g., exchange rates)
@cache-processProcessTop-level process completesValues constant across parallel branches (e.g., tenant settings)
Cache an expensive API call for the entire thread
{@ $api.ExchangeService/rates/USD.rate @cache-thread }
First call makes HTTP request. All subsequent calls in same thread return cached value.

Combining Multiple Options

Options are applied left to right. The output of each Apply becomes the input of the next.

Chain: default → trim → uppercase
{@ $var.countryCode @default:US @trim @uppercase }
null → "US" (default applied) → "US" (trim: no-op) → "US" (uppercase: already upper)
Order matters @json @base64 first JSON-serializes, then Base64-encodes the JSON string. Reversing to @base64 @json would Base64-encode the raw value, then JSON-serialize the Base64 string — a very different result.

Creating a Custom Option

Implement IDirectiveOption and register it in the DI container:

public class SanitizeHtmlDirectiveOption : IDirectiveOption
{
    public string OptionName => "sanitize-html";
    public bool IsSensitive => false;

    public void PopulateConstraints(FieldConstraints constraints)
    {
        // No constraints to set
    }

    public object? Apply(object? resolvedValue)
    {
        if (resolvedValue is not string str) return resolvedValue;
        return HtmlEncoder.Default.Encode(str);
    }

    public string? FormatOutput(object? finalValue)
        => finalValue?.ToString();
}

// Registration in DI
services.AddSingleton<IDirectiveOption, SanitizeHtmlDirectiveOption>();

The orchestrator discovers all registered IDirectiveOption implementations by name when parsing the @sanitize-html token from an expression.

Using the custom option
{@ $input.current.userComment @sanitize-html }
<script>alert('xss')</script> → &lt;script&gt;alert('xss')&lt;/script&gt;