Current section
Files
Jump to
Current section
Files
src/aion/awl/runtime.gleam
//// The generic run shell and literal-list indexing hoisted out of every
//// generated AWL module (AWL-BC-0, hoist-only).
////
//// `run` is the engine entry point's body: it reproduces the three-stage
//// decode/execute/encode case tree with the exact failure strings the emitter
//// used to inline. Each generated module keeps a three-line `run/1` wrapper
//// (the engine invokes it by name) that forwards to this function with the
//// module's own codecs and `execute`.
import aion/awl/error.{type AwlError, AwlDecodeInputFailed, AwlIndexOutOfRange}
import aion/codec.{type Codec}
import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/list
/// Decode the raw engine input to a string, decode it with `input_codec`, run
/// `execute`, and encode the outcome with `output_codec`. Failure strings are
/// byte-identical to the code the emitter used to inline.
///
/// A failure is returned as the `AwlError` codec's OWN encoding (the
/// `{"tag":…}` JSON object), exactly like `workflow.entrypoint` encodes a
/// definition's typed error. The engine records that text as the run's
/// failure details, and an awaiting parent decodes it back with the same
/// `aion/awl/error` codec it already holds — so typed kinds such as
/// `AwlVisitsExceeded` survive the parent-child boundary instead of leaking
/// the raw error record (whose term-to-JSON image is a *list*, which the
/// codec's object decoder can never accept).
pub fn run(
raw_input: Dynamic,
input_codec: Codec(input),
output_codec: Codec(output),
execute: fn(input) -> Result(output, AwlError),
) -> Result(String, String) {
case decode.run(raw_input, decode.string) {
Ok(raw_json) ->
case input_codec.decode(raw_json) {
Ok(input) ->
case execute(input) {
Ok(result) -> Ok(output_codec.encode(result))
Error(workflow_error) -> Error(encode_error(workflow_error))
}
Error(codec.DecodeError(reason: reason, path: _)) ->
Error(
encode_error(AwlDecodeInputFailed(
"failed to decode workflow input: " <> reason,
)),
)
}
Error(_) ->
Error(
encode_error(AwlDecodeInputFailed(
"workflow input payload was not a string",
)),
)
}
}
/// Encode a typed AWL failure with the fixed `AwlError` codec, the one wire
/// shape `aion/awl/error`'s decoder round-trips.
fn encode_error(workflow_error: AwlError) -> String {
let error_codec = error.codec()
error_codec.encode(workflow_error)
}
/// Literal-only list indexing; out of range is a step failure carrying the
/// source-anchored label the emitter built.
pub fn index(items: List(a), index: Int, label: String) -> Result(a, AwlError) {
case list.drop(items, index) |> list.first {
Ok(value) -> Ok(value)
Error(_) -> Error(AwlIndexOutOfRange(label))
}
}