use ic_platform::agent::{Agent, AgentBuilder, AgentError};
use std::sync::OnceLock;
use url::Url;
#[cfg(test)]
mod tests;
#[must_use]
pub fn agent_builder(endpoint: &Url, maximum_response_bytes: usize) -> AgentBuilder {
Agent::builder()
.with_url(endpoint.as_str())
.with_max_response_body_size(maximum_response_bytes)
}
#[derive(Debug, thiserror::Error)]
pub enum EndpointAgentError {
#[error("invalid IC agent endpoint")]
Endpoint(#[source] url::ParseError),
#[error("IC agent construction failed")]
Build(#[source] AgentError),
#[error("IC agent endpoint or response ceiling differs from its binding")]
BindingMismatch,
}
#[derive(Clone, Default)]
pub struct EndpointAgent {
transport: OnceLock<(String, usize, Agent)>,
}
impl EndpointAgent {
pub fn get(
&self,
endpoint: &str,
maximum_response_bytes: usize,
) -> Result<&Agent, EndpointAgentError> {
let transport = if let Some(transport) = self.transport.get() {
transport
} else {
let parsed = Url::parse(endpoint).map_err(EndpointAgentError::Endpoint)?;
let agent = agent_builder(&parsed, maximum_response_bytes)
.build()
.map_err(EndpointAgentError::Build)?;
self.transport
.get_or_init(|| (endpoint.to_owned(), maximum_response_bytes, agent))
};
let (selected_endpoint, selected_limit, agent) = transport;
if selected_endpoint != endpoint || *selected_limit != maximum_response_bytes {
return Err(EndpointAgentError::BindingMismatch);
}
Ok(agent)
}
}
impl std::fmt::Debug for EndpointAgent {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("EndpointAgent")
.field("initialized", &self.transport.get().is_some())
.finish_non_exhaustive()
}
}