async-codegen 0.12.1

Minimalist async-IO code generation framework.
Documentation
/*
 * Copyright © 2025 Anand Beh
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::any::{Any, TypeId, type_name};
use std::collections::HashMap;
use std::fmt::Debug;

/// Metadata associated with an I/O stream.
/// The context will be available to all [super::Writable] implementations.
/// For each value that it may provide, it should implement [ContextProvides] for that type.
pub trait Context: Debug {}

impl<'c, C> Context for &'c C where C: Context {}

/// Signals that the context statically provides a certain type.
///
/// Writables may require this bound upon the output's context in order to implement themselves.
/// A context that is calculated at runtime should implement `ContextProvides<T>` for all `T`, and
/// panic at runtime if a value is called that is not present.
/// A context that is calculated statically should implement this trait only for the types it
/// is statically known to provide.
pub trait ContextProvides<V> {
    fn provide(&self) -> &V;
}

impl<'c, C, V> ContextProvides<V> for &'c C
where
    C: ContextProvides<V>,
{
    fn provide(&self) -> &V {
        (**self).provide()
    }
}

/// Dynamic context object.
/// Provides every type that is `'static` via [ContextProvides], but will panic at runtime if used
/// with an unavailable type.
#[derive(Debug, Default)]
pub struct DynContext {
    backing: HashMap<TypeId, Box<dyn Any>>,
}

impl DynContext {
    /// Gets a single value if the type exists in the map
    pub fn provide_if_set<T: 'static>(&self) -> Option<&T> {
        let found = self.backing.get(&TypeId::of::<T>());
        found.map(|any| any.downcast_ref().unwrap())
    }
}

impl Context for DynContext {}

impl<V> ContextProvides<V> for DynContext
where
    V: 'static,
{
    fn provide(&self) -> &V {
        self.provide_if_set()
            .unwrap_or_else(|| panic!("Context does not provide {:?}", type_name::<V>()))
    }
}

/// A builder for a [`DynContext`]
#[derive(Debug, Default)]
pub struct DynContextBuilder(HashMap<TypeId, Box<dyn Any>>);

impl DynContextBuilder {
    /// Adds a context key. The value is stored according to its type
    pub fn add_key<T: 'static>(&mut self, value: T) -> &mut Self {
        self.0.insert(TypeId::of::<T>(), Box::new(value));
        self
    }

    /// Builds into a usable context object
    pub fn build(self) -> DynContext {
        DynContext { backing: self.0 }
    }
}

/// A context that yields nothing. Can be useful for testing.
#[derive(Copy, Clone, Debug)]
pub struct EmptyContext;

impl Context for EmptyContext {}

/// A context that provides only the tuple value
#[derive(Clone, Debug)]
pub struct SingularContext<V>(pub V);

impl<V: Debug> Context for SingularContext<V> {}

impl<V> ContextProvides<V> for SingularContext<V> {
    fn provide(&self) -> &V {
        &self.0
    }
}

/// A macro that helps implement [ContextProvides] for simple struct fields.
///
/// This can only be used with fields that don't use generic parameters, but you can use a type
/// alias to work around this limitation. For example:
/// ```
/// use std::collections::HashMap;
/// use async_codegen::{static_context_provides, context::ContextProvides};
///
/// struct MyDetail;
/// type CodeGenPoints = HashMap<u16, String>;
///
/// struct MyContext {
///   detail: MyDetail,
///   points: CodeGenPoints
/// }
///
/// static_context_provides!(MyContext, MyDetail, detail, CodeGenPoints, points);
/// ```
#[macro_export]
macro_rules! static_context_provides {
    ($struct_name:ty, $field_type:ident, $field_name:ident) => {
        impl $crate::context::ContextProvides<$field_type> for $struct_name {
            fn provide(&self) -> &$field_type {
                &self.$field_name
            }
        }
    };
    ($struct_name:ty, $field_type:ident, $field_name:ident, $($further:ident),+) => {
        impl $crate::context::ContextProvides<$field_type> for $struct_name {
            fn provide(&self) -> &$field_type {
                &self.$field_name
            }
        }
        static_context_provides!($struct_name, $($further),+);
    }
}

#[cfg(test)]
mod tests {
    use super::ContextProvides;
    pub struct StaticContextExample {
        pub edition: u16,
        pub name: String,
    }
    static_context_provides!(StaticContextExample, u16, edition, String, name);

    #[test]
    fn provide_same_values() {
        let example = StaticContextExample {
            edition: 32,
            name: "test".to_string(),
        };
        let edition: &u16 = example.provide();
        let name: &String = example.provide();
        assert_eq!(*edition, 32);
        assert_eq!(name, "test");
    }
}