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