moonbitstack/moontel/propagate does not have a README file

    Baggage

    pub(all) struct Baggage {
    members : Array[Member]
    } derive(Eq,
    Debug
    )

    Keys and values carried alongside a trace, ordered, with duplicates allowed.

    Order is part of the data: W3C Baggage asks a hop to preserve the order of duplicate entries when it mutates the list, so this is an array rather than a map. get answers with the first member carrying a key, which is the one a reader asking "what did the caller say" wants.

    Baggage::add

    fn Baggage::add(self : Baggage, entry : Member) -> Baggage

    The same bag with member added at the end, past the member limit keeping the bag as it was rather than growing an unusable header.

    Baggage::equal

    fn Baggage::equal(Baggage, Baggage) -> Bool

    Baggage::get

    fn Baggage::get(self : Baggage, key : String) -> String?

    The value of the first member keyed key, or None.

    Baggage::is_empty

    fn Baggage::is_empty(self : Baggage) -> Bool

    Whether the bag has no members.

    Baggage::len

    fn Baggage::len(self : Baggage) -> Int

    How many members the bag carries.

    Baggage::new

    fn Baggage::new(members? : Array[Member]) -> Baggage

    A bag carrying members, in the order given.

    Baggage::not_equal

    fn Baggage::not_equal(x : Baggage, y : Baggage) -> Bool

    Baggage::parse

    fn Baggage::parse(input : StringView) -> Baggage raise
    Refused

    Parse a baggage header value (W3C Baggage §3.2).

    Whitespace around a member, around an =, and around the commas is dropped; a member that does not match the grammar refuses the whole header, because a half-read bag is a bag the next hop would rewrite. Values are percent-decoded and sequences that are not UTF-8 become U+FFFD, as §3.2.2.2 asks.

    The limits are the ones a platform must propagate — 64 members and 8192 octets — checked against the value as it arrived, and enforced by refusing rather than truncating: a caller that receives a bag too big to be legal has received something a middlebox either wrote or mangled, and guessing which members it meant is not this library's business.

    Baggage::to_header

    fn Baggage::to_header(self : Baggage) -> String

    The header value: members in order, key=value with properties after ;, and every character outside the baggage-octet set percent-encoded in uppercase hexadecimal (W3C Baggage §3.2.2.2). Empty when the bag is empty, which on the wire is the same as sending no header. encode_value says why a space is %20 rather than the + form encoding would use.

    Baggage::to_repr

    Extracted

    What a hop takes out of a set of request headers: the trace it joins, if the request carried one, and the baggage that came with it.

    One value rather than two calls, because a hop that reads one and forgets the other is a bug this shape makes hard to write: inject puts back exactly what extract found, changed only where the caller changed it.

    Extracted::equal

    fn Extracted::equal(Extracted, Extracted) -> Bool

    Extracted::not_equal

    fn Extracted::not_equal(x : Extracted, y : Extracted) -> Bool

    Member

    pub(all) struct Member {
    key : String
    value : String
    properties : Array[Property]
    } derive(Eq,
    Debug
    )

    One baggage entry: a key, a value, and any properties attached to it.

    The value is stored decoded, as text. The wire form is percent-encoded ASCII, and decoding is where a hop stops caring how the value was spelled.

    Member::equal

    fn Member::equal(Member, Member) -> Bool

    Member::not_equal

    fn Member::not_equal(x : Member, y : Member) -> Bool

    Member::to_repr

    Property

    pub(all) struct Property {
    key : String
    value : String?
    } derive(Eq,
    Debug
    )

    One metadata entry on a baggage member: key or key=value, after the ; of a list-member (W3C Baggage §3.2.2.3). The specification gives properties no meaning of their own; they are carried so a hop that does not understand one can pass it on unchanged.

    Property::equal

    fn Property::equal(Property, Property) -> Bool

    Property::not_equal

    fn Property::not_equal(x : Property, y : Property) -> Bool

    Property::to_repr

    baggage_byte_limit

    let baggage_byte_limit : Int

    The size W3C Baggage §3.2.1.3 requires a platform to propagate: 8192 octets of combined header.

    baggage_header

    let baggage_header : String

    The header baggage travels in (W3C Baggage §3.2.1).

    baggage_member_limit

    let baggage_member_limit : Int

    The list-members W3C Baggage §3.2.1.3 requires a platform to propagate: a header of 64 members or fewer must survive the hop whole.

    empty_baggage

    let empty_baggage : Baggage

    Nothing carried, which a request with no baggage header arrives with.

    extract

    fn extract(headers : Array[(String, String)]) -> Extracted

    Read the propagation headers out of headers, which is the shape both an ASGI scope and an outgoing request carry.

    Header names are matched without regard to case; a value is taken as it is, with the whitespace around it dropped.

    Two traceparent headers refuse each other: the specification says a request with more than one is invalid, and picking one of the two would be guessing which proxy wrote the one that matters. Several tracestate or baggage headers are combined in order, as RFC 7230 allows a field to be split across lines.

    A header that does not parse leaves that field empty and the rest intact: a mangled tracestate must not cost a hop the trace, and a mangled baggage must not cost it the trace either.

    inject

    fn inject(headers : Array[(String, String)], extracted : Extracted) -> Array[(String, String)]

    Put extracted back into headers: the propagation headers already there are removed and rewritten from the context and the bag, and everything else is left exactly as it was.

    Rewriting rather than appending is what makes this safe to call on a request that already carried a context — the one from the sender must not survive next to the one this hop decided on.

    parse

    fn parse(traceparent : StringView, tracestate? : StringView) ->
    TraceContext
    raise
    Refused

    A parsed traceparent, plus the vendor state that came with it.

    version is kept even though nothing here acts on it: a caller debugging a failed handshake wants to know which version of the header the other side spoke, and a version above zero may one day carry fields this library does not read.

    The ids are validated on the way in — sixteen and eight octets, neither all zero — because a context that fails that is not a trace to join, and passing it on would make a broken hop look like a working one.

    parse_traceparent

    Parse a traceparent header value, without any vendor state.

    The grammar is W3C Trace Context §3.2:

    version(2)-trace-id(32)-parent-id(16)-trace-flags(2)

    A version this library does not know may append fields, but only after a -: 00 headers end at 55 characters, and 00-…-01.whatever is refused rather than read as a version-0 header with a suffix. Version ff is refused by the specification, as is an all-zero id.

    Whitespace around the value is dropped. HTTP allows OWS around a field value, and W3C Trace Context's examples send it; whitespace anywhere inside is refused as the illegal character it is.

    parse_tracestate

    Parse a tracestate header value. The grammar lives with the type that holds the state; this is the name a caller reaching for the header uses.

    traceparent_header

    let traceparent_header : String

    The header traceparent travels in (W3C Trace Context §3.2.1). Lowercase on the wire; lookups here compare without regard to case.

    tracestate_header

    let tracestate_header : String

    The header tracestate travels in (W3C Trace Context §3.3.1).