1#![expect(unsafe_code)]
15
16pub mod kind;
17
18use async_trait::async_trait;
19use inspect::Inspect;
20use mesh::MeshPayload;
21use mesh::OwnedMessage;
22use std::any::Any;
23use std::borrow::Cow;
24use std::fmt::Display;
25use std::marker::PhantomData;
26use std::sync::Arc;
27use thiserror::Error;
28
29pub trait ResourceKind: 'static + Send + Sync {
48 const NAME: &'static str;
50}
51
52pub trait CanResolveTo<O>: ResourceKind {
65 type Input<'a>: Send;
68}
69
70#[derive(MeshPayload)]
74#[mesh(bound = "")]
75pub struct Resource<K: ResourceKind> {
76 #[mesh(encoding = "mesh::payload::encoding::OwningCowField")]
77 id: Cow<'static, str>,
78 message: OwnedMessage,
79 _phantom: PhantomData<fn(K) -> K>,
80}
81
82pub trait IntoResource<K: ResourceKind> {
84 fn into_resource(self) -> Resource<K>;
86}
87
88impl<T: 'static + ResourceId<K> + MeshPayload + Send, K: ResourceKind> IntoResource<K> for T {
89 fn into_resource(self) -> Resource<K> {
90 Resource::new(self)
91 }
92}
93
94impl<K: ResourceKind> Resource<K> {
95 pub fn new<T: 'static + ResourceId<K> + MeshPayload + Send>(value: T) -> Self {
97 Self {
98 id: Cow::Borrowed(T::ID),
99 message: OwnedMessage::new(value),
100 _phantom: PhantomData,
101 }
102 }
103
104 pub fn id(&self) -> &str {
106 &self.id
107 }
108}
109
110impl<K: ResourceKind> std::fmt::Debug for Resource<K> {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.debug_struct("Resource").field("id", &self.id).finish()
113 }
114}
115
116#[derive(MeshPayload)]
121pub struct PlatformResource;
122
123impl<K: ResourceKind> ResourceId<K> for PlatformResource {
124 const ID: &'static str = "platform";
125}
126
127pub trait ResourceId<K> {
142 const ID: &'static str;
147}
148
149pub trait ResolveResource<K: CanResolveTo<Self::Output>, T>: Send + Sync {
151 type Output;
153 type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
155
156 fn resolve(&self, resource: T, input: K::Input<'_>) -> Result<Self::Output, Self::Error>;
158}
159
160#[async_trait]
165pub trait AsyncResolveResource<K: CanResolveTo<Self::Output>, T>: Send + Sync {
166 type Output;
168 type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
170
171 async fn resolve(
175 &self,
176 resolver: &ResourceResolver,
177 resource: T,
178 input: K::Input<'_>,
179 ) -> Result<Self::Output, Self::Error>;
180}
181
182#[repr(transparent)]
183struct TypedResolver<T, R> {
184 resolver: R,
185 _phantom: PhantomData<fn(T)>,
186}
187
188#[repr(transparent)]
189struct TypedAsyncResolver<T, R> {
190 resolver: R,
191 _phantom: PhantomData<fn(T)>,
192}
193
194#[async_trait]
195trait DynResolveResource<K: CanResolveTo<O>, O>: Send + Sync {
196 async fn dyn_resolve(
197 &self,
198 resolver: &ResourceResolver,
199 resource: Resource<K>,
200 input: K::Input<'_>,
201 ) -> Result<O, ResolveError>;
202}
203
204#[async_trait]
205impl<K, R, T, O> DynResolveResource<K, O> for TypedResolver<T, R>
206where
207 K: CanResolveTo<O>,
208 O: 'static,
209 R: ResolveResource<K, T, Output = O>,
210 T: 'static + MeshPayload + ResourceId<K> + Send,
211{
212 async fn dyn_resolve(
213 &self,
214 _resolver: &ResourceResolver,
215 resource: Resource<K>,
216 input: K::Input<'_>,
217 ) -> Result<O, ResolveError> {
218 let parsed = resource
219 .message
220 .parse()
221 .map_err(|source| ResolveError::ParseError {
222 kind: K::NAME,
223 id: resource.id.clone(),
224 source,
225 })?;
226
227 let resolved =
228 self.resolver
229 .resolve(parsed, input)
230 .map_err(|source| ResolveError::ResolverError {
231 kind: K::NAME,
232 id: resource.id.clone(),
233 source: source.into(),
234 })?;
235
236 Ok(resolved)
237 }
238}
239
240#[async_trait]
241impl<R, T, K, O> DynResolveResource<K, O> for TypedAsyncResolver<T, R>
242where
243 K: CanResolveTo<O>,
244 O: 'static,
245 R: AsyncResolveResource<K, T, Output = O>,
246 T: 'static + MeshPayload + ResourceId<K> + Send,
247{
248 async fn dyn_resolve(
249 &self,
250 resolver: &ResourceResolver,
251 resource: Resource<K>,
252 input: K::Input<'_>,
253 ) -> Result<O, ResolveError> {
254 let parsed = resource
255 .message
256 .parse()
257 .map_err(|source| ResolveError::ParseError {
258 kind: K::NAME,
259 id: resource.id.clone(),
260 source,
261 })?;
262
263 let resolved = self
264 .resolver
265 .resolve(resolver, parsed, input)
266 .await
267 .map_err(|source| ResolveError::ResolverError {
268 kind: K::NAME,
269 id: resource.id.clone(),
270 source: source.into(),
271 })?;
272
273 Ok(resolved)
274 }
275}
276
277struct UntypedResolver<K, O>(Box<dyn DynResolveResource<K, O>>);
278
279#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
280struct ResolverKey {
281 kind: &'static str,
282 id: &'static str,
283}
284
285impl Display for ResolverKey {
286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 write!(f, "{}:{}", self.kind, self.id)
288 }
289}
290
291#[derive(Clone)]
294pub struct ResourceResolver {
295 resolvers: Arc<Vec<(ResolverKey, Arc<dyn Any + Send + Sync>)>>,
296}
297
298impl Inspect for ResourceResolver {
299 fn inspect(&self, req: inspect::Request<'_>) {
300 let mut resp = req.respond();
301 for private::StaticResolver { key, .. } in private::STATIC_RESOLVERS
302 .iter()
303 .copied()
304 .flatten()
305 .copied()
306 .flatten()
307 {
308 resp.child(&format!("{}/{}", key.kind, key.id), |req| {
309 req.respond();
310 });
311 }
312 for (key, _) in &*self.resolvers {
313 resp.child(&format!("{}/{}", key.kind, key.id), |req| {
314 req.respond();
315 });
316 }
317 }
318}
319
320#[macro_export]
334macro_rules! declare_static_resolver {
335 ($resolver:tt, $(($kind:ty, $resource:ty $(,)?)),* $(,)?) => {
336 const _: () = {
337 use $crate::private::{StaticResolver, StaticResolverList, UntypedStaticResolver};
338
339 impl StaticResolverList for $resolver {
340 const RESOLVERS: &'static [StaticResolver] = &[
341 $(StaticResolver::new::<$kind, $resource, _>(&UntypedStaticResolver::new::<$resource, _>(&$resolver)),)*
342 ];
343 }
344 };
345 };
346}
347
348#[macro_export]
353macro_rules! declare_static_async_resolver {
354 ($resolver:tt, $(($kind:ty, $resource:ty $(,)?)),* $(,)?) => {
355 const _: () = {
356 use $crate::private::{StaticResolver, StaticResolverList, UntypedStaticResolver};
357
358 impl StaticResolverList for $resolver {
359 const RESOLVERS: &'static [StaticResolver] = &[
360 $(StaticResolver::new::<$kind, $resource, _>(&UntypedStaticResolver::new_async::<$resource, _>(&$resolver)),)*
361 ];
362 }
363 };
364 };
365}
366
367#[macro_export]
371macro_rules! register_static_resolvers {
372 {} => {};
373 { $( $(#[$a:meta])* $resolver:ty ),+ $(,)? } => {
374 $(
375 $(#[$a])*
376 const _: () = {
377 use $crate::private::{linkme, StaticResolver, StaticResolverList, STATIC_RESOLVERS};
378
379 #[linkme::distributed_slice(STATIC_RESOLVERS)]
380 #[linkme(crate = linkme)]
381 static RESOLVER: Option<&'static &'static [StaticResolver]> =
382 Some(&<$resolver as StaticResolverList>::RESOLVERS);
383 };
384 )*
385 };
386}
387
388#[doc(hidden)]
389pub mod private {
390 use super::AsyncResolveResource;
391 use super::DynResolveResource;
392 use super::ResolveResource;
393 use super::ResolverKey;
394 use super::ResourceId;
395 use super::TypedAsyncResolver;
396 use super::TypedResolver;
397 use crate::CanResolveTo;
398 pub use linkme;
399 use mesh::MeshPayload;
400 use std::any::Any;
401
402 #[linkme::distributed_slice]
408 pub static STATIC_RESOLVERS: [Option<&'static &'static [StaticResolver]>] = [..];
409
410 #[linkme::distributed_slice(STATIC_RESOLVERS)]
414 static WORKAROUND: Option<&'static &'static [StaticResolver]> = None;
415
416 pub trait StaticResolverList: Send {
417 const RESOLVERS: &'static [StaticResolver];
418 }
419
420 pub struct StaticResolver {
421 pub(super) key: ResolverKey,
422 pub(super) resolver: &'static (dyn Any + Send + Sync),
423 }
424
425 pub struct UntypedStaticResolver<K: CanResolveTo<O>, O: 'static>(
426 pub(super) &'static dyn DynResolveResource<K, O>,
427 );
428
429 impl<K: CanResolveTo<O>, O> UntypedStaticResolver<K, O> {
430 pub const fn new<T, R>(resolver: &'static R) -> Self
431 where
432 T: 'static + ResourceId<K> + MeshPayload + Send,
433 R: ResolveResource<K, T, Output = O>,
434 {
435 let resolver = unsafe {
437 std::mem::transmute::<&'static R, &'static TypedResolver<T, R>>(resolver)
438 };
439 Self(resolver)
440 }
441
442 pub const fn new_async<T, R>(resolver: &'static R) -> Self
443 where
444 T: 'static + ResourceId<K> + MeshPayload + Send,
445 R: AsyncResolveResource<K, T, Output = O>,
446 {
447 let resolver = unsafe {
449 std::mem::transmute::<&'static R, &'static TypedAsyncResolver<T, R>>(resolver)
450 };
451 Self(resolver)
452 }
453 }
454
455 impl StaticResolver {
456 pub const fn new<K: CanResolveTo<O>, T: ResourceId<K> + MeshPayload, O>(
457 resolver: &'static UntypedStaticResolver<K, O>,
458 ) -> Self {
459 Self {
460 key: ResolverKey {
461 kind: K::NAME,
462 id: T::ID,
463 },
464 resolver,
465 }
466 }
467 }
468}
469
470#[derive(Debug, Error)]
472pub enum ResolveError {
473 #[error("no resolver for {kind}:{id}")]
475 NoResolver {
476 kind: &'static str,
478 id: Cow<'static, str>,
480 },
481 #[error("failed to parse resource of type {kind}:{id}")]
483 ParseError {
484 kind: &'static str,
486 id: Cow<'static, str>,
488 #[source]
490 source: mesh::payload::Error,
491 },
492 #[error("failed to resolve resource of type {kind}:{id}")]
494 ResolverError {
495 kind: &'static str,
497 id: Cow<'static, str>,
499 #[source]
501 source: Box<dyn std::error::Error + Send + Sync>,
502 },
503}
504
505impl ResourceResolver {
506 pub fn new() -> Self {
508 let mut static_resolvers = private::STATIC_RESOLVERS
510 .iter()
511 .copied()
512 .flatten()
513 .copied()
514 .flatten()
515 .collect::<Vec<_>>();
516
517 static_resolvers.sort_by_key(|r| &r.key);
518 for (x, y) in static_resolvers.iter().zip(static_resolvers.iter().skip(1)) {
519 if x.key == y.key {
520 panic!("duplicate static resolver for {}", x.key);
521 }
522 }
523
524 Self {
525 resolvers: Arc::new(Vec::new()),
526 }
527 }
528
529 pub fn add_resolver<K, O, T, R>(&mut self, resolver: R)
533 where
534 K: CanResolveTo<O>,
535 O: 'static,
536 T: 'static + ResourceId<K> + MeshPayload + Send,
537 R: 'static + ResolveResource<K, T, Output = O>,
538 {
539 let key = ResolverKey {
540 kind: K::NAME,
541 id: T::ID,
542 };
543 if self.find_resolver::<K, O>(T::ID).is_some() {
544 panic!("duplicate resolver for {}", key);
545 }
546 let resolver = TypedResolver::<T, _> {
547 resolver,
548 _phantom: PhantomData,
549 };
550 let resolver = UntypedResolver::<K, O>(Box::new(resolver));
551 Arc::make_mut(&mut self.resolvers).push((key, Arc::new(resolver)));
552 }
553
554 pub fn add_async_resolver<K, O, T, R>(&mut self, resolver: R)
558 where
559 K: CanResolveTo<O>,
560 O: 'static,
561 T: 'static + ResourceId<K> + MeshPayload + Send,
562 R: 'static + AsyncResolveResource<K, T, Output = O>,
563 {
564 let key = ResolverKey {
565 kind: K::NAME,
566 id: T::ID,
567 };
568 if self.find_resolver::<K, O>(T::ID).is_some() {
569 panic!("duplicate resolver for {}", key);
570 }
571 let resolver = TypedAsyncResolver::<T, _> {
572 resolver,
573 _phantom: PhantomData,
574 };
575 let resolver = UntypedResolver::<K, O>(Box::new(resolver));
576 Arc::make_mut(&mut self.resolvers).push((key, Arc::new(resolver)));
577 }
578
579 fn find_resolver<K: CanResolveTo<O>, O: 'static>(
580 &self,
581 id: &str,
582 ) -> Option<&dyn DynResolveResource<K, O>> {
583 for private::StaticResolver { key, resolver } in private::STATIC_RESOLVERS
584 .iter()
585 .copied()
586 .flatten()
587 .copied()
588 .flatten()
589 {
590 if key.kind == K::NAME && key.id == id {
591 return Some(
592 resolver
593 .downcast_ref::<private::UntypedStaticResolver<K, O>>()
594 .unwrap()
595 .0,
596 );
597 }
598 }
599 for (key, resolver) in &*self.resolvers {
600 if key.kind == K::NAME && key.id == id {
601 return Some(
602 resolver
603 .downcast_ref::<UntypedResolver<K, O>>()
604 .unwrap()
605 .0
606 .as_ref(),
607 );
608 }
609 }
610 None
611 }
612
613 pub async fn resolve<K: CanResolveTo<O>, O: 'static>(
615 &self,
616 resource: Resource<K>,
617 input: K::Input<'_>,
618 ) -> Result<O, ResolveError> {
619 let resolver =
620 self.find_resolver(&resource.id)
621 .ok_or_else(|| ResolveError::NoResolver {
622 kind: K::NAME,
623 id: resource.id.clone(),
624 })?;
625
626 resolver.dyn_resolve(self, resource, input).await
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use super::ResolveResource;
633 use super::Resource;
634 use super::ResourceId;
635 use super::ResourceKind;
636 use super::ResourceResolver;
637 use crate::CanResolveTo;
638 use mesh::MeshPayload;
639 use mesh::payload::Protobuf;
640 use pal_async::async_test;
641 use std::convert::Infallible;
642
643 enum TestConfigKind {}
644
645 impl ResourceKind for TestConfigKind {
646 const NAME: &'static str = "test_config";
647 }
648
649 impl CanResolveTo<Resource<TestHandleKind>> for TestConfigKind {
650 type Input<'a> = ();
651 }
652
653 enum TestHandleKind {}
654
655 impl ResourceKind for TestHandleKind {
656 const NAME: &'static str = "test_handle";
657 }
658
659 impl CanResolveTo<TestConcreteObject> for TestHandleKind {
660 type Input<'a> = ();
661 }
662
663 #[derive(Protobuf)]
664 struct TestConfig {
665 value: u32,
666 }
667
668 impl ResourceId<TestConfigKind> for TestConfig {
669 const ID: &'static str = "foo";
670 }
671
672 #[derive(MeshPayload)]
673 struct TestHandle {
674 valuex2: u32,
675 }
676
677 impl ResourceId<TestHandleKind> for TestHandle {
678 const ID: &'static str = "open_foo";
679 }
680
681 struct TestConcreteObject {
682 result: String,
683 }
684
685 struct TestResolver;
686
687 impl ResolveResource<TestConfigKind, TestConfig> for TestResolver {
688 type Output = Resource<TestHandleKind>;
689 type Error = Infallible;
690
691 fn resolve(
692 &self,
693 resource: TestConfig,
694 _: (),
695 ) -> Result<Resource<TestHandleKind>, Self::Error> {
696 Ok(Resource::new(TestHandle {
697 valuex2: resource.value * 2,
698 }))
699 }
700 }
701
702 impl ResolveResource<TestHandleKind, TestHandle> for TestResolver {
703 type Output = TestConcreteObject;
704 type Error = Infallible;
705
706 fn resolve(&self, resource: TestHandle, _: ()) -> Result<TestConcreteObject, Self::Error> {
707 Ok(TestConcreteObject {
708 result: resource.valuex2.to_string(),
709 })
710 }
711 }
712
713 declare_static_resolver!(
714 TestResolver,
715 (TestConfigKind, TestConfig),
716 (TestHandleKind, TestHandle),
717 );
718
719 register_static_resolvers!(TestResolver);
720
721 #[async_test]
722 async fn test_resources() {
723 let resolver = ResourceResolver::new();
724
725 let x = resolver
727 .resolve(Resource::new(TestConfig { value: 5 }), ())
728 .await
729 .unwrap();
730
731 assert_eq!(resolver.resolve(x, ()).await.unwrap().result, "10");
732 }
733}