Skip to main content

onnx_std/textproto/
mod.rs

1//! Descriptor-driven ONNX protobuf TextFormat (`.onnxtxt` / `.pbtxt`).
2//!
3//! Parsing and printing use the same generated descriptor as JSON, eliminating
4//! per-codec field allowlists and covering the complete bound ONNX schema.
5
6use prost_reflect::{DynamicMessage, text_format::FormatOptions};
7
8use crate::{Error, Model, Result};
9
10/// Serialize a model as canonical, pretty protobuf TextFormat.
11pub fn to_textproto(model: &Model) -> Result<String> {
12    let dynamic = crate::proto_serde::to_dynamic(&model.to_proto()?)
13        .map_err(|error| textproto_error(error.to_string()))?;
14    Ok(dynamic.to_text_format_with_options(&FormatOptions::new().pretty(true)))
15}
16
17/// Parse protobuf TextFormat into an ONNX model.
18pub fn from_textproto(source: &str) -> Result<Model> {
19    let dynamic = DynamicMessage::parse_text_format(crate::proto_serde::descriptor(), source)
20        .map_err(|error| textproto_error(error.to_string()))?;
21    let proto = crate::proto_serde::from_dynamic(&dynamic)
22        .map_err(|error| textproto_error(error.to_string()))?;
23    Model::from_proto(proto)
24}
25
26/// Convert a protobuf TextFormat (`.textproto`) document into the binary
27/// protobuf wire encoding of its `ModelProto`.
28///
29/// This is the conversion used by runtime loaders to accept git-friendly
30/// textproto fixtures: parse the text and re-encode as the exact binary bytes a
31/// runtime's binary-decode path already expects. It is deliberately
32/// *lightweight* — it does not build the runtime graph or run shape inference
33/// (unlike [`from_textproto`]), so it faithfully reproduces the model bytes for
34/// a downstream runtime (e.g. ONNX Runtime) to load and validate itself.
35///
36/// Because the returned buffer carries no model-directory context, textproto
37/// documents must inline all weights (no external `.onnx.data`).
38pub fn to_binary(source: &str) -> Result<Vec<u8>> {
39    use prost::Message;
40    let dynamic = DynamicMessage::parse_text_format(crate::proto_serde::descriptor(), source)
41        .map_err(|error| textproto_error(error.to_string()))?;
42    Ok(dynamic.encode_to_vec())
43}
44
45fn textproto_error(message: impl Into<String>) -> Error {
46    Error::TextProto(message.into())
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn parses_comments_and_alternate_delimiters() {
55        let source = r#"
56            # protobuf comment
57            ir_version: 10
58            opset_import < domain: "" version: 21 >
59            graph {
60              name: "main"
61              input {
62                name: "X"
63                type { tensor_type { elem_type: 1 shape { dim { dim_value: 2 } } } }
64              }
65              output {
66                name: "Y"
67                type { tensor_type { elem_type: 1 shape { dim { dim_value: 2 } } } }
68              }
69              node { input: "X" output: "Y" op_type: "Identity" }
70            }
71        "#;
72        let model = from_textproto(source).unwrap();
73        assert_eq!(model.graph.num_nodes(), 1);
74        let printed = to_textproto(&model).unwrap();
75        assert!(printed.contains("elem_type: 1"));
76        assert!(printed.contains("op_type: \"Identity\""));
77    }
78}