wire_framework/service/
context_traits.rs

1use crate::{resource::Resource, service::context::ServiceContext, wiring_layer::WiringError};
2
3/// Trait used as input for wiring layers, aiming to provide all the resources the layer needs for wiring.
4///
5/// For most cases, the most conevenient way to implement this trait is to use the `#[derive(FromContext)]`.
6/// Otherwise, the trait has several blanket implementations (including the implementation for `()` and `Option`).
7///
8/// # Example
9///
10/// ```
11/// use wire_framework::FromContext;
12/// # #[derive(Clone)]
13/// # struct MandatoryResource;
14/// # impl wire_framework::resource::Resource for MandatoryResource { fn name() -> String { "a".into() } }
15/// # #[derive(Clone)]
16/// # struct OptionalResource;
17/// # impl wire_framework::resource::Resource for OptionalResource { fn name() -> String { "b".into() } }
18/// # #[derive(Default, Clone)]
19/// # struct ResourceWithDefault;
20/// # impl wire_framework::resource::Resource for ResourceWithDefault { fn name() -> String { "c".into() } }
21/// #[derive(FromContext)]
22/// struct MyWiringLayerInput {
23///     // The following field _must_ be present in the context.
24///     mandatory_resource: MandatoryResource,
25///     // The following field is optional.
26///     // If will be `None` if there is no such resource in the context.
27///     optional_resource: Option<OptionalResource>,
28///     // The following field is guaranteed to fetch the value from the context.
29///     // In case the value is missing, a default value will be added to the context.
30///     #[context(default)]
31///     resource_with_default: ResourceWithDefault,
32/// }
33/// ```
34pub trait FromContext: Sized {
35    fn from_context(context: &mut ServiceContext<'_>) -> Result<Self, WiringError>;
36}
37
38impl<T: Resource + Clone> FromContext for T {
39    fn from_context(context: &mut ServiceContext<'_>) -> Result<Self, WiringError> {
40        context.get_resource::<T>()
41    }
42}
43
44impl FromContext for () {
45    fn from_context(_context: &mut ServiceContext<'_>) -> Result<Self, WiringError> {
46        Ok(())
47    }
48}
49
50impl<T: FromContext> FromContext for Option<T> {
51    fn from_context(context: &mut ServiceContext<'_>) -> Result<Self, WiringError> {
52        match T::from_context(context) {
53            Ok(inner) => Ok(Some(inner)),
54            Err(WiringError::ResourceLacking { .. }) => Ok(None),
55            Err(err) => Err(err),
56        }
57    }
58}
59
60/// Trait used as output for wiring layers, aiming to provide all the resources and tasks the layer creates.
61///
62/// For most cases, the most conevenient way to implement this trait is to use the `#[derive(IntoContext)]`.
63/// Otherwise, the trait has several blanket implementations (including the implementation for `()` and `Option`).
64/// Note, however, that due to the lack of specialization, the blanket implementation for `Option<T: Task>` is not
65/// provided. When used in the macro, tasks must be annotated with the `#[context(task)]` attribute.
66///
67/// Note: returning a resource that already exists in the context will result in a wiring error. If you need to provide
68/// a "substitute" resource, request `Option` of it in the `FromContext` implementation to check whether it's already
69/// provided.
70///
71///
72/// # Example
73///
74/// ```
75/// use wire_framework::IntoContext;
76/// # struct MyTask;
77/// # #[async_trait::async_trait]
78/// # impl wire_framework::task::Task for MyTask {
79/// #   fn id(&self) -> wire_framework::TaskId { "a".into() }
80/// #   async fn run(self: Box<Self>, _: wire_framework::StopReceiver) -> eyre::Result<()> { Ok(()) }
81/// # }
82/// # struct MaybeTask;
83/// # #[async_trait::async_trait]
84/// # impl wire_framework::task::Task for MaybeTask {
85/// #   fn id(&self) -> wire_framework::TaskId { "b".into() }
86/// #   async fn run(self: Box<Self>, _: wire_framework::StopReceiver) -> eyre::Result<()> { Ok(()) }
87/// # }
88/// # struct MyResource;
89/// # impl wire_framework::resource::Resource for MyResource { fn name() -> String { "a".into() } }
90/// # struct MaybeResource;
91/// # impl wire_framework::resource::Resource for MaybeResource { fn name() -> String { "b".into() } }
92/// #[derive(IntoContext)]
93/// struct MyWiringLayerOutput {
94///     // This resource will be inserted unconditionally.
95///     // Will err if such resource is already present in the context.
96///     recource: MyResource,
97///     // Will only provide the resource if it's `Some`.
98///     maybe_resource: Option<MaybeResource>,
99///     // Will provide task unconditionally.
100///     #[context(task)]
101///     task: MyTask,
102///     // Will provide task only if it's `Some`.
103///     #[context(task)]
104///     maybe_task: Option<MaybeTask>,
105/// }
106/// ```
107pub trait IntoContext {
108    fn into_context(self, context: &mut ServiceContext<'_>) -> Result<(), WiringError>;
109}
110
111// Unfortunately, without specialization we cannot provide a blanket implementation for `T: Task`
112// as well. `Resource` is chosen because it also has a blanket implementation of `FromContext`.
113impl<T: Resource> IntoContext for T {
114    fn into_context(self, context: &mut ServiceContext<'_>) -> Result<(), WiringError> {
115        context.insert_resource(self)
116    }
117}
118
119impl IntoContext for () {
120    fn into_context(self, _context: &mut ServiceContext<'_>) -> Result<(), WiringError> {
121        Ok(())
122    }
123}
124
125impl<T: IntoContext> IntoContext for Option<T> {
126    fn into_context(self, context: &mut ServiceContext<'_>) -> Result<(), WiringError> {
127        if let Some(inner) = self {
128            inner.into_context(context)
129        } else {
130            Ok(())
131        }
132    }
133}