async_codegen/
lib.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
17#![forbid(unsafe_code)]
18#![allow(async_fn_in_trait)]
19
20//!
21//! A library for async code generation that imposes no ownership choices (can use borrowed or
22//! owned data) and is fully composable using generics and general-purpose structs.
23//!
24//! All syntax that can be generated is represented by a [`Writable`]. This is the core type of the
25//! library and it is quite simple. Any type that is supposed to be written to an output should
26//! implement this trait.
27//!
28//! Writable output is sent to an [`Output`]. Output consists of the direct I/O output as well as
29//! some context tied to it.
30//!
31
32use crate::context::{Context, ContextProvides, DynContext};
33
34/// Provides common and re-usable types, including types that are conceptual to this library and
35/// its type model.
36pub mod common;
37/// Using context for code generation requires importing this module
38pub mod context;
39/// Rust code syntax is available through this module.
40pub mod rust;
41
42/// A type that can be written to an output stream.
43///
44/// This struct is typically implemented by generic structs whenever their fields are also
45/// `Writable`. In this way, writable types depend on each other and re-use common syntax.
46pub trait Writable<O: Output> {
47    /// Writes to the output. Returns the output's error upon failure.
48    ///
49    /// A writable can always be written multiple times. Because of this, the function takes a
50    /// borrowed reference.
51    async fn write_to(&self, output: &mut O) -> Result<(), O::Error>;
52}
53
54impl<'w, W, O> Writable<O> for &'w W
55where
56    W: Writable<O>,
57    O: Output,
58{
59    async fn write_to(&self, output: &mut O) -> Result<(), O::Error> {
60        (**self).write_to(output).await
61    }
62}
63
64/// A sequence of writable types. Sequences are modeled in the library by this interface, so that
65/// different separators can implement [`SequenceAccept`].
66pub trait WritableSeq<O: Output> {
67    /// Writes each writable value in the sequence
68    async fn for_each<S>(&self, sink: &mut S) -> Result<(), O::Error>
69    where
70        S: SequenceAccept<O>;
71}
72
73impl<'w, W, O> WritableSeq<O> for &'w W
74where
75    O: Output,
76    W: WritableSeq<O>,
77{
78    async fn for_each<S>(&self, sink: &mut S) -> Result<(), O::Error>
79    where
80        S: SequenceAccept<O>,
81    {
82        (**self).for_each(sink).await
83    }
84}
85
86/// A collector for multiple writable values. This trait is the ingredient to [`WritableSeq`] that
87/// represents how the sequence is handled. For example, `accept` can be implemented by adding
88/// commas after each element but not the last, which can be obtained using `comma_separated`:
89/// ```
90/// use async_codegen::common::SeparatedSeqAccept;///
91/// use async_codegen::{Output, WritableSeq};
92///
93/// async fn write_comma_separated<O, Seq>(output: &mut O, seq: Seq) -> Result<(), O::Error> where O: Output, Seq: WritableSeq<O> {
94///   let mut comma_sep = SeparatedSeqAccept::comma_separated(output);
95///   seq.for_each(&mut comma_sep).await?;
96///   Ok(())
97/// }
98/// ```
99pub trait SequenceAccept<O: Output> {
100    /// Writes a single writable type to this sink.
101    async fn accept<W>(&mut self, writable: &W) -> Result<(), O::Error>
102    where
103        W: Writable<O>;
104}
105
106impl<'s, S, O> SequenceAccept<O> for &'s mut S
107where
108    O: Output,
109    S: SequenceAccept<O>,
110{
111    async fn accept<W>(&mut self, writable: &W) -> Result<(), O::Error>
112    where
113        W: Writable<O>,
114    {
115        (**self).accept(writable).await
116    }
117}
118
119/// Code generation output. This is a high-level trait intended to represent wherever you're
120/// writing to, with associated context. It can be split into that context in order to separate the
121/// I/O stream itself.
122pub trait Output {
123    /// The I/O stream type
124    type IO: IoOutput;
125    /// The context holder
126    type Ctx: Context;
127    /// The error type for write operations.
128    type Error: From<std::io::Error>;
129
130    /// Writes the given value to the output.
131    async fn write(&mut self, value: &str) -> Result<(), Self::Error>;
132
133    /// Splits into the context and the I/O stream, so that they can be used separately
134    fn split(&mut self) -> (&mut Self::IO, &Self::Ctx);
135
136    /// Gets all the context associated with this output
137    fn context(&self) -> &Self::Ctx;
138
139    /// Gets a particular context value
140    fn get_ctx<T>(&self) -> &T
141    where
142        Self::Ctx: ContextProvides<T>,
143    {
144        self.context().provide()
145    }
146}
147
148impl<'o, O> Output for &'o mut O
149where
150    O: Output,
151{
152    type IO = O::IO;
153    type Ctx = O::Ctx;
154    type Error = O::Error;
155
156    async fn write(&mut self, value: &str) -> Result<(), Self::Error> {
157        (**self).write(value).await
158    }
159
160    fn split(&mut self) -> (&mut Self::IO, &Self::Ctx) {
161        (**self).split()
162    }
163
164    fn context(&self) -> &Self::Ctx {
165        (**self).context()
166    }
167}
168
169/// An output that simply composes the I/O stream with a dynamic context
170pub struct SimpleOutput<I: IoOutput> {
171    pub io_output: I,
172    pub context: DynContext,
173}
174
175impl<I> Output for SimpleOutput<I>
176where
177    I: IoOutput,
178{
179    type IO = I;
180    type Ctx = DynContext;
181    type Error = std::io::Error;
182
183    async fn write(&mut self, value: &str) -> Result<(), Self::Error> {
184        self.io_output.write(value).await
185    }
186
187    fn split(&mut self) -> (&mut Self::IO, &Self::Ctx) {
188        (&mut self.io_output, &self.context)
189    }
190
191    fn context(&self) -> &Self::Ctx {
192        &self.context
193    }
194}
195
196/// The raw IO output.
197///
198/// This trait is not implemented by the library in a concrete way. Instead, depending on which
199/// async runtime you are using, you will have to implement it yourself.
200pub trait IoOutput {
201    /// Writes a string to the output. Yields an I/O error upon failure.
202    async fn write(&mut self, value: &str) -> Result<(), std::io::Error>;
203}
204
205impl<'o, O> IoOutput for &'o mut O
206where
207    O: IoOutput,
208{
209    async fn write(&mut self, value: &str) -> Result<(), std::io::Error> {
210        (**self).write(value).await
211    }
212}
213
214#[cfg(test)]
215mod tests {}