prototext_core/lib.rs
1// SPDX-FileCopyrightText: 2025 - 2026 Frederic Ruget <fred@atlant.is> <fred@s3ns.io> (GitHub: @douzebis)
2// SPDX-FileCopyrightText: 2025 - 2026 Thales Cloud Sécurisé
3//
4// SPDX-License-Identifier: MIT
5
6pub mod decoder;
7pub mod helpers;
8pub mod schema;
9pub mod serialize;
10
11pub use schema::{ParsedSchema, SchemaError};
12
13// ── Public API types ──────────────────────────────────────────────────────────
14
15/// Options controlling how a protobuf binary payload is rendered as text.
16#[derive(Debug, Clone)]
17pub struct RenderOpts {
18 /// When `true`, always treat the input as raw protobuf binary.
19 /// When `false`, auto-detect: if the payload already carries the
20 /// `#@ prototext:` header it is returned unchanged (zero-copy fast path).
21 pub assume_binary: bool,
22 /// Emit inline comments with schema field names and types.
23 pub include_annotations: bool,
24 /// Indentation step in spaces.
25 pub indent: usize,
26}
27
28impl Default for RenderOpts {
29 fn default() -> Self {
30 RenderOpts {
31 assume_binary: false,
32 include_annotations: false,
33 indent: 1,
34 }
35 }
36}
37
38/// Errors that can occur while decoding or encoding a protobuf payload.
39#[non_exhaustive]
40#[derive(Debug)]
41pub enum CodecError {
42 /// The input bytes could not be decoded as a protobuf wire payload.
43 DecodeFailed(String),
44 /// The input bytes could not be decoded as a textual prototext payload.
45 TextDecodeFailed(String),
46}
47
48impl std::fmt::Display for CodecError {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 CodecError::DecodeFailed(msg) => write!(f, "decode failed: {msg}"),
52 CodecError::TextDecodeFailed(msg) => write!(f, "text decode failed: {msg}"),
53 }
54 }
55}
56
57impl std::error::Error for CodecError {}
58
59// ── Public API functions ──────────────────────────────────────────────────────
60
61/// Decode a raw protobuf binary payload and render it as protoc-style text.
62///
63/// When `opts.assume_binary` is `false` and the data already carries the
64/// `#@ prototext:` header, the bytes are returned unchanged (zero-copy fast
65/// path).
66pub fn render_as_text(
67 data: &[u8],
68 schema: Option<&ParsedSchema>,
69 opts: RenderOpts,
70) -> Result<Vec<u8>, CodecError> {
71 if !opts.assume_binary && serialize::render_text::is_prototext_text(data) {
72 return Ok(data.to_vec());
73 }
74 Ok(serialize::render_text::decode_and_render(
75 data,
76 schema,
77 opts.include_annotations,
78 opts.indent,
79 ))
80}
81
82/// Encode a textual prototext payload back to raw protobuf binary wire bytes.
83///
84/// The input must carry the `#@ prototext:` header (i.e. be produced by
85/// `render_as_text`). Raw binary input is passed through unchanged.
86pub fn render_as_bytes(data: &[u8], opts: RenderOpts) -> Result<Vec<u8>, CodecError> {
87 if opts.assume_binary || !serialize::render_text::is_prototext_text(data) {
88 Ok(data.to_vec())
89 } else {
90 Ok(serialize::encode_text::encode_text_to_binary(data))
91 }
92}
93
94/// Parse a compiled `.pb` descriptor into a `ParsedSchema`.
95///
96/// Re-exported from `schema` for convenience so callers only need to import
97/// from the crate root.
98pub fn parse_schema(schema_bytes: &[u8], root_message: &str) -> Result<ParsedSchema, SchemaError> {
99 schema::parse_schema(schema_bytes, root_message)
100}