Skip to main content

synta_codegen/
lib.rs

1//! ASN.1 schema parser and Rust code generator
2//!
3//! This library parses ASN.1 module definitions and generates Rust code
4//! using the `synta` library's derive macros.
5//!
6//! # Quick start
7//!
8//! ```no_run
9//! use synta_codegen::{parse, generate};
10//!
11//! let schema = r#"
12//!     Certificate DEFINITIONS ::= BEGIN
13//!         Certificate ::= SEQUENCE {
14//!             version INTEGER,
15//!             serialNumber INTEGER
16//!         }
17//!     END
18//! "#;
19//!
20//! let module = parse(schema).unwrap();
21//! let rust_code = generate(&module).unwrap();
22//! println!("{}", rust_code);
23//! ```
24//!
25//! # Configuration
26//!
27//! [`generate_with_config`] accepts a [`CodeGenConfig`] that controls several
28//! aspects of the emitted code.
29//!
30//! ## Owned vs. borrowed string types
31//!
32//! By default all ASN.1 string and binary types (`OCTET STRING`, `BIT STRING`,
33//! `UTF8String`, `PrintableString`, `IA5String`) are generated as **owned**
34//! heap-allocating types (`OctetString`, `BitString`, …).  This is convenient
35//! when constructing structs programmatically.
36//!
37//! For parse-only workloads (e.g. X.509 certificate inspection) you can switch
38//! to **zero-copy borrowed** types (`OctetStringRef<'a>`, `BitStringRef<'a>`,
39//! …) that borrow directly from the input buffer.  Structs that contain these
40//! fields automatically gain a `'a` lifetime parameter.
41//!
42//! ```no_run
43//! use synta_codegen::{parse, generate_with_config, CodeGenConfig, StringTypeMode};
44//!
45//! let schema = r#"
46//!     Msg DEFINITIONS ::= BEGIN
47//!         Msg ::= SEQUENCE {
48//!             payload OCTET STRING,
49//!             label   UTF8String
50//!         }
51//!     END
52//! "#;
53//!
54//! let module = parse(schema).unwrap();
55//! let config = CodeGenConfig {
56//!     string_type_mode: StringTypeMode::Borrowed,
57//!     ..Default::default()
58//! };
59//! // Emits:
60//! //   pub struct Msg<'a> {
61//! //       pub payload: OctetStringRef<'a>,
62//! //       pub label: Utf8StringRef<'a>,
63//! //   }
64//! let rust_code = generate_with_config(&module, config).unwrap();
65//! println!("{}", rust_code);
66//! ```
67//!
68//! String types that have no zero-copy variant (`TeletexString`, `BmpString`,
69//! `UniversalString`, `GeneralString`, `NumericString`, `VisibleString`) are
70//! always emitted as owned types regardless of [`StringTypeMode`].
71//!
72//! Named bit strings (`BIT STRING { flag(0), … }`) are always emitted as
73//! owned `BitString` because they are decoded into a concrete bit-field type.
74//!
75//! ## Derive macro gating
76//!
77//! By default every `Asn1Sequence` / `Asn1Set` / `Asn1Choice` derive and its
78//! associated `asn1(…)` helper attributes are wrapped in
79//! `#[cfg_attr(feature = "derive", …)]`.  This lets the consuming crate make
80//! `synta-derive` an **optional** dependency controlled by a Cargo feature:
81//!
82//! ```toml
83//! # Cargo.toml of the consuming crate (default behaviour)
84//! [dependencies]
85//! synta-derive = { version = "0.1", optional = true }
86//!
87//! [features]
88//! derive = ["dep:synta-derive"]
89//! ```
90//!
91//! Third-party crates that **always** depend on `synta-derive` and do not want
92//! to expose a `derive` Cargo feature can use [`DeriveMode::Always`]:
93//!
94//! ```no_run
95//! use synta_codegen::{parse, generate_with_config, CodeGenConfig, DeriveMode};
96//!
97//! let schema = r#"
98//!     Msg DEFINITIONS ::= BEGIN
99//!         Msg ::= SEQUENCE { id INTEGER }
100//!     END
101//! "#;
102//!
103//! let module = parse(schema).unwrap();
104//! let config = CodeGenConfig {
105//!     derive_mode: DeriveMode::Always,
106//!     ..Default::default()
107//! };
108//! // Emits:
109//! //   #[derive(Debug, Clone, PartialEq)]
110//! //   #[derive(Asn1Sequence)]          ← no cfg_attr wrapper
111//! //   pub struct Msg { pub id: Integer }
112//! let rust_code = generate_with_config(&module, config).unwrap();
113//! println!("{}", rust_code);
114//! ```
115//!
116//! If the crate uses a feature name other than `"derive"`, pass it via
117//! [`DeriveMode::Custom`]:
118//!
119//! ```no_run
120//! use synta_codegen::{parse, generate_with_config, CodeGenConfig, DeriveMode};
121//!
122//! # let schema = "Msg DEFINITIONS ::= BEGIN Msg ::= SEQUENCE { id INTEGER } END";
123//! # let module = parse(schema).unwrap();
124//! let config = CodeGenConfig {
125//!     derive_mode: DeriveMode::Custom("asn1-derive".to_string()),
126//!     ..Default::default()
127//! };
128//! // Emits:
129//! //   #[cfg_attr(feature = "asn1-derive", derive(Asn1Sequence))]
130//! let rust_code = generate_with_config(&module, config).unwrap();
131//! println!("{}", rust_code);
132//! ```
133//!
134//! ## Import path prefix
135//!
136//! Use [`CodeGenConfig::with_crate_imports`], [`CodeGenConfig::with_super_imports`],
137//! or [`CodeGenConfig::with_custom_prefix`] to emit `use` statements instead of
138//! the default comment-only import annotations.
139//!
140//! ## Constrained INTEGER type selection
141//!
142//! When a top-level `INTEGER` type carries a value-range constraint (e.g.
143//! `INTEGER (0..100)`), synta-codegen generates a newtype whose inner field is
144//! the **smallest native Rust integer primitive** that covers the declared range,
145//! rather than the arbitrary-precision `Integer` type:
146//!
147//! - Lower bound ≥ 0 → unsigned: `u8` (≤255), `u16` (≤65535), `u32` (≤4294967295), `u64`.
148//! - Lower bound < 0 → signed: `i8`, `i16`, `i32`, `i64`.
149//! - Unconstrained bounds (`MIN`/`MAX`, named values) → `i64`.
150//!
151//! Using a primitive type means the generated struct automatically derives
152//! `Copy`, `PartialOrd`, and `Ord`, and avoids heap allocation.  For example,
153//! `Percentage ::= INTEGER (0..100)` produces:
154//!
155//! ```text
156//! #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
157//! pub struct Percentage(u8);
158//!
159//! impl Percentage {
160//!     pub fn new(value: u8) -> Result<Self, &'static str> { ... }
161//!     pub const fn new_unchecked(value: u8) -> Self { Percentage(value) }
162//!     pub const fn get(&self) -> u8 { self.0 }
163//!     pub fn into_inner(self) -> u8 { self.0 }
164//! }
165//! ```
166//!
167//! The equivalent C generation uses `uint8_t` / `uint16_t` / `uint32_t` /
168//! `uint64_t` for non-negative ranges and `int8_t` / `int16_t` / `int32_t` /
169//! `int64_t` for signed ranges.
170
171pub mod ast;
172pub mod c_cmake_codegen;
173pub mod c_codegen;
174pub mod c_impl_codegen;
175pub mod c_meson_codegen;
176pub mod codegen;
177pub mod import_graph;
178pub mod naming;
179pub mod parser;
180
181pub use ast::{Definition, Module, Type};
182pub use c_cmake_codegen::{generate_cmake, CMakeConfig};
183pub use c_codegen::{generate_c, generate_c_with_config, CCodeGenConfig};
184pub use c_impl_codegen::{generate_c_impl, CImplConfig, PatternMode};
185pub use c_meson_codegen::{generate_meson, MesonConfig};
186pub use codegen::{generate, generate_with_config, CodeGenConfig, DeriveMode, StringTypeMode};
187pub use import_graph::{detect_cycles, topological_order, ImportCycle};
188pub use naming::module_file_stem;
189pub use parser::{parse, ParseError};
190
191/// Parse ASN.1 schema and generate Rust code in one step
192pub fn parse_and_generate(input: &str) -> Result<String, Box<dyn std::error::Error>> {
193    let module = parse(input)?;
194    let code = generate(&module)?;
195    Ok(code)
196}
197
198/// Parse ASN.1 schema and generate C header in one step
199pub fn parse_and_generate_c(input: &str) -> Result<String, Box<dyn std::error::Error>> {
200    let module = parse(input)?;
201    let code = generate_c(&module)?;
202    Ok(code)
203}
204
205/// Parse ASN.1 schema and generate C implementation in one step
206pub fn parse_and_generate_c_impl(
207    input: &str,
208    header_file: &str,
209) -> Result<String, Box<dyn std::error::Error>> {
210    let module = parse(input)?;
211    let config = CImplConfig {
212        header_file: header_file.to_string(),
213        arena_mode: false,
214        pattern_mode: Default::default(),
215        with_containing: false,
216    };
217    let code = generate_c_impl(&module, config)?;
218    Ok(code)
219}