use std::sync::Arc;
use crate::adapters::secondary::transport::detect::{DetectConfig, DetectError, Detected, detect};
use crate::adapters::secondary::transport::http::HttpClientError;
use crate::application::client::{
Client, ClientConfig, ClientTransport, HandshakeError, INITIALIZED, initialize_params,
initialize_session, offer,
};
use crate::domain::NegotiatedSession;
use crate::domain::ir::JsonObject;
#[derive(Debug, thiserror::Error)]
pub enum ConnectError {
#[error("this client was offered no revision, so there is nothing to negotiate")]
NothingOffered,
#[error(transparent)]
Detect(#[from] DetectError),
#[error(transparent)]
Handshake(#[from] HandshakeError),
#[error("the server answered the probe with something that is not an object")]
UnreadableProbe,
#[error(transparent)]
Http(#[from] HttpClientError),
#[error("the session was negotiated but the client could not announce it: {0}")]
NotAnnounced(#[from] crate::application::client::TransportError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Spoke {
StreamableHttp,
LegacyHttpSse,
}
impl Spoke {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::StreamableHttp => "streamable-http",
Self::LegacyHttpSse => "legacy-http-sse",
}
}
}
pub async fn connect(url: &str, config: &ClientConfig) -> Result<(Client, Spoke), ConnectError> {
let (preferred, supported) = offer(config).ok_or(ConnectError::NothingOffered)?;
let probe = initialize_params(preferred, config)?;
let detected = detect(DetectConfig::new(url)?, probe.as_str().as_bytes()).await?;
match detected {
Detected::Streamable { client, result } => {
let answer = JsonObject::new(String::from_utf8_lossy(&result).into_owned())
.map_err(|_| ConnectError::UnreadableProbe)?;
let session = initialize_session(&answer, preferred, &supported, config)?;
let client = finish(client, session).await?;
Ok((client, Spoke::StreamableHttp))
}
Detected::Legacy(client) => {
let session = crate::application::client::handshake(&client, config).await?;
let client = finish(client, session).await?;
Ok((client, Spoke::LegacyHttpSse))
}
}
}
async fn finish<T>(transport: T, session: NegotiatedSession) -> Result<Client, ConnectError>
where
T: ClientTransport + 'static,
{
let params = JsonObject::new("{}").map_err(|error| HandshakeError::Assembling {
method: INITIALIZED,
source: error,
})?;
transport.notify(INITIALIZED, ¶ms).await?;
Ok(Client::from_session(&session, Arc::new(transport)))
}
#[cfg(all(test, feature = "http"))]
#[path = "connect/tests.rs"]
mod tests;