1use std::{fmt, rc::Rc};
3use futures::future::LocalBoxFuture;
4use lenso_kernel::{InvocationContext, ModuleDependencies, NativeRequestEndpoint, NativeRequestFuture, NativeRequestHandle, RequestCapability, RuntimeFailure};
5
6pub const CAPABILITY_ID: &str = "lenso.http.endpoint@1";
7pub const DESCRIPTOR_VERSION: &str = "1.1.0";
8pub const PORTABLE: bool = true;
9pub const CROSS_LANE_TRANSFER: bool = true;
10pub const ENDPOINT_CAPABILITY_ID: &str = CAPABILITY_ID;
11pub const ENDPOINT_DESCRIPTOR_VERSION: &str = DESCRIPTOR_VERSION;
12
13pub const DESCRIBE_OPERATION: &str = "describe";
14pub const HANDLE_OPERATION: &str = "handle";
15
16pub use lenso_contract_runtime::{Bytes, UnknownDomainError};
17use lenso_contract_runtime::{decode_portable_json, encode_portable_json};
18
19#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
20pub struct DescribeRequest {
21
22}
23
24#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
25pub struct DescribeResponse {
26 #[serde(rename = "routes")]
27 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
28 pub routes: Vec<DescribeResponseRoutesItem>,
29}
30
31#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
32pub struct DescribeResponseRoutesItem {
33 #[serde(rename = "method")]
34 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
35 pub method: String,
36 #[serde(rename = "openapi")]
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub openapi: Option<std::collections::BTreeMap<String, serde_json::Value>>,
39 #[serde(rename = "path")]
40 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
41 pub path: String,
42 #[serde(rename = "route_id")]
43 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
44 pub route_id: String,
45}
46
47#[derive(Clone, Debug, PartialEq)]
48pub enum DescribeError {
49 InvalidConfiguration,
50 Unknown(UnknownDomainError),
51}
52
53#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
54pub struct HandleRequest {
55 #[serde(rename = "body")]
56 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
57 pub body: Bytes,
58 #[serde(rename = "credential")]
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub credential: Option<HandleRequestCredential>,
61 #[serde(rename = "headers")]
62 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
63 pub headers: Vec<HandleRequestHeadersItem>,
64 #[serde(rename = "method")]
65 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
66 pub method: String,
67 #[serde(rename = "path")]
68 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
69 pub path: String,
70 #[serde(rename = "path_parameters")]
71 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
72 pub path_parameters: Vec<HandleRequestPathParametersItem>,
73 #[serde(rename = "query")]
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub query: Option<String>,
76 #[serde(rename = "request_id")]
77 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
78 pub request_id: String,
79 #[serde(rename = "route_id")]
80 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
81 pub route_id: String,
82}
83
84#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
85pub struct HandleRequestCredential {
86 #[serde(rename = "scheme")]
87 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
88 pub scheme: String,
89 #[serde(rename = "value")]
90 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
91 pub value: String,
92}
93
94#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
95pub struct HandleRequestHeadersItem {
96 #[serde(rename = "name")]
97 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
98 pub name: String,
99 #[serde(rename = "value")]
100 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
101 pub value: String,
102}
103
104#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
105pub struct HandleRequestPathParametersItem {
106 #[serde(rename = "name")]
107 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
108 pub name: String,
109 #[serde(rename = "value")]
110 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
111 pub value: String,
112}
113
114#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
115pub struct HandleResponse {
116 #[serde(rename = "body")]
117 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
118 pub body: Bytes,
119 #[serde(rename = "headers")]
120 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
121 pub headers: Vec<HandleResponseHeadersItem>,
122 #[serde(rename = "status")]
123 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
124 pub status: i64,
125}
126
127#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
128pub struct HandleResponseHeadersItem {
129 #[serde(rename = "name")]
130 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
131 pub name: String,
132 #[serde(rename = "value")]
133 #[serde(deserialize_with = "lenso_contract_runtime::serde::deserialize_required")]
134 pub value: String,
135}
136
137#[derive(Clone, Debug, PartialEq)]
138pub enum HandleError {
139 Rejected,
140 Unknown(UnknownDomainError),
141}
142
143#[derive(Debug)]
144pub struct EndpointDescribe;
145impl RequestCapability for EndpointDescribe {
146 type Request = DescribeRequest;
147 type Response = DescribeResponse;
148 type DomainError = DescribeError;
149 const ID: &'static str = CAPABILITY_ID;
150 const DESCRIPTOR_VERSION: &'static str = DESCRIPTOR_VERSION;
151
152 fn invoke_native(endpoint: &dyn NativeRequestEndpoint, operation: &str, request: Self::Request, context: InvocationContext) -> NativeRequestFuture<Self> {
153 if operation != DESCRIBE_OPERATION {
154 return lenso_kernel::invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context);
155 }
156 let Some(typed_endpoint) = endpoint
157 .typed_endpoint()
158 .and_then(|endpoint| endpoint.downcast_ref::<EndpointRequestEndpoint>())
159 else {
160 return lenso_kernel::invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context);
161 };
162 Rc::clone(&typed_endpoint.provider).describe(context, request)
163 }
164}
165
166#[derive(Debug)]
167pub struct EndpointHandle;
168impl RequestCapability for EndpointHandle {
169 type Request = HandleRequest;
170 type Response = HandleResponse;
171 type DomainError = HandleError;
172 const ID: &'static str = CAPABILITY_ID;
173 const DESCRIPTOR_VERSION: &'static str = DESCRIPTOR_VERSION;
174
175 fn invoke_native(endpoint: &dyn NativeRequestEndpoint, operation: &str, request: Self::Request, context: InvocationContext) -> NativeRequestFuture<Self> {
176 if operation != HANDLE_OPERATION {
177 return lenso_kernel::invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context);
178 }
179 let Some(typed_endpoint) = endpoint
180 .typed_endpoint()
181 .and_then(|endpoint| endpoint.downcast_ref::<EndpointRequestEndpoint>())
182 else {
183 return lenso_kernel::invoke_typed_or_erased_native_request::<Self>(endpoint, operation, request, context);
184 };
185 Rc::clone(&typed_endpoint.provider).handle(context, request)
186 }
187}
188
189impl serde::Serialize for DescribeError {
190 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
191 where
192 S: serde::Serializer,
193 {
194 use serde::ser::SerializeMap;
195 match self {
196 Self::InvalidConfiguration => serializer.serialize_str("invalid_configuration"),
197 Self::Unknown(value) => {
198 let mut map = serializer.serialize_map(Some(1 + usize::from(value.payload.is_some()) + value.extra.len()))?;
199 map.serialize_entry("code", &value.code)?;
200 if let Some(payload) = &value.payload {
201 map.serialize_entry("payload", payload)?;
202 }
203 for (key, extra) in &value.extra {
204 map.serialize_entry(key, extra)?;
205 }
206 map.end()
207 },
208 }
209 }
210}
211
212impl<'de> serde::Deserialize<'de> for DescribeError {
213 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
214 where
215 D: serde::Deserializer<'de>,
216 {
217 let value = <serde_json::Value as serde::Deserialize>::deserialize(deserializer)?;
218 match value {
219 serde_json::Value::String(code) => match code.as_str() {
220 "invalid_configuration" => Ok(Self::InvalidConfiguration),
221 _ => Ok(Self::Unknown(UnknownDomainError { code, payload: None, extra: std::collections::BTreeMap::new() })),
222 },
223 serde_json::Value::Object(mut object) => {
224 let Some(code) = object.remove("code").and_then(|value| value.as_str().map(ToOwned::to_owned)) else {
225 return Err(serde::de::Error::custom("Domain Error object is missing a string code"));
226 };
227 let payload = object.remove("payload");
228 let extra = object.into_iter().collect::<std::collections::BTreeMap<_, _>>();
229 Ok(Self::Unknown(UnknownDomainError { code, payload, extra }))
230 }
231 other => Err(serde::de::Error::custom(format!("Domain Error must be a string or object, got {other}"))),
232 }
233 }
234}
235
236impl serde::Serialize for HandleError {
237 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238 where
239 S: serde::Serializer,
240 {
241 use serde::ser::SerializeMap;
242 match self {
243 Self::Rejected => serializer.serialize_str("rejected"),
244 Self::Unknown(value) => {
245 let mut map = serializer.serialize_map(Some(1 + usize::from(value.payload.is_some()) + value.extra.len()))?;
246 map.serialize_entry("code", &value.code)?;
247 if let Some(payload) = &value.payload {
248 map.serialize_entry("payload", payload)?;
249 }
250 for (key, extra) in &value.extra {
251 map.serialize_entry(key, extra)?;
252 }
253 map.end()
254 },
255 }
256 }
257}
258
259impl<'de> serde::Deserialize<'de> for HandleError {
260 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
261 where
262 D: serde::Deserializer<'de>,
263 {
264 let value = <serde_json::Value as serde::Deserialize>::deserialize(deserializer)?;
265 match value {
266 serde_json::Value::String(code) => match code.as_str() {
267 "rejected" => Ok(Self::Rejected),
268 _ => Ok(Self::Unknown(UnknownDomainError { code, payload: None, extra: std::collections::BTreeMap::new() })),
269 },
270 serde_json::Value::Object(mut object) => {
271 let Some(code) = object.remove("code").and_then(|value| value.as_str().map(ToOwned::to_owned)) else {
272 return Err(serde::de::Error::custom("Domain Error object is missing a string code"));
273 };
274 let payload = object.remove("payload");
275 let extra = object.into_iter().collect::<std::collections::BTreeMap<_, _>>();
276 Ok(Self::Unknown(UnknownDomainError { code, payload, extra }))
277 }
278 other => Err(serde::de::Error::custom(format!("Domain Error must be a string or object, got {other}"))),
279 }
280 }
281}
282
283pub fn encode_describe_request(value: &DescribeRequest) -> Result<String, serde_json::Error> { encode_portable_json(value) }
284pub fn decode_describe_request(wire: &str) -> Result<DescribeRequest, serde_json::Error> { decode_portable_json(wire) }
285pub fn encode_describe_response(value: &DescribeResponse) -> Result<String, serde_json::Error> { encode_portable_json(value) }
286pub fn decode_describe_response(wire: &str) -> Result<DescribeResponse, serde_json::Error> { decode_portable_json(wire) }
287pub fn encode_describe_error(value: &DescribeError) -> Result<String, serde_json::Error> { encode_portable_json(value) }
288pub fn decode_describe_error(wire: &str) -> Result<DescribeError, serde_json::Error> { decode_portable_json(wire) }
289
290pub fn encode_handle_request(value: &HandleRequest) -> Result<String, serde_json::Error> { encode_portable_json(value) }
291pub fn decode_handle_request(wire: &str) -> Result<HandleRequest, serde_json::Error> { decode_portable_json(wire) }
292pub fn encode_handle_response(value: &HandleResponse) -> Result<String, serde_json::Error> { encode_portable_json(value) }
293pub fn decode_handle_response(wire: &str) -> Result<HandleResponse, serde_json::Error> { decode_portable_json(wire) }
294pub fn encode_handle_error(value: &HandleError) -> Result<String, serde_json::Error> { encode_portable_json(value) }
295pub fn decode_handle_error(wire: &str) -> Result<HandleError, serde_json::Error> { decode_portable_json(wire) }
296
297pub trait EndpointProvider: fmt::Debug + 'static {
298 fn describe(&self, context: InvocationContext, request: DescribeRequest) -> NativeRequestFuture<EndpointDescribe>;
299 fn handle(&self, context: InvocationContext, request: HandleRequest) -> NativeRequestFuture<EndpointHandle>;
300}
301
302#[derive(Debug)]
303struct EndpointRequestEndpoint { provider: Rc<dyn EndpointProvider> }
304
305#[derive(Debug)]
306pub struct EndpointEndpoint<P: EndpointProvider> { provider: Rc<P>, request_endpoint: EndpointRequestEndpoint }
307impl<P: EndpointProvider> EndpointEndpoint<P> {
308 pub fn new(provider: P) -> Self {
309 let provider = Rc::new(provider);
310 let request_provider: Rc<dyn EndpointProvider> = provider.clone();
311 Self { provider, request_endpoint: EndpointRequestEndpoint { provider: request_provider } }
312 }
313}
314
315impl<P: EndpointProvider> NativeRequestEndpoint for EndpointEndpoint<P> {
316 fn capability_id(&self) -> &'static str { CAPABILITY_ID }
317 fn descriptor_version(&self) -> &'static str { DESCRIPTOR_VERSION }
318 fn operations(&self) -> &'static [&'static str] { &[
319 DESCRIBE_OPERATION,
320 HANDLE_OPERATION,
321 ] }
322 fn typed_endpoint(&self) -> Option<&dyn std::any::Any> { Some(&self.request_endpoint) }
323 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>> {
324 match operation {
325 DESCRIBE_OPERATION => {
326 let Ok(request) = request.downcast::<DescribeRequest>() else {
327 return Box::pin(futures::future::ready(Err(RuntimeFailure::ProtocolViolation { capability: CAPABILITY_ID })));
328 };
329 let invocation = Rc::clone(&self.provider).describe(context, *request);
330 Box::pin(async move {
331 invocation.await.map(|result| {
332 result
333 .map(|value| Box::new(value) as Box<dyn std::any::Any>)
334 .map_err(|error| Box::new(error) as Box<dyn std::any::Any>)
335 })
336 })
337 },
338 HANDLE_OPERATION => {
339 let Ok(request) = request.downcast::<HandleRequest>() else {
340 return Box::pin(futures::future::ready(Err(RuntimeFailure::ProtocolViolation { capability: CAPABILITY_ID })));
341 };
342 let invocation = Rc::clone(&self.provider).handle(context, *request);
343 Box::pin(async move {
344 invocation.await.map(|result| {
345 result
346 .map(|value| Box::new(value) as Box<dyn std::any::Any>)
347 .map_err(|error| Box::new(error) as Box<dyn std::any::Any>)
348 })
349 })
350 }
351 _ => Box::pin(futures::future::ready(Err(RuntimeFailure::UnknownOperation { capability: CAPABILITY_ID, operation: operation.to_owned() }))),
352 }
353 }
354}
355
356#[derive(Debug)]
357pub struct EndpointClient {
358 describe: NativeRequestHandle<EndpointDescribe>,
359 handle: NativeRequestHandle<EndpointHandle>,
360}
361impl EndpointClient {
362 pub fn from_dependencies(dependencies: &ModuleDependencies) -> Result<Self, RuntimeFailure> {
363 Ok(Self {
364 describe: dependencies.one::<EndpointDescribe>()?,
365 handle: dependencies.one::<EndpointHandle>()?,
366 })
367 }
368
369 pub async fn describe(&self, request: DescribeRequest) -> Result<DescribeResponse, EndpointDescribeInvocationError> {
370 self.describe.invoke(DESCRIBE_OPERATION, request).await
371 .map_err(EndpointDescribeInvocationError::Runtime)?
372 .map_err(EndpointDescribeInvocationError::Domain)
373 }
374
375 pub async fn describe_with_context(&self, context: InvocationContext, request: DescribeRequest) -> Result<DescribeResponse, EndpointDescribeInvocationError> {
376 self.describe.invoke_with_context(DESCRIBE_OPERATION, context, request).await
377 .map_err(EndpointDescribeInvocationError::Runtime)?
378 .map_err(EndpointDescribeInvocationError::Domain)
379 }
380
381 pub async fn handle(&self, request: HandleRequest) -> Result<HandleResponse, EndpointHandleInvocationError> {
382 self.handle.invoke(HANDLE_OPERATION, request).await
383 .map_err(EndpointHandleInvocationError::Runtime)?
384 .map_err(EndpointHandleInvocationError::Domain)
385 }
386
387 pub async fn handle_with_context(&self, context: InvocationContext, request: HandleRequest) -> Result<HandleResponse, EndpointHandleInvocationError> {
388 self.handle.invoke_with_context(HANDLE_OPERATION, context, request).await
389 .map_err(EndpointHandleInvocationError::Runtime)?
390 .map_err(EndpointHandleInvocationError::Domain)
391 }
392}
393
394#[derive(Clone, Debug, PartialEq)]
395pub enum EndpointDescribeInvocationError {
396 Domain(DescribeError),
397 Runtime(RuntimeFailure),
398}
399#[derive(Clone, Debug, PartialEq)]
400pub enum EndpointHandleInvocationError {
401 Domain(HandleError),
402 Runtime(RuntimeFailure),
403}