vm_resource/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Infrastructure for type-erased "resource" types, representing interchangeable
5//! resources that can be resolved into the same type at runtime.
6//!
7//! This allows a device's resources to be described via [`mesh`] messages so
8//! that the device initialization code does not have to be statically aware of
9//! all the different possible resource types (e.g. via an `enum`). VMMs can
10//! link in different resource resolvers to support different resource types
11//! depending on compile-time configuration.
12
13// UNSAFETY: Uses transmute to allow for type erasure.
14#![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
29/// Trait implemented by resource kinds.
30///
31/// A resource kind defines a family of interchangeable resource types, where
32/// each resource type can be resolved to the same output type.
33///
34/// The output type is specified in the [`CanResolveTo`] trait.
35///
36/// Typically this trait will be implemented on an uninhabited tag type, e.g.
37///
38/// ```
39/// enum DiskKind {}
40///
41/// trait Disk {};
42///
43/// impl vm_resource::ResourceKind for DiskKind {
44///     const NAME: &'static str = "disk";
45/// }
46/// ```
47pub trait ResourceKind: 'static + Send + Sync {
48    /// The name of the resource kind. This must be unique amongst resource kinds.
49    const NAME: &'static str;
50}
51
52/// Trait specifying that a [`ResourceKind`] can be resolved to a given output
53/// type.
54///
55/// This should be implemented exactly once for each resource kind so that
56/// Rust's type inference can determine the output type without callers having
57/// to be explicit.
58///
59/// This trait is separate from [`ResourceKind`] so that it can be implemented
60/// in a separate crate without violating Rust's coherence (orphan) rules. This
61/// is important because the type a resource resolves to is usually of no
62/// interest to the client constructing the resource, so there is no need to
63/// include the crate defining the output time in the client's dependency graph.
64pub trait CanResolveTo<O>: ResourceKind {
65    /// Additional input (besides the resource itself) when resolving resources
66    /// of this resource kind.
67    type Input<'a>: Send;
68}
69
70/// An opaque resource of kind `K`, for erasing the resource's type.
71///
72/// The resource can later be resolved with a [`ResourceResolver`].
73#[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
82/// Trait for converting resources into opaque [`Resource`]s.
83pub trait IntoResource<K: ResourceKind> {
84    /// Converts `self` into a `Resource`.
85    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    /// Wraps `value` as an opaque resource.
96    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    /// Returns the ID of the resource type.
105    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/// The generic resource defined for all resource kinds.
117///
118/// This can be used to get the default resource for the platform, for kinds
119/// where that is supported and a platform resource is registered.
120#[derive(MeshPayload)]
121pub struct PlatformResource;
122
123impl<K: ResourceKind> ResourceId<K> for PlatformResource {
124    const ID: &'static str = "platform";
125}
126
127/// A trait identifying a resource type's ID (within a given [`ResourceKind`]).
128///
129/// ```
130/// enum DiskKind {}
131///
132/// #[derive(mesh::MeshPayload)]
133/// struct FileDiskConfig {
134///     path: String,
135/// }
136///
137/// impl vm_resource::ResourceId<DiskKind> for FileDiskConfig {
138///     const ID: &'static str = "file";
139/// }
140/// ```
141pub trait ResourceId<K> {
142    /// The ID of this resource type.
143    ///
144    /// This must be unique amongst resource types of this kind. It does not
145    /// need to be unique between types of different resource kinds.
146    const ID: &'static str;
147}
148
149/// Trait implemented to resolve resource type `T` as resource kind `K`.
150pub trait ResolveResource<K: CanResolveTo<Self::Output>, T>: Send + Sync {
151    /// The output type for resource resolution.
152    type Output;
153    /// The error type for `resolve`.
154    type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
155
156    /// Resolves the resource.
157    fn resolve(&self, resource: T, input: K::Input<'_>) -> Result<Self::Output, Self::Error>;
158}
159
160/// Trait implemented to resolve resource type `T` as resource kind `K`.
161///
162/// Unlike [`ResolveResource`], this allows for async operation, including
163/// calling into other resource resolvers to resolve sub-resources.
164#[async_trait]
165pub trait AsyncResolveResource<K: CanResolveTo<Self::Output>, T>: Send + Sync {
166    /// The output type for resource resolution.
167    type Output;
168    /// The error type for `resolve`.
169    type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
170
171    /// Resolves the resource.
172    ///
173    /// `resolver` can be used to resolve sub-resources.
174    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/// A resource resolver capable of resolving resources of multiple types and
292/// kinds.
293#[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/// Declares the resource kinds and types that a static resolver can resolve.
321///
322/// This can be used along with [`register_static_resolvers`] to build up the
323/// list of resolvers at link time, simplifying construction of the resolver
324/// list.
325///
326/// ```ignore
327/// declare_static_resolver! {
328///     FileDiskResolver,
329///     (DiskConfigKind, FileDiskConfig),
330///     (DiskHandleKind, FileDiskHandle),
331/// }
332/// ```
333#[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/// Declares the resource kinds and types that an async static resolver can
349/// resolve.
350///
351/// See [`declare_static_resolver`].
352#[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/// Registers a static resolver, declared via [`declare_static_resolver`] or
368/// [`declare_static_async_resolver`], so that it is automatically available to
369/// any [`ResourceResolver`] in the binary.
370#[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    // Use Option<&&[X]> in case the linker inserts some stray nulls, as we
403    // think it might on Windows. The double pointer is necessary since &[X]
404    // alone is two pointers wide.
405    //
406    // See <https://devblogs.microsoft.com/oldnewthing/20181108-00/?p=100165>.
407    #[linkme::distributed_slice]
408    pub static STATIC_RESOLVERS: [Option<&'static &'static [StaticResolver]>] = [..];
409
410    // Always have at least one entry to work around linker bugs.
411    //
412    // See <https://github.com/llvm/llvm-project/issues/65855>.
413    #[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            // SAFETY: TypedResolver<T, R> contains a &'static R and is transparent.
436            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            // SAFETY: TypedAsyncResolver<T, R> contains a &'static R and is transparent.
448            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/// An error returned by [`ResourceResolver::resolve`].
471#[derive(Debug, Error)]
472pub enum ResolveError {
473    /// The resolver can't be found.
474    #[error("no resolver for {kind}:{id}")]
475    NoResolver {
476        /// The resource kind.
477        kind: &'static str,
478        /// The resource type's ID.
479        id: Cow<'static, str>,
480    },
481    /// The resource couldn't be parsed back to the expected type.
482    #[error("failed to parse resource of type {kind}:{id}")]
483    ParseError {
484        /// The resource kind.
485        kind: &'static str,
486        /// The resource type's ID.
487        id: Cow<'static, str>,
488        /// The underlying error.
489        #[source]
490        source: mesh::payload::Error,
491    },
492    /// The resource couldn't be resolved.
493    #[error("failed to resolve resource of type {kind}:{id}")]
494    ResolverError {
495        /// The resource kind.
496        kind: &'static str,
497        /// The resource type's ID.
498        id: Cow<'static, str>,
499        /// The underlying error.
500        #[source]
501        source: Box<dyn std::error::Error + Send + Sync>,
502    },
503}
504
505impl ResourceResolver {
506    /// Returns a new resolver, which initially only supports the static resolvers.
507    pub fn new() -> Self {
508        // Ensure the static resolvers don't have duplicates.
509        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    /// Adds a dynamic resolver.
530    ///
531    /// Panics if a resolver already exists for this resource type.
532    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    /// Adds a dynamic async resolver.
555    ///
556    /// Panics if a resolver already exists for this resource type.
557    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    /// Resolves a resource.
614    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        // Resolve from TestConfig -> TestHandle -> TestConcreteResult.
726        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}