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}