aion-server 0.31.0

Aion workflow server library: HTTP, gRPC, WebSocket, and worker endpoints. Run it with the `aion` binary from the aion-cli crate.
Documentation
//! The studio projection carries the author's prose, and carries its absence.
//!
//! Both arms in one test on purpose: a projection that always emitted a
//! string would pass a "documented" assertion and be wrong about every
//! undocumented declaration, which is most of them.

use super::{StudioTypeKind, build};

/// A document carrying `///` on a type, a field, a variant, a worker, an
/// action and a parameter — every site the projection reports.
const DOCUMENTED: &str = "\
//! Grading.

workflow grade
  input essay: String
  outcome graded: type String, route success

/// What a marker decided.
type Verdict {
  /// The mark itself.
  grade: String,
}

/// How sure the marker was.
type Confidence =
  /// No doubt at all.
  | Certain
  | Unsure

/// The queue that marks essays.
worker marker
  /// Read one essay and settle on a grade.
  action mark(
    /// The text to read.
    essay: String,
  ) -> Verdict

step mark
  mark(essay: essay) -> verdict
  outcome graded: else, route graded(verdict.grade)
";

/// The same document with every `///` removed.
const UNDOCUMENTED: &str = "\
//! Grading.

workflow grade
  input essay: String
  outcome graded: type String, route success

type Verdict { grade: String }

type Confidence = Certain | Unsure

worker marker
  action mark(essay: String) -> Verdict

step mark
  mark(essay: essay) -> verdict
  outcome graded: else, route graded(verdict.grade)
";

#[test]
fn the_projection_carries_the_prose_and_carries_its_absence()
-> Result<(), Box<dyn std::error::Error>> {
    let documented = build(&aion_awl::parse(DOCUMENTED)?);
    let verdict = documented
        .types
        .iter()
        .find(|ty| ty.name == "Verdict")
        .ok_or("the projection names the declared record")?;
    assert_eq!(verdict.kind, StudioTypeKind::Record);
    assert_eq!(
        verdict.documentation.as_deref(),
        Some("What a marker decided.")
    );
    assert_eq!(
        verdict.fields[0].documentation.as_deref(),
        Some("The mark itself.")
    );

    let confidence = documented
        .types
        .iter()
        .find(|ty| ty.name == "Confidence")
        .ok_or("the projection names the declared enum")?;
    assert_eq!(
        confidence.variants[0].documentation.as_deref(),
        Some("No doubt at all.")
    );
    assert!(
        confidence.variants[1].documentation.is_none(),
        "an undocumented variant projects its absence"
    );

    let worker = documented
        .workers
        .first()
        .ok_or("the projection names the declared worker")?;
    assert_eq!(
        worker.documentation.as_deref(),
        Some("The queue that marks essays.")
    );
    let action = worker
        .actions
        .first()
        .ok_or("the projection names the declared action")?;
    assert_eq!(
        action.documentation.as_deref(),
        Some("Read one essay and settle on a grade.")
    );
    assert_eq!(
        action.params[0].documentation.as_deref(),
        Some("The text to read.")
    );

    // THE OTHER ARM. Without it, a projection that hardcoded a sentence
    // would be just as green above.
    let plain = build(&aion_awl::parse(UNDOCUMENTED)?);
    for ty in &plain.types {
        assert!(ty.documentation.is_none(), "`{}` carries no prose", ty.name);
        for field in &ty.fields {
            assert!(field.documentation.is_none());
        }
        for variant in &ty.variants {
            assert!(variant.documentation.is_none());
        }
    }
    for worker in &plain.workers {
        assert!(worker.documentation.is_none());
        for action in &worker.actions {
            assert!(action.documentation.is_none());
            for param in &action.params {
                assert!(param.documentation.is_none());
            }
        }
    }
    Ok(())
}

#[test]
fn the_projection_serialises_an_absent_sentence_as_null() -> Result<(), Box<dyn std::error::Error>>
{
    // The console reads this over the wire, so absence has to survive the
    // encoding as `null` rather than as an empty string a renderer would
    // print as a blank line.
    let plain = build(&aion_awl::parse(UNDOCUMENTED)?);
    let encoded = serde_json::to_value(&plain)?;
    let documentation = &encoded["types"][0]["documentation"];
    assert!(
        documentation.is_null(),
        "an undocumented type must encode as null, got {documentation}"
    );
    Ok(())
}