use std::{fmt, rc::Rc};
use futures::future::LocalBoxFuture;
use lenso_kernel::{InvocationContext, ModuleDependencies, NativeRequestEndpoint, NativeRequestFuture, NativeRequestHandle, RequestCapability, RuntimeFailure};
pub const CAPABILITY_ID: &str = "lenso.http.client@1";
pub const DESCRIPTOR_VERSION: &str = "1.0.1";
pub const PORTABLE: bool = true;
pub const CROSS_LANE_TRANSFER: bool = true;
pub const CLIENT_CAPABILITY_ID: &str = CAPABILITY_ID;
pub const CLIENT_DESCRIPTOR_VERSION: &str = DESCRIPTOR_VERSION;
pub const SEND_OPERATION: &str = "send";
pub use lenso_contract_runtime::{Bytes, UnknownDomainError};
use lenso_contract_runtime::{decode_portable_json, encode_portable_json};
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SendRequest {
#[serde(rename = "body")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub body: Bytes,
#[serde(rename = "headers")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub headers: Vec<SendRequestHeadersItem>,
#[serde(rename = "method")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub method: String,
#[serde(rename = "url")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub url: String,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SendRequestHeadersItem {
#[serde(rename = "name")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub name: String,
#[serde(rename = "value")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub value: String,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SendResponse {
#[serde(rename = "body")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub body: Bytes,
#[serde(rename = "headers")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub headers: Vec<SendResponseHeadersItem>,
#[serde(rename = "status")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub status: i64,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SendResponseHeadersItem {
#[serde(rename = "name")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub name: String,
#[serde(rename = "value")]
#[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
pub value: String,
}
#[derive(Clone, Debug, PartialEq)]
pub enum SendError {
DestinationNotAllowed,
InvalidRequest,
RequestTooLarge,
ResponseTooLarge,
Timeout,
TransportFailure,
Unknown(UnknownDomainError),
}
#[derive(Debug)]
pub struct Client;
impl RequestCapability for Client {
type Request = SendRequest;
type Response = SendResponse;
type DomainError = SendError;
const ID: &'static str = CAPABILITY_ID;
const DESCRIPTOR_VERSION: &'static str = DESCRIPTOR_VERSION;
fn invoke_native(endpoint: &dyn NativeRequestEndpoint, operation: &str, request: Self::Request, context: InvocationContext) -> NativeRequestFuture<Self> {
if operation != SEND_OPERATION {
return lenso_kernel::invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context);
}
let Some(typed_endpoint) = endpoint
.typed_endpoint()
.and_then(|endpoint| endpoint.downcast_ref::<ClientRequestEndpoint>())
else {
return lenso_kernel::invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context);
};
Rc::clone(&typed_endpoint.provider).send(context, request)
}
}
impl serde::Serialize for SendError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
match self {
Self::DestinationNotAllowed => serializer.serialize_str("destination_not_allowed"),
Self::InvalidRequest => serializer.serialize_str("invalid_request"),
Self::RequestTooLarge => serializer.serialize_str("request_too_large"),
Self::ResponseTooLarge => serializer.serialize_str("response_too_large"),
Self::Timeout => serializer.serialize_str("timeout"),
Self::TransportFailure => serializer.serialize_str("transport_failure"),
Self::Unknown(value) => {
let mut map = serializer.serialize_map(Some(1 + usize::from(value.payload.is_some()) + value.extra.len()))?;
map.serialize_entry("code", &value.code)?;
if let Some(payload) = &value.payload {
map.serialize_entry("payload", payload)?;
}
for (key, extra) in &value.extra {
map.serialize_entry(key, extra)?;
}
map.end()
},
}
}
}
impl<'de> serde::Deserialize<'de> for SendError {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <serde_json::Value as serde::Deserialize>::deserialize(deserializer)?;
match value {
serde_json::Value::String(code) => match code.as_str() {
"destination_not_allowed" => Ok(Self::DestinationNotAllowed),
"invalid_request" => Ok(Self::InvalidRequest),
"request_too_large" => Ok(Self::RequestTooLarge),
"response_too_large" => Ok(Self::ResponseTooLarge),
"timeout" => Ok(Self::Timeout),
"transport_failure" => Ok(Self::TransportFailure),
_ => Ok(Self::Unknown(UnknownDomainError { code, payload: None, extra: std::collections::BTreeMap::new() })),
},
serde_json::Value::Object(mut object) => {
let Some(code) = object.remove("code").and_then(|value| value.as_str().map(ToOwned::to_owned)) else {
return Err(serde::de::Error::custom("Domain Error object is missing a string code"));
};
let payload = object.remove("payload");
let extra = object.into_iter().collect::<std::collections::BTreeMap<_, _>>();
Ok(Self::Unknown(UnknownDomainError { code, payload, extra }))
}
other => Err(serde::de::Error::custom(format!("Domain Error must be a string or object, got {other}"))),
}
}
}
pub fn encode_send_request(value: &SendRequest) -> Result<String, serde_json::Error> { encode_portable_json(value) }
pub fn decode_send_request(wire: &str) -> Result<SendRequest, serde_json::Error> { decode_portable_json(wire) }
pub fn encode_send_response(value: &SendResponse) -> Result<String, serde_json::Error> { encode_portable_json(value) }
pub fn decode_send_response(wire: &str) -> Result<SendResponse, serde_json::Error> { decode_portable_json(wire) }
pub fn encode_send_error(value: &SendError) -> Result<String, serde_json::Error> { encode_portable_json(value) }
pub fn decode_send_error(wire: &str) -> Result<SendError, serde_json::Error> { decode_portable_json(wire) }
pub trait ClientProvider: fmt::Debug + 'static {
fn send(&self, context: InvocationContext, request: SendRequest) -> NativeRequestFuture<Client>;
}
#[derive(Debug)]
struct ClientRequestEndpoint { provider: Rc<dyn ClientProvider> }
#[derive(Debug)]
pub struct ClientEndpoint<P: ClientProvider> { provider: Rc<P>, request_endpoint: ClientRequestEndpoint }
impl<P: ClientProvider> ClientEndpoint<P> {
pub fn new(provider: P) -> Self {
let provider = Rc::new(provider);
let request_provider: Rc<dyn ClientProvider> = provider.clone();
Self { provider, request_endpoint: ClientRequestEndpoint { provider: request_provider } }
}
}
impl<P: ClientProvider> NativeRequestEndpoint for ClientEndpoint<P> {
fn capability_id(&self) -> &'static str { CAPABILITY_ID }
fn descriptor_version(&self) -> &'static str { DESCRIPTOR_VERSION }
fn operations(&self) -> &'static [&'static str] { &[
SEND_OPERATION,
] }
fn typed_endpoint(&self) -> Option<&dyn std::any::Any> { Some(&self.request_endpoint) }
fn invoke(&self, operation: &str, request: Box<dyn std::any::Any>, context: InvocationContext) -> LocalBoxFuture<'static, Result<Result<Box<dyn std::any::Any>, Box<dyn std::any::Any>>, RuntimeFailure>> {
match operation {
SEND_OPERATION => {
let Ok(request) = request.downcast::<SendRequest>() else {
return Box::pin(futures::future::ready(Err(RuntimeFailure::ProtocolViolation { capability: CAPABILITY_ID })));
};
let invocation = Rc::clone(&self.provider).send(context, *request);
Box::pin(async move {
invocation.await.map(|result| {
result
.map(|value| Box::new(value) as Box<dyn std::any::Any>)
.map_err(|error| Box::new(error) as Box<dyn std::any::Any>)
})
})
}
_ => Box::pin(futures::future::ready(Err(RuntimeFailure::UnknownOperation { capability: CAPABILITY_ID, operation: operation.to_owned() }))),
}
}
}
#[derive(Debug)]
pub struct ClientClient {
send: NativeRequestHandle<Client>,
}
impl ClientClient {
pub fn new(handle: NativeRequestHandle<Client>) -> Self {
Self { send: handle }
}
pub fn from_dependencies(dependencies: &ModuleDependencies) -> Result<Self, RuntimeFailure> {
Ok(Self {
send: dependencies.one::<Client>()?,
})
}
pub async fn send(&self, request: SendRequest) -> Result<SendResponse, ClientInvocationError> {
self.send.invoke(SEND_OPERATION, request).await
.map_err(ClientInvocationError::Runtime)?
.map_err(ClientInvocationError::Domain)
}
pub async fn send_with_context(&self, context: InvocationContext, request: SendRequest) -> Result<SendResponse, ClientInvocationError> {
self.send.invoke_with_context(SEND_OPERATION, context, request).await
.map_err(ClientInvocationError::Runtime)?
.map_err(ClientInvocationError::Domain)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ClientInvocationError {
Domain(SendError),
Runtime(RuntimeFailure),
}