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
26/// Signals that the context statically provides a certain type.
27///
28/// Writables may require this bound upon the output's context in order to implement themselves.
29/// A context that is calculated at runtime should implement `ContextProvides<T>` for all `T`, and
30/// panic at runtime if a value is called that is not present.
31/// A context that is calculated statically should implement this trait only for the types it
32/// is statically known to provide.
33pub trait ContextProvides<V> {
34 fn provide(&self) -> &V;
35}
36
37/// Dynamic context object.
38/// Provides every type that is `'static` via [ContextProvides], but will panic at runtime if used
39/// with an unavailable type.
40#[derive(Debug, Default)]
41pub struct DynContext {
42 backing: HashMap<TypeId, Box<dyn Any>>,
43}
44
45impl DynContext {
46 /// Gets a single value if the type exists in the map
47 pub fn provide_if_set<T: 'static>(&self) -> Option<&T> {
48 let found = self.backing.get(&TypeId::of::<T>());
49 found.map(|any| any.downcast_ref().unwrap())
50 }
51}
52
53impl Context for DynContext {}
54
55impl<V> ContextProvides<V> for DynContext
56where
57 V: 'static,
58{
59 fn provide(&self) -> &V {
60 self.provide_if_set()
61 .unwrap_or_else(|| panic!("Context does not provide {:?}", type_name::<V>()))
62 }
63}
64
65/// A builder for a [`DynContext`]
66#[derive(Default)]
67pub struct DynContextBuilder(HashMap<TypeId, Box<dyn Any>>);
68
69impl DynContextBuilder {
70 /// Adds a context key. The value is stored according to its type
71 pub fn add_key<T: 'static>(&mut self, value: T) -> &mut Self {
72 self.0.insert(TypeId::of::<T>(), Box::new(value));
73 self
74 }
75
76 /// Builds into a usable context object
77 pub fn build(self) -> DynContext {
78 DynContext { backing: self.0 }
79 }
80}
81
82/// A macro that helps implement [ContextProvides] for simple struct fields.
83///
84/// This can only be used with fields that don't use generic parameters, but you can use a type
85/// alias to work around this limitation. For example:
86/// ```
87/// use std::collections::HashMap;
88/// use async_codegen::{static_context_provides, context::ContextProvides};
89///
90/// struct MyDetail;
91/// type CodeGenPoints = HashMap<u16, String>;
92///
93/// struct MyContext {
94/// detail: MyDetail,
95/// points: CodeGenPoints
96/// }
97///
98/// static_context_provides!(MyContext, MyDetail, detail, CodeGenPoints, points);
99/// ```
100#[macro_export]
101macro_rules! static_context_provides {
102 ($struct_name:ident, $field_type:ident, $field_name:ident) => {
103 impl ContextProvides<$field_type> for $struct_name {
104 fn provide(&self) -> &$field_type {
105 &self.$field_name
106 }
107 }
108 };
109 ($struct_name:ident, $field_type:ident, $field_name:ident, $($further:ident),+) => {
110 impl ContextProvides<$field_type> for $struct_name {
111 fn provide(&self) -> &$field_type {
112 &self.$field_name
113 }
114 }
115 static_context_provides!($struct_name, $($further),+);
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::ContextProvides;
122 use static_assertions::assert_impl_all;
123
124 pub struct StaticContextExample {
125 pub edition: u16,
126 pub name: String,
127 }
128 static_context_provides!(StaticContextExample, u16, edition, String, name);
129 assert_impl_all!(StaticContextExample: ContextProvides<u16>, ContextProvides<String>);
130
131 #[test]
132 fn provide_same_values() {
133 let example = StaticContextExample {
134 edition: 32,
135 name: "test".to_string(),
136 };
137 let edition: &u16 = example.provide();
138 let name: &String = example.provide();
139 assert_eq!(*edition, 32);
140 assert_eq!(name, "test");
141 }
142}