Skip to main content

oxiproto_build/
lib.rs

1#![forbid(unsafe_code)]
2
3//! `oxiproto-build` — Pure Rust `.proto` → Rust codegen, no `protoc` required.
4//!
5//! Downstream crates add this as a `[build-dependencies]` entry and call
6//! [`compile_protos`] (or [`Builder`]) from their `build.rs`.
7//!
8//! # Quick start
9//!
10//! ```no_run
11//! fn main() -> Result<(), Box<dyn std::error::Error>> {
12//!     oxiproto_build::compile_protos(&["proto/service.proto"], &["proto/"])?;
13//!     Ok(())
14//! }
15//! ```
16
17pub mod builder;
18pub mod compile_str;
19pub mod error;
20/// Native `.proto` parser (Slice P fills this in).
21pub mod parser;
22
23pub use builder::Builder;
24pub use compile_str::compile_str as compile_str_fn;
25pub use error::BuildError;
26
27/// Re-export `compile_str` under its canonical name for test compatibility.
28pub use compile_str::compile_str;
29
30use std::path::Path;
31
32/// Compile `.proto` files to Rust without requiring `protoc` on `PATH`.
33///
34/// Generated `.rs` files are written to `OUT_DIR` (set by Cargo in
35/// `build.rs` context).
36///
37/// # Arguments
38///
39/// * `protos`   — Paths to the `.proto` source files to compile.
40/// * `includes` — Include directories used to resolve imports inside the
41///   `.proto` files.
42///
43/// # Errors
44///
45/// Returns [`oxiproto_core::OxiProtoError::ParseError`] if `protox` cannot
46/// parse or resolve the proto sources, or
47/// [`oxiproto_core::OxiProtoError::CodegenError`] if `prost-build` cannot
48/// emit Rust from the resolved descriptors.
49///
50/// # Example (in `build.rs`)
51///
52/// ```no_run
53/// fn main() -> Result<(), Box<dyn std::error::Error>> {
54///     oxiproto_build::compile_protos(&["proto/service.proto"], &["proto/"])?;
55///     Ok(())
56/// }
57/// ```
58pub fn compile_protos(
59    protos: &[impl AsRef<Path>],
60    includes: &[impl AsRef<Path>],
61) -> Result<(), oxiproto_core::OxiProtoError> {
62    Builder::new()
63        .compile(protos, includes)
64        .map_err(oxiproto_core::OxiProtoError::from)
65}
66
67/// Parse `.proto` files to a [`prost_types::FileDescriptorSet`] without
68/// invoking `protoc` or writing any files.
69///
70/// This is the low-level building block used by [`compile_protos`] and by the
71/// `oxiproto-cli` binary.  It delegates to [`protox::compile`] (pure Rust).
72///
73/// # Errors
74///
75/// Returns [`oxiproto_core::OxiProtoError::ParseError`] if `protox` cannot
76/// parse or resolve the proto sources.
77///
78/// # Example
79///
80/// ```no_run
81/// let fds = oxiproto_build::compile_to_fds(
82///     &["proto/hello.proto"],
83///     &["proto/"],
84/// )?;
85/// println!("{} file(s) in FDS", fds.file.len());
86/// # Ok::<(), oxiproto_core::OxiProtoError>(())
87/// ```
88pub fn compile_to_fds(
89    protos: &[impl AsRef<Path>],
90    includes: &[impl AsRef<Path>],
91) -> Result<prost_types::FileDescriptorSet, oxiproto_core::OxiProtoError> {
92    Builder::new()
93        .compile_to_fds(protos, includes)
94        .map_err(oxiproto_core::OxiProtoError::from)
95}
96
97/// Compile a proto3 source string to a [`prost_types::FileDescriptorSet`]
98/// using the native pure-Rust parser (no `protox` dependency).
99///
100/// Requires the `native-parser` feature.
101///
102/// This function handles only single-file protos with no imports. For
103/// multi-file protos use [`compile_files_native`].
104///
105/// # Errors
106///
107/// Returns [`BuildError`] on parse, resolution, or I/O failure.
108#[cfg(feature = "native-parser")]
109pub fn compile_str_native(
110    proto_source: &str,
111) -> Result<prost_types::FileDescriptorSet, BuildError> {
112    use crate::parser::{build_file_descriptor_set, parse_file, resolve};
113
114    let proto_file = parse_file(proto_source).map_err(|e| BuildError::Parse {
115        file: "<inline>.proto".to_owned(),
116        line: 0,
117        col: 0,
118        message: e.to_string(),
119    })?;
120    // Single-file mode: reject any imports
121    if !proto_file.imports.is_empty() {
122        return Err(BuildError::Parse {
123            file: "<inline>.proto".to_owned(),
124            line: 0,
125            col: 0,
126            message:
127                "imports are not supported in compile_str_native; use compile_files_native instead"
128                    .to_owned(),
129        });
130    }
131    let resolved = resolve(&proto_file).map_err(|e| BuildError::Parse {
132        file: "<inline>.proto".to_owned(),
133        line: 0,
134        col: 0,
135        message: e.to_string(),
136    })?;
137    Ok(build_file_descriptor_set(
138        &resolved,
139        "<inline>.proto",
140        proto_source,
141    ))
142}
143
144/// Compile a set of `.proto` files to a [`prost_types::FileDescriptorSet`]
145/// using the native parser.
146///
147/// Include directories are searched in order to resolve imports.
148/// Well-known types (google/protobuf/*.proto) are loaded from the bundled pool.
149///
150/// # Errors
151///
152/// Returns [`BuildError`] if any file cannot be found, parsed, or type-resolved.
153#[cfg(feature = "native-parser")]
154pub fn compile_files_native(
155    protos: &[impl AsRef<std::path::Path>],
156    includes: &[impl AsRef<std::path::Path>],
157) -> Result<prost_types::FileDescriptorSet, BuildError> {
158    let inc: Vec<std::path::PathBuf> = includes.iter().map(|p| p.as_ref().to_path_buf()).collect();
159    parser::loader::compile_files(protos, &inc)
160}