async_codegen/context.rs
1/*
2 * Copyright © 2025 Anand Beh
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use std::any::{Any, TypeId, type_name};
18use std::collections::HashMap;
19use std::fmt::Debug;
20
21/// Metadata associated with an I/O stream.
22/// The context will be available to all [super::Writable] implementations.
23/// For each value that it may provide, it should implement [ContextProvides] for that type.
24pub trait Context: Debug {}
25
26impl<'c, C> Context for &'c C where C: Context {}
27
28/// Signals that the context statically provides a certain type.
29///
30/// Writables may require this bound upon the output's context in order to implement themselves.
31/// A context that is calculated at runtime should implement `ContextProvides<T>` for all `T`, and
32/// panic at runtime if a value is called that is not present.
33/// A context that is calculated statically should implement this trait only for the types it
34/// is statically known to provide.
35pub trait ContextProvides<V> {
36 fn provide(&self) -> &V;
37}
38
39impl<'c, C, V> ContextProvides<V> for &'c C
40where
41 C: ContextProvides<V>,
42{
43 fn provide(&self) -> &V {
44 (**self).provide()
45 }
46}
47
48/// Dynamic context object.
49/// Provides every type that is `'static` via [ContextProvides], but will panic at runtime if used
50/// with an unavailable type.
51#[derive(Debug, Default)]
52pub struct DynContext {
53 backing: HashMap<TypeId, Box<dyn Any>>,
54}
55
56impl DynContext {
57 /// Gets a single value if the type exists in the map
58 pub fn provide_if_set<T: 'static>(&self) -> Option<&T> {
59 let found = self.backing.get(&TypeId::of::<T>());
60 found.map(|any| any.downcast_ref().unwrap())
61 }
62}
63
64impl Context for DynContext {}
65
66impl<V> ContextProvides<V> for DynContext
67where
68 V: 'static,
69{
70 fn provide(&self) -> &V {
71 self.provide_if_set()
72 .unwrap_or_else(|| panic!("Context does not provide {:?}", type_name::<V>()))
73 }
74}
75
76/// A builder for a [`DynContext`]
77#[derive(Debug, Default)]
78pub struct DynContextBuilder(HashMap<TypeId, Box<dyn Any>>);
79
80impl DynContextBuilder {
81 /// Adds a context key. The value is stored according to its type
82 pub fn add_key<T: 'static>(&mut self, value: T) -> &mut Self {
83 self.0.insert(TypeId::of::<T>(), Box::new(value));
84 self
85 }
86
87 /// Builds into a usable context object
88 pub fn build(self) -> DynContext {
89 DynContext { backing: self.0 }
90 }
91}
92
93/// A context that yields nothing. Can be useful for testing.
94#[derive(Copy, Clone, Debug)]
95pub struct EmptyContext;
96
97impl Context for EmptyContext {}
98
99/// A context that provides only the tuple value
100#[derive(Clone, Debug)]
101pub struct SingularContext<V>(pub V);
102
103impl<V: Debug> Context for SingularContext<V> {}
104
105impl<V> ContextProvides<V> for SingularContext<V> {
106 fn provide(&self) -> &V {
107 &self.0
108 }
109}
110
111/// A macro that helps implement [ContextProvides] for simple struct fields.
112///
113/// This can only be used with fields that don't use generic parameters, but you can use a type
114/// alias to work around this limitation. For example:
115/// ```
116/// use std::collections::HashMap;
117/// use async_codegen::{static_context_provides, context::ContextProvides};
118///
119/// struct MyDetail;
120/// type CodeGenPoints = HashMap<u16, String>;
121///
122/// struct MyContext {
123/// detail: MyDetail,
124/// points: CodeGenPoints
125/// }
126///
127/// static_context_provides!(MyContext, MyDetail, detail, CodeGenPoints, points);
128/// ```
129#[macro_export]
130macro_rules! static_context_provides {
131 ($struct_name:ty, $field_type:ident, $field_name:ident) => {
132 impl $crate::context::ContextProvides<$field_type> for $struct_name {
133 fn provide(&self) -> &$field_type {
134 &self.$field_name
135 }
136 }
137 };
138 ($struct_name:ty, $field_type:ident, $field_name:ident, $($further:ident),+) => {
139 impl $crate::context::ContextProvides<$field_type> for $struct_name {
140 fn provide(&self) -> &$field_type {
141 &self.$field_name
142 }
143 }
144 static_context_provides!($struct_name, $($further),+);
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::ContextProvides;
151 pub struct StaticContextExample {
152 pub edition: u16,
153 pub name: String,
154 }
155 static_context_provides!(StaticContextExample, u16, edition, String, name);
156
157 #[test]
158 fn provide_same_values() {
159 let example = StaticContextExample {
160 edition: 32,
161 name: "test".to_string(),
162 };
163 let edition: &u16 = example.provide();
164 let name: &String = example.provide();
165 assert_eq!(*edition, 32);
166 assert_eq!(name, "test");
167 }
168}