Skip to main content

Lifecycle

Trait Lifecycle 

Source
pub trait Lifecycle {
    type Profile: Copy + Debug + PartialEq;
    type Version: Copy + Display + PartialEq;

    // Required methods
    fn classify(
        &self,
        state: SessionState,
        profile: Option<Self::Profile>,
        method: &str,
    ) -> Classification;
    fn resolve(
        &self,
        declaration: Declaration<'_>,
    ) -> Result<Resolution<Self::Profile, Self::Version>, DeclarationError>;
}
Expand description

The vocabulary one protocol brings to the session engine.

Declared by the engine, which is its only consumer, and implemented by whichever protocol module is in play. See the module docs for why it is two methods and two associated types rather than a wider interface.

§Examples

A protocol with no handshake, which is what A2A’s per-request version header amounts to. Nothing in this module changes for it.

use protocol_droid::application::session::{
    Classification, Declaration, DeclarationError, Lifecycle, MetaView, Resolution,
};
use protocol_droid::domain::session::SessionState;

/// A2A speaks `Major.Minor`; MCP speaks dates. Nothing here assumes either.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct MajorMinor(u8, u8);

impl std::fmt::Display for MajorMinor {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{}.{}", self.0, self.1)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct A2AProfile {
    version: MajorMinor,
}

struct A2ALifecycle {
    supported: Vec<MajorMinor>,
}

impl Lifecycle for A2ALifecycle {
    type Profile = A2AProfile;
    type Version = MajorMinor;

    fn classify(
        &self,
        state: SessionState,
        _profile: Option<Self::Profile>,
        _method: &str,
    ) -> Classification {
        // No handshake, so nothing is ever a handshake — and the engine
        // does not need to be told what "initialize" means.
        let _ = state;
        Classification::Ordinary
    }

    fn resolve(&self, declaration: Declaration<'_>) -> Result<Resolution<Self::Profile, Self::Version>, DeclarationError> {
        // A2A negotiates out of its own metadata rather than a version
        // string, so both arms are read but neither is the version. That
        // difference is the point: the engine never supplies it.
        match declaration {
            Declaration::Stateless { .. } | Declaration::Handshake { .. } => {}
        }
        let version = self.supported.first().copied().ok_or(DeclarationError::NoVersion)?;
        Ok(Resolution {
            profile: A2AProfile { version },
            version,
            echoed: true,
        })
    }
}

let lifecycle = A2ALifecycle { supported: vec![MajorMinor(0, 3)] };
let resolution = lifecycle
    .resolve(Declaration::Stateless { meta: MetaView::default() })
    .expect("a supported version is answered");
assert_eq!(resolution.version, MajorMinor(0, 3));
assert_eq!(
    lifecycle.classify(SessionState::Stateless, Some(resolution.profile), "message/send"),
    Classification::Ordinary,
);

Required Associated Types§

Source

type Profile: Copy + Debug + PartialEq

What this protocol calls the set of things a session may do.

Opaque to the engine. FeatureProfile for MCP, which the engine also never inspects — it is handed back to classify.

Source

type Version: Copy + Display + PartialEq

How this protocol spells a version.

Required Methods§

Source

fn classify( &self, state: SessionState, profile: Option<Self::Profile>, method: &str, ) -> Classification

What a message naming method means for a session in state.

profile is None before anything has been agreed. A protocol that gates a method on a feature has to say so here rather than by inspecting a version, because the version is not this engine’s to interpret.

The engine refuses a message in a closing or closed session whatever this answers, so a protocol does not have to repeat that check.

Source

fn resolve( &self, declaration: Declaration<'_>, ) -> Result<Resolution<Self::Profile, Self::Version>, DeclarationError>

Reads what a peer declared and answers with what this side agrees to.

§Errors

DeclarationError when the declaration is not one this protocol can turn into a profile. Never for an unsupported but well-formed version: answering with something else is the specified behaviour, and an error there would stop a server working the day a revision is published.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§