aion-server 0.29.0

Aion workflow server library: HTTP, gRPC, WebSocket, and worker endpoints. Run it with the `aion` binary from the aion-cli crate.
Documentation
//! Required-field and envelope encode/decode helpers for the shared handlers.

use aion_core::{Payload, WorkflowId};
use aion_proto::{
    WireError,
    convert::{ProtoPayload, decode_core_value, encode_event},
};

/// A request field the contract requires, absent. A CLIENT-shaped omission
/// — the caller left the field out — is `invalid_input` (HTTP 400, gRPC
/// `INVALID_ARGUMENT`), never `backend`: a 500 tells an operator the server
/// broke, and trains them to ignore the one status that means it did.
pub(super) fn required_workflow_id(
    id: Option<aion_proto::ProtoWorkflowId>,
) -> Result<WorkflowId, WireError> {
    id.ok_or_else(|| WireError::invalid_input("workflow_id is required and was not supplied"))?
        .try_into()
}

/// The start input or signal payload the contract requires, absent.
///
/// Required rather than defaulted to JSON `null` (which `optional_payload`
/// does for query arguments): a start input is validated against the
/// document's declared schema and a signal payload against the signal's
/// declared type, so an absent one is ambiguous — "send nothing" and "I
/// forgot the field" look the same — and the honest answer is to say so at
/// the boundary, as a client error naming the field. Note that over HTTP a
/// JSON `null` deserialises into the DTO's `Option` as absent, so `"payload":
/// null` reaches here too; a caller who means "no payload" sends `{}` or the
/// signal's declared shape.
pub(super) fn required_payload(payload: Option<ProtoPayload>) -> Result<Payload, WireError> {
    payload
        .ok_or_else(|| {
            WireError::invalid_input(
                "payload is required and was not supplied (a JSON null is read as absent; send \
                 {} or the declared shape)",
            )
        })?
        .try_into()
}

/// Decode an optional wire payload, substituting the canonical "nothing
/// supplied" document when the field is absent.
///
/// Used by surfaces whose payload is genuinely optional — query arguments —
/// so an omitted field becomes one well-formed JSON document rather than an
/// empty byte string no decoder can read.
pub(super) fn optional_payload(payload: Option<ProtoPayload>) -> Result<Payload, WireError> {
    payload.map_or_else(|| Ok(Payload::json_null()), TryInto::try_into)
}

/// Decode the list contract request from its envelope.
///
/// The envelope is REQUIRED: the contract carries no default sort or limit,
/// so a call without one is malformed rather than "the first page of
/// everything".
pub(super) fn decode_list_request(
    request: Option<&aion_proto::WireEnvelope>,
) -> Result<aion_core::WorkflowListRequest, WireError> {
    request.map_or_else(
        || {
            Err(WireError::invalid_input(
                "list request is missing: a list names its filter, sort, cursor, and limit",
            ))
        },
        decode_core_value,
    )
}

pub(super) fn encode_history(
    include_history: bool,
    namespace: &str,
    history: &[aion_core::Event],
) -> Result<Vec<aion_proto::WireEnvelope>, WireError> {
    if include_history {
        history
            .iter()
            .map(|event| encode_event(namespace.to_owned(), None, event))
            .collect()
    } else {
        Ok(Vec::new())
    }
}