Skip to main content

PeerSession

Struct PeerSession 

Source
pub struct PeerSession { /* private fields */ }
Expand description

The negotiated connection a handler is serving.

Cheap to clone: a Copy revision, a Copy profile, a Copy session id and two Arcs. A handler may hand one to a spawned task without the ceremony a deep type would need.

§Examples

use std::sync::Arc;

use protocol_droid::application::server::context::{
    CallbackExchange, ExchangeError, PeerSession,
};
use protocol_droid::domain::feature::{Feature, FeatureProfile};
use protocol_droid::domain::ir::JsonObject;
use protocol_droid::domain::session::SessionId;
use protocol_droid::domain::{ProtocolVersion, TransportKind};

struct NoExchange;

let peer = PeerSession::new(
    ProtocolVersion::V2025_03_26,
    FeatureProfile::empty().with(Feature::Sampling),
    SessionId::new(1),
    TransportKind::Stdio,
    Arc::new(NoExchange),
    Arc::new(NullSink),
);

// Elicitation was never negotiated; sampling was. A handler that asked the
// question up front gets an answer rather than a frame the peer may reject.
assert!(!peer.allows(Feature::Elicitation));
assert!(peer.allows(Feature::Sampling));

Implementations§

Source§

impl PeerSession

Source

pub fn new( negotiated: ProtocolVersion, profile: FeatureProfile, session: SessionId, transport: TransportKind, exchange: Arc<dyn DynExchange>, sink: Arc<dyn DynFrameSink>, ) -> Self

Records what a connection negotiated, and where its frames go.

The caller is AuthContext::anonymous, which holds no scopes. That is a default-deny default and not a convenience: a primitive requiring a scope is invisible to an anonymous session, and one requiring none is visible to everybody. with_auth is how a real principal arrives, so a transport that never calls it serves exactly the primitives nobody had to authorise.

Source

pub fn with_log_gate(self, gate: LogGate) -> Self

This peer, filtering its log records through gate.

A stateful session keeps the open gate it was built with and moves it with logging/setLevel. A 2026-07-28 request replaces it with one built from its own _meta — see LogGate::for_request.

Source

pub const fn log_gate(&self) -> &LogGate

The threshold this peer’s log records are filtered against.

Source

pub const fn transport(&self) -> TransportKind

The transport this session runs over.

Part of the session rather than of the caller because a profile is not resolvable without one: resolve consults transport_allowed for every transport-gated feature row, so a handshake that needed the negotiated profile had nowhere to get the transport from until this carried it.

Source

pub const fn auth(&self) -> &AuthContext

The caller, for the visibility gate.

Source

pub const fn with_profile(self, profile: FeatureProfile) -> Self

Narrows what this session may do.

Separate from the constructor because a profile can be narrowed after the fact in a way it cannot be widened. The 2026-07-28 stateless lifecycle is the reason: every request carries its own version and capabilities in _meta and is served against its own profile, so the connection settles one profile and each request may hold less.

Narrowing only, deliberately. Widening would let a caller grant a session a feature the negotiation never produced, and that is the “rules-tightening in code” hazard in its most dangerous form: the refusal it defeats is a refusal the peer was never entitled to.

Source

pub fn with_auth(self, auth: AuthContext) -> Self

Records who the caller is.

Per request, and the distinction is the one that goes wrong silently. A bearer token can be refreshed, replaced, or withdrawn between two requests on one session, so the principal belongs to the RequestContext being served rather than to the session the peer was negotiated into. A transport therefore overlays the context it resolved onto the session peer per request, and what it persists carries the anonymous default that without_auth restores.

Source

pub fn without_auth(self) -> Self

This peer’s session state with the principal dropped.

What a transport stores when it persists a handshake result. The returned peer keeps the negotiated revision, the profile and the session id — everything that is the session — and answers auth with AuthContext::anonymous.

A handshake narrows the context it was handed, so the peer it returns inherits that context’s principal. A transport that overlays the request’s own context per request overwrites it on every request and never observes the difference, which is precisely why it is worth stating rather than leaving to chance: a principal recorded against session state has the wrong lifetime for a token that can be refreshed, replaced, or withdrawn mid-session, and the next reader of the session to skip that overlay would be answered as whoever opened it.

Source

pub fn with_exchange(self, exchange: Arc<dyn DynExchange>) -> Self

This peer, opening server-initiated requests through exchange.

The partner to Self::with_sink: the sink is where a notification goes, the exchange is how a request leaves and waits. A transport that publishes both a tool’s log records and its sampling/createMessage through one session has to bind both to that session, or the log records reach one client and the sampling request waits for another.

Replaces rather than layers: a peer has one exchange, and keeping a second that silently refuses would make a mis-bound peer refuse rather than fail loudly.

Source

pub const fn with_session(self, id: SessionId) -> Self

This peer, identified as id rather than as the seed it was built from.

A transport that opens more than one session needs this, and the reason is invisible until two sessions are live. Everything a session does is keyed on its id: the idempotency store that makes a retried tools/call replay its recorded answer, the in-flight map that refuses a duplicate. Both read peer.session(). A peer seeded with one id and never rebind therefore gives every session the same key space, so the first session’s recorded tools/call answer is handed back to the second session’s call as its “already completed” result — another tool’s output, served as if it were this one’s.

The store allocates the id; this is where it reaches the peer.

Source

pub fn with_sink(self, sink: Arc<dyn DynFrameSink>) -> Self

This peer, publishing its server-initiated messages through sink.

The peer is constructed with a sink long before a transport knows where that session’s messages will be read from — a handshake happens inside a request whose response stream does not exist yet, and a transport that seeds a session with one placeholder and persists it finds later that the placeholder is still what the session writes to. Binding it here, once the session has a tail worth publishing to, is the only point where the two can be made the same object.

Replaces rather than layers: a peer has exactly one sink, and keeping a second that quietly discards would restore the failure this exists to prevent.

Source

pub const fn negotiated(&self) -> ProtocolVersion

The revision this connection settled on.

Source

pub const fn profile(&self) -> FeatureProfile

The profile that revision and the peer’s capabilities produced.

Source

pub const fn session(&self) -> SessionId

Which session this is.

Source

pub fn allows(&self, feature: Feature) -> bool

Whether feature may be used on this connection.

Source

pub const fn exchange(&self) -> &Arc<dyn DynExchange> ⓘ

The port for a server-initiated round trip.

Source

pub const fn sink(&self) -> &Arc<dyn DynFrameSink> ⓘ

The port for a one-way notification.

Trait Implementations§

Source§

impl Clone for PeerSession

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PeerSession

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Prints the negotiation, and deliberately not the ports.

A sink holds a channel and an exchange holds a socket; neither is readable and both are large, so the derived form would print unreadable noise. The two fields a post-mortem wants are the revision and how much the session can do.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more