cucumber-messages

    Cucumber Messages protocol types for MoonBit with JSON serialization and NDJSON streaming

    cucumber
    messages
    bdd
    testing
    protocol
    Download zip
    Author
    Version
    0.6.0
    License
    Apache-2.0
    Last updated
    6 days ago
    Downloads
    17K

    #moonrockz/cucumber-messages

    Cucumber Messages protocol types for MoonBit. Defines the standardized message types used by Cucumber for representing test execution results.

    #Installation

    moon add moonrockz/cucumber-messages

    Version 0.6.0 and later need MoonBit 0.10.20 or newer (moonc v0.10.20+65efd5b93, moon 0.1.20261104). These versions use the #derive(...) attribute syntax and the moonbitlang/core/json package API, which older toolchains do not accept. On an older toolchain, use version 0.5.0.

    #What are Cucumber Messages?

    Cucumber Messages is a message protocol for representing the results of running Cucumber tests. Messages are exchanged as NDJSON (newline-delimited JSON) streams -- one JSON object per line, each wrapped in an Envelope.

    This decouples Gherkin parsing from test execution, enables real-time result processing, and reduces memory consumption compared to traditional JSON/XML formats.

    #Message Types

    The protocol defines message types wrapped in an Envelope discriminated union:

    CategoryMessage Types
    Source & ParsingSource, GherkinDocument, ParseError
    Test CompilationPickle
    Glue DefinitionsStepDefinition, Hook, ParameterType
    Test Run LifecycleTestRunStarted, TestRunFinished
    Test Case LifecycleTestCase, TestCaseStarted, TestCaseFinished
    Step LifecycleTestStepStarted, TestStepFinished
    Hook LifecycleTestRunHookStarted, TestRunHookFinished
    ArtifactsAttachment, ExternalAttachment
    MetadataMeta, Suggestion, UndefinedParameterType

    #Usage

    // Deserialize a message from JSON (this example imports the package as @cm)
    let json = @json.parse(
    "{\"meta\": {\"protocolVersion\": \"34.2.1\", \"implementation\": {\"name\": \"cucumber-moonbit\"}, \"runtime\": {\"name\": \"moonbit\"}, \"os\": {\"name\": \"linux\"}, \"cpu\": {\"name\": \"amd64\"}}}"
    ) catch { _ => panic() }
    let envelope : @cm.Envelope = @json.from_json(json) catch { _ => panic() }

    // Serialize a message to JSON
    let json_value = envelope.to_json()

    // NDJSON streaming: parse multiple messages from newline-delimited JSON
    let envelopes = @cm.parse_ndjson(ndjson_string) catch { _ => panic() }

    // Serialize envelopes to NDJSON
    let ndjson = @cm.envelopes_to_ndjson(envelopes)

    #Compatibility

    This library targets Cucumber Messages protocol v34.2.1 (see @cm.protocol_version). It is designed to interoperate with other Cucumber implementations (Java, JavaScript, Ruby, Python, Go, etc.) via the shared NDJSON format.

    Every sample of the Cucumber Compatibility Kit v31.0.0 is parsed and serialized back to identical JSON in the test suite.

    #Streaming

    For long runs, read and write one envelope at a time:

    // Read: feed chunks of any size; a partial last line waits for the next chunk.
    let reader = @cm.NdjsonReader::new()
    for chunk in chunks {
    for envelope in reader.feed(chunk) catch { e => { println(e); [] } } {
    handle(envelope)
    }
    }
    // Parse a last line that has no trailing newline.
    let last = reader.finish() catch { _ => None }
    if last is Some(envelope) {
    handle(envelope)
    }

    // Or read one line at a time; blank lines give None.
    let envelope = reader.read_line(line) catch { _ => None }

    // Write: one line per envelope to any &Logger (for example StringBuilder).
    envelope.write_ndjson(out)

    #With async I/O

    The library does no I/O, so it works with any I/O model. With moonbitlang/async (native targets only), read lines asynchronously and give each one to NdjsonReader::read_line:

    // moon.pkg imports: moonbitlang/async, moonbitlang/async/fs,
    // moonbitlang/async/io, moonbitlang/async/stdio,
    // "moonrockz/cucumber-messages" @cm

    ///|
    /// Read NDJSON envelopes from any async reader, one line at a time.
    async fn read_envelopes(
    input : &@io.Reader,
    handle : async (@cm.Envelope) -> Unit,
    ) -> Unit {
    let reader = @cm.NdjsonReader::new()
    while input.read_until("\n") is Some(line) {
    if reader.read_line(line) is Some(envelope) {
    handle(envelope)
    }
    }
    }

    ///|
    /// Write one envelope as an NDJSON line to any async writer.
    async fn write_envelope(output : &@io.Writer, envelope : @cm.Envelope) -> Unit {
    output.write(envelope.to_ndjson_line() + "\n")
    }

    ///|
    async fn main {
    let file = @fs.open("messages.ndjson", mode=ReadOnly)
    defer file.close()
    read_envelopes(file, async fn(envelope) {
    if envelope is TestStepFinished(_) {
    write_envelope(@stdio.stdout, envelope)
    }
    })
    }

    #Parsing rules

    • Unknown message types (for example a message added by a newer protocol version) decode as Envelope::UnknownMessage(key, value) and serialize back to the same JSON.
    • Unknown fields inside a known message are ignored on parse and are not written back. The protocol only adds optional fields, so a reader can skip them safely.
    • null on an optional field decodes as None. Serialization omits None fields.
    • Validation: an envelope must hold exactly one message. Integers must have no fractional part, and values outside the schema ranges (for example nanos outside 0..999999999) raise an error.
    • parse_ndjson raises NdjsonParseError with the 1-based line number of the bad line. envelopes_to_ndjson ends every line with \n.

    #License

    Apache-2.0

    NdjsonParseError

    pub suberror NdjsonParseError {
    NdjsonParseError(line~ : Int, cause~ : Error)
    }

    Error raised by parse_ndjson when one line of the input is not a valid envelope. line is the 1-based line number in the input.

    NdjsonParseError::output

    fn NdjsonParseError::output(self : NdjsonParseError, logger : &Logger) -> Unit

    NdjsonParseError::to_string

    fn NdjsonParseError::to_string(self : NdjsonParseError) -> String

    Attachment

    pub struct Attachment {
    body : String
    contentEncoding : AttachmentContentEncoding
    mediaType : String
    fileName : String?
    source : Source?
    testCaseStartedId : String?
    testStepId : String?
    url : String?
    testRunStartedId : String?
    testRunHookStartedId : String?
    timestamp : Timestamp?
    } derive(
    FromJson
    ,
    ToJson
    )

    Attachment: embedded test artifact.

    Attachment::to_json

    AttachmentContentEncoding

    pub enum AttachmentContentEncoding {
    Identity
    Base64
    }

    Background

    pub struct Background {
    location : Location
    keyword : String
    name : String
    description : String
    steps : Array[Step]
    id : String
    } derive(
    FromJson
    ,
    ToJson
    )

    Background: a background in a feature or rule.

    Background::to_json

    pub struct Ci {
    name : String
    url : String?
    buildNumber : String?
    git : Git?
    } derive(
    FromJson
    ,
    ToJson
    )

    Ci: CI environment (Meta sub-type).

    Ci::to_json

    Comment

    pub struct Comment {
    location : Location
    text : String
    } derive(
    FromJson
    ,
    ToJson
    )

    Comment: a comment in a Gherkin document.

    Comment::to_json

    DataTable

    DataTable: a data table in a step.

    DataTable::to_json

    DocString

    pub struct DocString {
    location : Location
    content : String
    delimiter : String
    mediaType : String?
    } derive(
    FromJson
    ,
    ToJson
    )

    DocString: a doc string in a step.

    DocString::to_json

    Duration

    pub struct Duration {
    seconds : Int64
    nanos : Int
    }

    Envelope

    pub enum Envelope {
    Meta(Meta)
    Source(Source)
    ParseError(ParseError)
    TestRunStarted(TestRunStarted)
    TestRunFinished(TestRunFinished)
    UndefinedParameterType(UndefinedParameterType)
    TestCaseStarted(TestCaseStarted)
    TestCaseFinished(TestCaseFinished)
    TestStepStarted(TestStepStarted)
    TestStepFinished(TestStepFinished)
    Attachment(Attachment)
    ExternalAttachment(ExternalAttachment)
    Hook(Hook)
    StepDefinition(StepDefinition)
    ParameterType(ParameterType)
    Suggestion(Suggestion)
    TestRunHookStarted(TestRunHookStarted)
    TestRunHookFinished(TestRunHookFinished)
    GherkinDocument(GherkinDocument)
    Pickle(Pickle)
    TestCase(TestCase)
    UnknownMessage(String,
    Json
    )
    }

    Envelope: exactly one message variant per JSON object.

    Envelope::to_json

    Envelope::to_ndjson_line

    fn Envelope::to_ndjson_line(self : Envelope) -> String

    Serialize an Envelope to a single NDJSON line (no trailing newline).

    Envelope::write_ndjson

    fn Envelope::write_ndjson(self : Envelope, logger : &Logger) -> Unit

    Write this envelope as one NDJSON line, ending with \n.

    Examples

    pub struct Examples {
    location : Location
    tags : Array[Tag]
    keyword : String
    name : String
    description : String
    tableHeader : TableRow?
    tableBody : Array[TableRow]
    id : String
    } derive(
    FromJson
    ,
    ToJson
    )

    Examples: examples table for a scenario outline.

    Examples::to_json

    Exception

    pub struct Exception {
    type_ : String
    message : String?
    stackTrace : String?
    }

    Exception: simplified exception (type, optional message and stackTrace).

    Exception::to_json

    ExternalAttachment

    pub struct ExternalAttachment {
    url : String
    mediaType : String
    testCaseStartedId : String?
    testStepId : String?
    testRunHookStartedId : String?
    timestamp : Timestamp?
    } derive(
    FromJson
    ,
    ToJson
    )

    ExternalAttachment: linked test artifact.

    Feature

    pub struct Feature {
    location : Location
    tags : Array[Tag]
    language : String
    keyword : String
    name : String
    description : String
    children : Array[FeatureChild]
    } derive(
    FromJson
    ,
    ToJson
    )

    Feature: a feature in a Gherkin document.

    Feature::to_json

    FeatureChild

    pub struct FeatureChild {
    rule : Rule?
    background : Background?
    scenario : Scenario?
    } derive(
    FromJson
    ,
    ToJson
    )

    FeatureChild: a child of a Feature (rule, background, or scenario).

    GherkinDocument

    pub struct GherkinDocument {
    uri : String?
    feature : Feature?
    comments : Array[Comment]
    } derive(
    FromJson
    ,
    ToJson
    )

    GherkinDocument: parsed Gherkin AST.

    Git

    pub struct Git {
    remote : String
    revision : String
    branch : String?
    tag : String?
    } derive(
    FromJson
    ,
    ToJson
    )

    Git: remote, revision, optional branch and tag (Meta sub-type).

    Git::to_json

    Group

    pub struct Group {
    children : Array[Group]?
    start : Int?
    value : String?
    } derive(
    ToJson
    )

    Hook

    pub struct Hook {
    id : String
    sourceReference : SourceReference
    name : String?
    tagExpression : String?
    type_ : HookType?
    }

    Hook: before/after hook definition. Note: JSON key is "type" (not "hookType") to match upstream schema.

    Hook::to_json

    HookType

    pub enum HookType {
    BeforeTestRun
    AfterTestRun
    BeforeTestCase
    AfterTestCase
    BeforeTestStep
    AfterTestStep
    }

    JavaMethod

    pub struct JavaMethod {
    className : String
    methodName : String
    methodParameterTypes : Array[String]
    } derive(
    FromJson
    ,
    ToJson
    )

    JavaMethod: for SourceReference (Java glue).

    JavaMethod::to_json

    JavaStackTraceElement

    pub struct JavaStackTraceElement {
    className : String
    fileName : String
    methodName : String
    } derive(
    FromJson
    ,
    ToJson
    )

    JavaStackTraceElement: for SourceReference.

    KeywordType

    pub enum KeywordType {
    Unknown
    Context
    Action
    Outcome
    Conjunction
    }

    Location

    pub struct Location {
    line : Int
    column : Int?
    } derive(
    ToJson
    )

    Location: line and optional column in a text file (Cucumber Messages).

    Location::to_json

    Meta

    pub struct Meta {
    protocolVersion : String
    implementation : Product
    runtime : Product
    os : Product
    cpu : Product
    ci : Ci?
    } derive(
    FromJson
    ,
    ToJson
    )

    Meta: protocol metadata (protocolVersion, implementation, runtime, os, cpu, optional ci).

    Meta::to_json

    NdjsonReader

    pub struct NdjsonReader {
    // private fields
    }

    Incremental NDJSON reader. Give it input one line at a time with read_line, or in chunks of any size with feed; it keeps track of line numbers so errors name the bad line.

    NdjsonReader::feed

    fn NdjsonReader::feed(self : NdjsonReader, chunk : String) -> Array[Envelope] raise NdjsonParseError

    Parse every complete line in chunk. A partial last line is kept until the next feed or finish.

    NdjsonReader::finish

    fn NdjsonReader::finish(self : NdjsonReader) -> Envelope? raise NdjsonParseError

    Parse the last line when the input does not end with \n.

    NdjsonReader::line_number

    fn NdjsonReader::line_number(self : NdjsonReader) -> Int

    Number of lines read so far.

    NdjsonReader::new

    Create a reader positioned before line 1.

    NdjsonReader::read_line

    fn NdjsonReader::read_line(self : NdjsonReader, line : StringView) -> Envelope? raise NdjsonParseError

    Parse the next line of input (without its \n). A blank line gives None. A bad line raises NdjsonParseError with its line number.

    ParameterType

    pub struct ParameterType {
    id : String
    name : String
    preferForRegularExpressionMatch : Bool
    regularExpressions : Array[String]
    useForSnippets : Bool
    sourceReference : SourceReference?
    } derive(
    ToJson
    )

    ParseError

    pub struct ParseError {
    source : SourceReference
    message : String
    } derive(
    FromJson
    ,
    ToJson
    )

    ParseError: parse error with source reference and message.

    ParseError::to_json

    Pickle

    pub(all) struct Pickle {
    id : String
    uri : String
    name : String
    language : String
    steps : Array[PickleStep]
    tags : Array[PickleTag]
    astNodeIds : Array[String]
    location : Location?
    } derive(
    ToJson
    )

    PickleDocString

    pub(all) struct PickleDocString {
    argumentIndex : Int?
    mediaType : String?
    content : String
    } derive(
    ToJson
    )

    PickleStep

    pub(all) struct PickleStep {
    id : String
    text : String
    astNodeIds : Array[String]
    type_ : PickleStepType?
    argument : PickleStepArgument?
    }

    PickleStep: a step in a pickle. Note: JSON key is "type" (not "pickleStepType") to match upstream schema.

    PickleStep::to_json

    PickleStepArgument

    pub(all) struct PickleStepArgument {
    docString : PickleDocString?
    dataTable : PickleTable?
    } derive(
    FromJson
    ,
    ToJson
    )

    PickleStepArgument: argument for a pickle step.

    PickleStepType

    pub(all) enum PickleStepType {
    Unknown
    Context
    Action
    Outcome
    }

    PickleTable

    pub(all) struct PickleTable {
    argumentIndex : Int?
    rows : Array[PickleTableRow]
    } derive(
    ToJson
    )

    PickleTableCell

    pub(all) struct PickleTableCell {
    value : String
    } derive(
    FromJson
    ,
    ToJson
    )

    PickleTableCell: a cell in a pickle data table.

    PickleTableRow

    pub(all) struct PickleTableRow {
    cells : Array[PickleTableCell]
    } derive(
    ToJson
    )

    PickleTag

    pub(all) struct PickleTag {
    name : String
    astNodeId : String
    } derive(
    FromJson
    ,
    ToJson
    )

    PickleTag: a tag in a pickle.

    PickleTag::to_json

    Product

    pub struct Product {
    name : String
    version : String?
    } derive(
    FromJson
    ,
    ToJson
    )

    Product: name and optional version (Meta sub-type).

    Product::to_json

    Rule

    pub struct Rule {
    location : Location
    tags : Array[Tag]
    keyword : String
    name : String
    description : String
    children : Array[RuleChild]
    id : String
    } derive(
    FromJson
    ,
    ToJson
    )

    Rule: a rule grouping scenarios in a feature.

    Rule::to_json

    RuleChild

    pub struct RuleChild {
    background : Background?
    scenario : Scenario?
    } derive(
    FromJson
    ,
    ToJson
    )

    RuleChild: a child of a Rule (background or scenario).

    RuleChild::to_json

    Scenario

    pub struct Scenario {
    location : Location
    tags : Array[Tag]
    keyword : String
    name : String
    description : String
    steps : Array[Step]
    examples : Array[Examples]
    id : String
    } derive(
    FromJson
    ,
    ToJson
    )

    Scenario: a scenario in a feature or rule.

    Scenario::to_json

    Snippet

    pub struct Snippet {
    language : String
    code : String
    } derive(
    FromJson
    ,
    ToJson
    )

    Snippet: code snippet for a suggestion.

    Snippet::to_json

    Source

    pub struct Source {
    uri : String
    data : String
    mediaType : SourceMediaType
    } derive(
    FromJson
    ,
    ToJson
    )

    Source: a source file (uri, data, mediaType).

    Source::to_json

    SourceMediaType

    pub enum SourceMediaType {
    GherkinPlain
    GherkinMarkdown
    }

    SourceReference

    pub struct SourceReference {
    uri : String?
    javaMethod : JavaMethod?
    javaStackTraceElement : JavaStackTraceElement?
    location : Location?
    } derive(
    FromJson
    ,
    ToJson
    )

    SourceReference: points to a Source and optional Location (or Java method/stack).

    Step

    pub struct Step {
    location : Location
    keyword : String
    text : String
    id : String
    keywordType : KeywordType?
    docString : DocString?
    dataTable : DataTable?
    } derive(
    FromJson
    ,
    ToJson
    )

    Step: a step in a scenario or background.

    Step::to_json

    StepDefinition

    pub struct StepDefinition {
    id : String
    pattern : StepDefinitionPattern
    sourceReference : SourceReference
    } derive(
    FromJson
    ,
    ToJson
    )

    StepDefinition: step definition with pattern and source reference.

    StepDefinitionPattern

    pub struct StepDefinitionPattern {
    source : String
    type_ : StepDefinitionPatternType
    }

    StepDefinitionPattern: pattern for a step definition. Note: JSON key is "type" (not "patternType") to match upstream schema.

    StepDefinitionPattern::to_json

    StepDefinitionPatternType

    pub enum StepDefinitionPatternType {
    CucumberExpression
    RegularExpression
    }

    StepMatchArgument

    pub struct StepMatchArgument {
    group : Group
    parameterTypeName : String?
    } derive(
    FromJson
    ,
    ToJson
    )

    StepMatchArgument: argument from step matching.

    StepMatchArgumentsList

    pub struct StepMatchArgumentsList {
    stepMatchArguments : Array[StepMatchArgument]
    } derive(
    FromJson
    ,
    ToJson
    )

    StepMatchArgumentsList: list of step match arguments.

    Suggestion

    pub struct Suggestion {
    id : String
    pickleStepId : String
    snippets : Array[Snippet]
    } derive(
    ToJson
    )

    TableCell

    pub struct TableCell {
    location : Location
    value : String
    } derive(
    FromJson
    ,
    ToJson
    )

    TableCell: a cell in a data table row.

    TableCell::to_json

    TableRow

    pub struct TableRow {
    location : Location
    cells : Array[TableCell]
    id : String
    } derive(
    FromJson
    ,
    ToJson
    )

    TableRow: a row in a data table.

    TableRow::to_json

    Tag

    pub struct Tag {
    location : Location
    name : String
    id : String
    } derive(
    FromJson
    ,
    ToJson
    )

    Tag: a tag in a Gherkin document.

    Tag::to_json

    TestCase

    pub struct TestCase {
    id : String
    pickleId : String
    testSteps : Array[TestStep]
    testRunStartedId : String?
    } derive(
    FromJson
    ,
    ToJson
    )

    TestCase: a test case (compiled from a pickle).

    TestCase::to_json

    TestCaseFinished

    pub struct TestCaseFinished {
    testCaseStartedId : String
    timestamp : Timestamp
    willBeRetried : Bool
    } derive(
    FromJson
    ,
    ToJson
    )

    TestCaseFinished: test case attempt finished.

    TestCaseStarted

    pub struct TestCaseStarted {
    attempt : Int
    id : String
    testCaseId : String
    workerId : String?
    timestamp : Timestamp
    } derive(
    ToJson
    )

    TestRunFinished

    pub struct TestRunFinished {
    message : String?
    success : Bool
    timestamp : Timestamp
    exception : Exception?
    testRunStartedId : String?
    } derive(
    FromJson
    ,
    ToJson
    )

    TestRunFinished: test run finished (success, timestamp, optional message, exception, testRunStartedId).

    TestRunHookFinished

    pub struct TestRunHookFinished {
    testRunHookStartedId : String
    result : TestStepResult
    timestamp : Timestamp
    } derive(
    FromJson
    ,
    ToJson
    )

    TestRunHookFinished: hook execution finished.

    TestRunHookStarted

    pub struct TestRunHookStarted {
    id : String
    testRunStartedId : String
    hookId : String
    timestamp : Timestamp
    workerId : String?
    } derive(
    FromJson
    ,
    ToJson
    )

    TestRunHookStarted: hook execution lifecycle.

    TestRunStarted

    pub struct TestRunStarted {
    timestamp : Timestamp
    id : String?
    } derive(
    FromJson
    ,
    ToJson
    )

    TestRunStarted: test run lifecycle.

    TestStep

    pub struct TestStep {
    id : String
    hookId : String?
    pickleStepId : String?
    stepDefinitionIds : Array[String]?
    stepMatchArgumentsLists : Array[StepMatchArgumentsList]?
    } derive(
    FromJson
    ,
    ToJson
    )

    TestStep: a step in a test case.

    TestStep::to_json

    TestStepFinished

    pub struct TestStepFinished {
    testCaseStartedId : String
    testStepId : String
    testStepResult : TestStepResult
    timestamp : Timestamp
    } derive(
    FromJson
    ,
    ToJson
    )

    TestStepFinished: step finished with result.

    TestStepResult

    pub struct TestStepResult {
    duration : Duration
    status : TestStepResultStatus
    message : String?
    exception : Exception?
    } derive(
    FromJson
    ,
    ToJson
    )

    TestStepResult: result of a step (duration, status, optional message and exception).

    TestStepResultStatus

    pub enum TestStepResultStatus {
    Unknown
    Passed
    Skipped
    Pending
    Undefined
    Ambiguous
    Failed
    }

    TestStepStarted

    pub struct TestStepStarted {
    testCaseStartedId : String
    testStepId : String
    timestamp : Timestamp
    } derive(
    FromJson
    ,
    ToJson
    )

    TestStepStarted: step lifecycle.

    Timestamp

    pub struct Timestamp {
    seconds : Int64
    nanos : Int
    }

    Timestamp: seconds since Unix epoch + nanos (0..999_999_999). seconds is an Int64 so that dates after 2038-01-19 fit.

    Timestamp::to_json

    UndefinedParameterType

    pub struct UndefinedParameterType {
    expression : String
    name : String
    } derive(
    FromJson
    ,
    ToJson
    )

    UndefinedParameterType: reports undefined parameter type.

    envelopes_to_ndjson

    fn envelopes_to_ndjson(envelopes : Array[Envelope]) -> String

    Serialize an array of Envelopes to NDJSON: one JSON object per line, each line ending with \n.

    parse_ndjson

    fn parse_ndjson(input : String) -> Array[Envelope] raise NdjsonParseError

    Parse an NDJSON string (multiple lines) into an array of Envelopes. Blank lines are skipped. A bad line raises NdjsonParseError.

    parse_ndjson_line

    fn parse_ndjson_line(line : String) -> Envelope raise

    Parse a single NDJSON line into an Envelope.

    protocol_version

    let protocol_version : String

    Cucumber Messages protocol version.