Skip to main content

limen_codegen/
builder.rs

1//! Strongly typed, Language Server–friendly builder for Limen graphs
2//! (no proc-macro input and no stringly-typed DSL).
3//!
4//! Use this from your crate’s `build.rs` to construct a
5//! `crate::ast::GraphDef` with ordinary Rust types and expressions
6//! (typically authored via `syn::parse_quote!`). Then pass the finished AST to
7//! `expand_ast_to_file(..)` to emit the generated graph implementation.
8//!
9//! # Example
10//!
11//! ```rust,ignore
12//! use limen_codegen::builder::{GraphBuilder, GraphVisibility, Node, Edge};
13//! use limen_core::policy::{AdmissionPolicy, EdgePolicy, OverBudgetAction, QueueCaps};
14//!
15//! let edge_policy = EdgePolicy::new(
16//!     QueueCaps::new(8, 6, None, None),
17//!     AdmissionPolicy::DropNewest,
18//!     OverBudgetAction::Drop,
19//! );
20//!
21//! let ast = GraphBuilder::new("MyGraph", GraphVisibility::Public)
22//!     .node(
23//!         Node::new(0)
24//!             .ty::<my_crate::Source>()
25//!             .in_ports(0)
26//!             .out_ports(1)
27//!             .in_payload::<()>()
28//!             .out_payload::<u32>()
29//!             .name(Some("source"))
30//!             .ingress_policy(edge_policy),
31//!     )
32//!     .node(
33//!         Node::new(1)
34//!             .ty::<my_crate::MapU32>()
35//!             .in_ports(1)
36//!             .out_ports(1)
37//!             .in_payload::<u32>()
38//!             .out_payload::<u32>()
39//!             .name(Some("map")),
40//!     )
41//!     .edge(
42//!         Edge::new(0)
43//!             .ty::<limen_core::edge::spsc_array::StaticRing<8>>()
44//!             .payload::<u32>()
45//!             .manager_ty::<limen_core::memory::static_manager::StaticMemoryManager<u32, 8>>()
46//!             .from(0, 0)
47//!             .to(1, 0)
48//!             .policy(edge_policy)
49//!             .name(Some("source->map")),
50//!     )
51//!     .concurrent(false)
52//!     .finish();
53//!
54//! // writer.write("my_graph")?;
55//! ```
56
57use crate::ast;
58use limen_core::prelude::payload::Payload;
59use std::any::type_name;
60use std::path::Path;
61use std::path::PathBuf;
62use syn::parse_str;
63use syn::{Expr, Ident, Type, TypePath, Visibility};
64
65// Bring core types in so the builder can accept "real" NodeLink/EdgeLink values.
66use limen_core::edge::link::EdgeLink;
67use limen_core::node::link::NodeLink;
68use limen_core::policy::{AdmissionPolicy, EdgePolicy, OverBudgetAction};
69
70/// Visibility options for generated graph types used by `GraphBuilder::new`.
71#[derive(Debug, Clone, Copy)]
72pub enum GraphVisibility {
73    /// Creates graph with public visibility.
74    Public,
75    /// Creates graph with pub(crate) visibility.
76    Crate,
77    /// Creates graph with pub(super) visibility.
78    Super,
79    /// Creates graph with private visibility.
80    Private,
81}
82
83impl GraphVisibility {
84    fn as_syn_visibility(&self) -> Visibility {
85        match &self {
86            GraphVisibility::Public => parse_str::<Visibility>("pub").expect("parse pub"),
87            GraphVisibility::Crate => {
88                parse_str::<Visibility>("pub(crate)").expect("parse pub(crate)")
89            }
90            GraphVisibility::Super => {
91                parse_str::<Visibility>("pub(super)").expect("parse pub(super)")
92            }
93            GraphVisibility::Private => Visibility::Inherited,
94        }
95    }
96}
97
98/// Builder for a single Limen graph AST (`ast::GraphDef`).
99///
100/// `GraphBuilder` collects nodes and edges with precise typing, so you get full
101/// IDE completion and compiler checking while authoring graphs in `build.rs`.
102#[derive(Default)]
103pub struct GraphBuilder {
104    vis: Option<Visibility>,
105    name: Option<Ident>,
106    nodes: Vec<ast::NodeDef>,
107    edges: Vec<ast::EdgeDef>,
108    emit_concurrent: bool,
109}
110
111impl GraphBuilder {
112    /// Create a new graph builder.
113    ///
114    /// # Parameters
115    /// - `name`: graph type name as an identifier string (e.g. `"MyGraph"`).
116    /// - `vis`: a `GraphVisibility` enum controlling `pub` / `pub(crate)` / `pub(super)` / private.
117    ///
118    /// This convenience avoids calling `syn::parse_quote` in build.rs; we
119    /// synthesize a `syn::Visibility` internally.
120    pub fn new(name: &str, vis: GraphVisibility) -> Self {
121        let vis_syn = vis.as_syn_visibility();
122        let name_ident: Ident = parse_str(name).expect("invalid graph name");
123        Self {
124            vis: Some(vis_syn),
125            name: Some(name_ident),
126            nodes: Vec::new(),
127            edges: Vec::new(),
128            emit_concurrent: false,
129        }
130    }
131
132    /// Append a fully specified node to the graph.
133    ///
134    /// The `Node` parameter is a fluent sub-builder; it is converted to an
135    /// `ast::NodeDef` at call time.
136    ///
137    /// # Examples
138    ///
139    /// ```rust,ignore
140    /// use limen_codegen::builder::{GraphBuilder, GraphVisibility, Node};
141    ///
142    /// let gb = GraphBuilder::new("MyGraph", GraphVisibility::Public)
143    ///     .node(
144    ///         Node::new(0)
145    ///             .ty::<MySource>()
146    ///             .in_ports(0)
147    ///             .out_ports(1)
148    ///             .in_payload::<()>()
149    ///             .out_payload::<u32>()
150    ///     );
151    /// ```
152    pub fn node(mut self, n: Node) -> Self {
153        self.nodes.push(n.finish());
154        self
155    }
156
157    /// Append a Node described by a core `NodeLink` — the builder extracts
158    /// its type, payload types, id, and optional name and constructs an AST
159    /// `ast::NodeDef` entry.
160    ///
161    /// `ingress_policy` is optional; supply it for source nodes.
162    pub fn node_from_link<N, const IN: usize, const OUT: usize, InP, OutP>(
163        mut self,
164        link: NodeLink<N, IN, OUT, InP, OutP>,
165        ingress_policy: Option<EdgePolicy>,
166    ) -> Self
167    where
168        InP: Payload + 'static,
169        OutP: Payload + 'static,
170        N: limen_core::node::Node<IN, OUT, InP, OutP> + 'static,
171    {
172        // Extract numeric index
173        let idx = *link.id().as_usize();
174
175        // Type names for node and payload types
176        let node_ty = type_of_val_to_syn_type::<N>();
177        let in_p_ty = type_of_val_to_syn_type::<InP>();
178        let out_p_ty = type_of_val_to_syn_type::<OutP>();
179
180        // Optional name expression
181        let name_opt = link.name().map(|nm| name_to_expr(Some(nm)));
182
183        // Optional ingress policy expression (convert if supplied).
184        let ingress_expr = match ingress_policy {
185            Some(p) => {
186                let s = edge_policy_value_to_string(&p);
187                Some(parse_str::<Expr>(&s).expect("failed to parse EdgePolicy expr"))
188            }
189            None => None,
190        };
191
192        self.nodes.push(ast::NodeDef {
193            idx,
194            ty: match node_ty {
195                Type::Path(tp) => tp, // convert Type -> TypePath
196                _ => panic!("node type must be a path"),
197            },
198            in_ports: IN,
199            out_ports: OUT,
200            in_payload: in_p_ty,
201            out_payload: out_p_ty,
202            name_opt,
203            ingress_policy_opt: ingress_expr,
204        });
205        self
206    }
207
208    /// Append a fully specified edge to the graph.
209    ///
210    /// The `Edge` parameter is a fluent sub-builder; it is converted to an
211    /// `ast::EdgeDef` at call time.
212    ///
213    /// # Examples
214    ///
215    /// ```rust,ignore
216    /// use limen_codegen::builder::{GraphBuilder, GraphVisibility, Edge};
217    /// use limen_core::policy::{AdmissionPolicy, EdgePolicy, OverBudgetAction, QueueCaps};
218    ///
219    /// let policy = EdgePolicy::new(
220    ///     QueueCaps::new(8, 6, None, None),
221    ///     AdmissionPolicy::DropNewest,
222    ///     OverBudgetAction::Drop,
223    /// );
224    ///
225    /// let gb = GraphBuilder::new("MyGraph", GraphVisibility::Public)
226    ///     .edge(
227    ///         Edge::new(0)
228    ///             .ty::<limen_core::edge::spsc_array::StaticRing<8>>()
229    ///             .payload::<u32>()
230    ///             .manager_ty::<limen_core::memory::static_manager::StaticMemoryManager<u32, 8>>()
231    ///             .from(0, 0)
232    ///             .to(1, 0)
233    ///             .policy(policy)
234    ///             .name(Some("source->map"))
235    ///     );
236    /// ```
237    pub fn edge(mut self, e: Edge) -> Self {
238        self.edges.push(e.finish());
239        self
240    }
241
242    /// Append an Edge described by a core `EdgeLink`. The builder extracts the
243    /// queue type, endpoints, policy, and id and converts them into an
244    /// `ast::EdgeDef`.
245    ///
246    /// The payload type `P` and memory manager type `M` must be supplied
247    /// explicitly because `EdgeLink` does not carry them.
248    pub fn edge_from_link<Q, P, M>(mut self, link: EdgeLink<Q>) -> Self
249    where
250        P: Payload + 'static,
251        Q: limen_core::edge::Edge + 'static,
252        M: 'static,
253    {
254        let id = *link.id().as_usize();
255
256        // Queue type, payload type, and manager type
257        let q_ty = type_of_val_to_syn_type::<Q>();
258        let p_ty = type_of_val_to_syn_type::<P>();
259        let m_ty = type_of_val_to_syn_type::<M>();
260
261        // Endpoint indices
262        let up = link.upstream_port();
263        let dn = link.downstream_port();
264        let from_node = *up.node().as_usize();
265        let from_port = *up.port().as_usize();
266        let to_node = *dn.node().as_usize();
267        let to_port = *dn.port().as_usize();
268
269        // Policy expression
270        let policy_expr = {
271            let s = edge_policy_value_to_string(link.policy());
272            parse_str::<Expr>(&s).expect("failed to parse EdgePolicy")
273        };
274
275        let name_opt = link.name().map(|nm| name_to_expr(Some(nm)));
276
277        self.edges.push(ast::EdgeDef {
278            idx: id,
279            ty: match q_ty {
280                Type::Path(tp) => tp,
281                _ => panic!("edge queue type must be a path"),
282            },
283            payload: p_ty,
284            manager_ty: match m_ty {
285                Type::Path(tp) => tp,
286                _ => panic!("edge manager type must be a path"),
287            },
288            from_node,
289            from_port,
290            to_node,
291            to_port,
292            policy: policy_expr,
293            name_opt,
294        });
295
296        self
297    }
298
299    /// Control whether to emit the std-only scoped execution API
300    /// (`ScopedGraphApi`) for the generated graph.
301    ///
302    /// This does not change the graph structure. It only controls whether
303    /// codegen emits the extra `run_scoped(..)` implementation.
304    pub fn concurrent(mut self, emit_concurrent: bool) -> Self {
305        self.emit_concurrent = emit_concurrent;
306        self
307    }
308
309    /// Finalize and produce the `ast::GraphDef`.
310    ///
311    /// This consumes the builder and returns a complete graph AST suitable for
312    /// code generation.
313    ///
314    /// # Panics
315    ///
316    /// Panics if the builder was not created via [`GraphBuilder::new`], or if
317    /// `vis` or `name` were not set.
318    pub fn to_graph_def(self) -> ast::GraphDef {
319        ast::GraphDef {
320            vis: self.vis.expect("visibility required"),
321            name: self.name.expect("name required"),
322            emit_concurrent: self.emit_concurrent,
323            nodes: self.nodes,
324            edges: self.edges,
325        }
326    }
327
328    /// Finalize and produce a `GeneratedGraph` that owns the AST and can write it to file.
329    pub fn finish(self) -> GraphWriter {
330        GraphWriter {
331            g: ast::GraphDef {
332                vis: self.vis.expect("visibility required"),
333                name: self.name.expect("name required"),
334                emit_concurrent: self.emit_concurrent,
335                nodes: self.nodes,
336                edges: self.edges,
337            },
338        }
339    }
340}
341
342/// The result of `GraphBuilder::finish()` — owns the AST and can write it to disk.
343pub struct GraphWriter {
344    g: ast::GraphDef,
345}
346
347impl GraphWriter {
348    /// Consume `self` and write the generated code to `dest` using the
349    /// codegen emitter and formatter. Returns the path written.
350    pub fn write_to_path<P: AsRef<Path>>(self, dest: P) -> Result<PathBuf, crate::CodegenError> {
351        crate::expand_ast_to_file(self.g, dest)
352    }
353
354    /// Write graph to default path.
355    /// - `$OUT_DIR/generated/<filename>`.
356    ///
357    /// Returns an error if `OUT_DIR` is not set or the write fails.
358    pub fn write(self, filename: &str) -> Result<PathBuf, crate::CodegenError> {
359        let name_with_extension = {
360            let p = std::path::Path::new(filename);
361            if p.extension().is_none() {
362                // No extension -> append .rs to the input string as given.
363                format!("{}.rs", filename)
364            } else {
365                // Keep exactly what caller passed
366                filename.to_string()
367            }
368        };
369
370        // Read OUT_DIR (map VarError into CodegenError::Io)
371        let out_dir = std::env::var("OUT_DIR").map_err(|e| {
372            crate::CodegenError::Io(std::io::Error::new(
373                std::io::ErrorKind::NotFound,
374                format!("OUT_DIR environment variable not set: {}", e),
375            ))
376        })?;
377
378        let dest_dir = std::path::Path::new(&out_dir).join("generated");
379        std::fs::create_dir_all(&dest_dir).map_err(crate::CodegenError::Io)?;
380        let dest = dest_dir.join(name_with_extension);
381
382        // Use existing writer (consumes self)
383        crate::expand_ast_to_file(self.g, &dest)?;
384
385        // Cargo bookkeeping so build script is re-run when build.rs changes
386        // and give a visible message with the written path.
387        println!("cargo:rerun-if-changed=build.rs");
388        println!("cargo:warning=generated graph A -> {}", dest.display());
389
390        Ok(dest)
391    }
392}
393
394/// Fluent builder for a single node declaration.
395pub struct Node {
396    idx: usize,
397    ty: Option<TypePath>,
398    in_ports: Option<usize>,
399    out_ports: Option<usize>,
400    in_payload: Option<Type>,
401    out_payload: Option<Type>,
402    name_opt: Option<Expr>,
403    ingress_policy_opt: Option<Expr>,
404}
405
406impl Node {
407    /// Start a node builder for node index `idx`.
408    ///
409    /// The index is the stable node identifier used throughout the generated
410    /// graph. It must be unique within the graph.
411    ///
412    /// # Examples
413    ///
414    /// ```rust,ignore
415    /// use limen_codegen::builder::Node;
416    /// let n = Node::new(0);
417    /// ```
418    pub fn new(idx: usize) -> Self {
419        Self {
420            idx,
421            ty: None,
422            in_ports: None,
423            out_ports: None,
424            in_payload: None,
425            out_payload: None,
426            name_opt: None,
427            ingress_policy_opt: None,
428        }
429    }
430
431    /// Set the concrete node type using a real Rust type `T`.
432    /// This avoids calling `syn::parse_quote!` at the call site.
433    pub fn ty<T: 'static>(mut self) -> Self {
434        let t = type_of_val_to_syn_type::<T>();
435        match t {
436            Type::Path(tp) => {
437                // convert syn::Type::Path into TypePath
438                self.ty = Some(TypePath {
439                    qself: None,
440                    path: tp.path,
441                });
442            }
443            _ => panic!("ty_val: expected a path type for node type"),
444        }
445        self
446    }
447
448    /// Set the number of input ports for this node.
449    ///
450    /// If `in_ports == 0` and `out_ports > 0`, the node will be treated as a
451    /// **source** by codegen.
452    pub fn in_ports(mut self, n: usize) -> Self {
453        self.in_ports = Some(n);
454        self
455    }
456
457    /// Set the number of output ports for this node.
458    ///
459    /// If `out_ports == 0` and `in_ports > 0`, the node will be treated as a
460    /// **sink** by codegen.
461    pub fn out_ports(mut self, n: usize) -> Self {
462        self.out_ports = Some(n);
463        self
464    }
465
466    /// Set the input payload type using a Rust type `T`.
467    pub fn in_payload<T: 'static>(mut self) -> Self {
468        let t = type_of_val_to_syn_type::<T>();
469        self.in_payload = Some(t);
470        self
471    }
472
473    /// Set the output payload type using a Rust type `T`.
474    pub fn out_payload<T: 'static>(mut self) -> Self {
475        let t = type_of_val_to_syn_type::<T>();
476        self.out_payload = Some(t);
477        self
478    }
479
480    /// Set the optional node name from a Rust `&'static str` (or `None`).
481    pub fn name(mut self, s: Option<&'static str>) -> Self {
482        self.name_opt = Some(name_to_expr(s));
483        self
484    }
485
486    /// Supply a concrete `EdgePolicy` value for ingress policy (converted internally).
487    pub fn ingress_policy(mut self, p: EdgePolicy) -> Self {
488        let s = edge_policy_value_to_string(&p);
489        self.ingress_policy_opt = Some(parse_str::<Expr>(&s).expect("parse EdgePolicy"));
490        self
491    }
492
493    /// Finalise the node and produce the Nodedef.
494    fn finish(self) -> ast::NodeDef {
495        ast::NodeDef {
496            idx: self.idx,
497            ty: self.ty.expect("node.ty"),
498            in_ports: self.in_ports.expect("node.in_ports"),
499            out_ports: self.out_ports.expect("node.out_ports"),
500            in_payload: self.in_payload.expect("node.in_payload"),
501            out_payload: self.out_payload.expect("node.out_payload"),
502            name_opt: self.name_opt,
503            ingress_policy_opt: self.ingress_policy_opt,
504        }
505    }
506}
507
508/// Fluent builder for a single edge declaration.
509pub struct Edge {
510    idx: usize,
511    ty: Option<TypePath>,
512    payload: Option<Type>,
513    manager_ty: Option<TypePath>,
514    from_node: Option<usize>,
515    from_port: Option<usize>,
516    to_node: Option<usize>,
517    to_port: Option<usize>,
518    policy: Option<Expr>,
519    name_opt: Option<Expr>,
520}
521
522impl Edge {
523    /// Start an edge builder for edge index `idx`.
524    ///
525    /// The index is the stable edge identifier used in descriptors. It must be
526    /// unique among *real* (non-ingress) edges.
527    pub fn new(idx: usize) -> Self {
528        Self {
529            idx,
530            ty: None,
531            payload: None,
532            manager_ty: None,
533            from_node: None,
534            from_port: None,
535            to_node: None,
536            to_port: None,
537            policy: None,
538            name_opt: None,
539        }
540    }
541
542    /// Set the edge queue type using a real Rust type `T`.
543    pub fn ty<T: 'static>(mut self) -> Self {
544        let t = type_of_val_to_syn_type::<T>();
545        match t {
546            Type::Path(tp) => {
547                self.ty = Some(TypePath {
548                    qself: None,
549                    path: tp.path,
550                });
551            }
552            _ => panic!("ty_val: expected a path type for edge queue"),
553        }
554        self
555    }
556
557    /// Set the edge payload type using a Rust type `T`.
558    pub fn payload<T: 'static>(mut self) -> Self {
559        let t = type_of_val_to_syn_type::<T>();
560        self.payload = Some(t);
561        self
562    }
563
564    /// Set the memory manager type for this edge using a real Rust type `M`.
565    ///
566    /// Every edge must specify a manager type. For `no_std` graphs, use
567    /// `StaticMemoryManager<P, DEPTH>` where `DEPTH` matches the queue's
568    /// `max_items`. For `std` concurrent graphs, use `ConcurrentMemoryManager<P>`.
569    pub fn manager_ty<M: 'static>(mut self) -> Self {
570        let t = type_of_val_to_syn_type::<M>();
571        match t {
572            Type::Path(tp) => {
573                self.manager_ty = Some(TypePath {
574                    qself: None,
575                    path: tp.path,
576                });
577            }
578            _ => panic!("manager_ty: expected a path type for memory manager"),
579        }
580        self
581    }
582
583    /// Set the upstream `(node_index, port_index)` for this edge.
584    pub fn from(mut self, node: usize, port: usize) -> Self {
585        self.from_node = Some(node);
586        self.from_port = Some(port);
587        self
588    }
589
590    /// Set the downstream `(node_index, port_index)` for this edge.
591    pub fn to(mut self, node: usize, port: usize) -> Self {
592        self.to_node = Some(node);
593        self.to_port = Some(port);
594        self
595    }
596
597    /// Supply a concrete `EdgePolicy` value (converted internally).
598    pub fn policy(mut self, p: EdgePolicy) -> Self {
599        let s = edge_policy_value_to_string(&p);
600        self.policy = Some(parse_str::<Expr>(&s).expect("parse EdgePolicy"));
601        self
602    }
603
604    /// Set the optional edge name using a Rust `&'static str` (or `None`).
605    pub fn name(mut self, s: Option<&'static str>) -> Self {
606        self.name_opt = Some(name_to_expr(s));
607        self
608    }
609
610    /// Finalise the edge and produce the Edgedef.
611    fn finish(self) -> ast::EdgeDef {
612        ast::EdgeDef {
613            idx: self.idx,
614            ty: self.ty.expect("edge.ty"),
615            payload: self.payload.expect("edge.payload"),
616            manager_ty: self.manager_ty.expect("edge.manager_ty"),
617            from_node: self.from_node.expect("edge.from.node"),
618            from_port: self.from_port.expect("edge.from.port"),
619            to_node: self.to_node.expect("edge.to.node"),
620            to_port: self.to_port.expect("edge.to.port"),
621            policy: self.policy.expect("edge.policy"),
622            name_opt: self.name_opt,
623        }
624    }
625}
626
627/// Turn an `Option<&'static str>` name into a `syn::Expr` (Some("...") or None).
628fn name_to_expr(n: Option<&'static str>) -> Expr {
629    match n {
630        Some(s) => parse_str::<Expr>(&format!("Some({:?})", s)).expect("parse name"),
631        None => parse_str::<Expr>("None").expect("parse None"),
632    }
633}
634
635/// Convert a Rust generic type `T` into a `syn::Type` by using `type_name::<T>()`.
636/// This is the mechanism that lets the builder accept real Rust types without
637/// the caller having to `syn::parse_quote` them.
638fn type_of_val_to_syn_type<T: 'static>() -> Type {
639    let s = type_name::<T>();
640    parse_str::<Type>(s).unwrap_or_else(|e| panic!("failed to parse type `{}`: {}", s, e))
641}
642
643/// Convert a runtime `EdgePolicy` value into a fully-qualified Rust expression
644/// string that constructs an equivalent `EdgePolicy` using public constructors.
645/// We use the public getters you added (caps(), admission(), over_budget()).
646fn edge_policy_value_to_string(p: &EdgePolicy) -> String {
647    // Queue caps
648    let caps = p.caps(); // assume returns Copy/Clone or by value
649    let max_items = caps.max_items();
650    let soft_items = caps.soft_items();
651    let max_bytes = match caps.max_bytes() {
652        Some(b) => format!("Some({})", b),
653        None => "None".to_string(),
654    };
655    let soft_bytes = match caps.soft_bytes() {
656        Some(b) => format!("Some({})", b),
657        None => "None".to_string(),
658    };
659
660    // admission
661    let admission_str = match p.admission() {
662        AdmissionPolicy::DropNewest => "limen_core::policy::AdmissionPolicy::DropNewest",
663        AdmissionPolicy::DropOldest => "limen_core::policy::AdmissionPolicy::DropOldest",
664        AdmissionPolicy::Block => "limen_core::policy::AdmissionPolicy::Block",
665        AdmissionPolicy::DeadlineAndQoSAware => {
666            "limen_core::policy::AdmissionPolicy::DeadlineAndQoSAware"
667        }
668        _ => panic!("Unsupported AdmissionPolicy {:?}", p.admission()),
669    };
670
671    let over_str = match p.over_budget() {
672        OverBudgetAction::Drop => "limen_core::policy::OverBudgetAction::Drop",
673        OverBudgetAction::SkipStage => "limen_core::policy::OverBudgetAction::SkipStage",
674        OverBudgetAction::Degrade => "limen_core::policy::OverBudgetAction::Degrade",
675        OverBudgetAction::DefaultOnTimeout => {
676            "limen_core::policy::OverBudgetAction::DefaultOnTimeout"
677        }
678        _ => panic!("Unsupported OverBudgetAction {:?}", p.over_budget()),
679    };
680
681    format!(
682        "limen_core::policy::EdgePolicy::new(limen_core::policy::QueueCaps::new({}, {}, {}, {}), {}, {})",
683        max_items, soft_items, max_bytes, soft_bytes, admission_str, over_str
684    )
685}