mod backend;
mod codegen;
mod message;
mod naming;
mod rules;
use std::fs;
use std::path::{Path, PathBuf};
use prost_reflect::DescriptorPool;
pub use backend::Backend;
#[derive(Default)]
pub struct Builder {
file_descriptor_set_bytes: Option<Vec<u8>>,
out_dir: Option<PathBuf>,
extern_paths: Vec<(String, String)>,
backend: Backend,
fail_on_runtime_only: bool,
runtime_bridge: bool,
}
impl Builder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn file_descriptor_set_bytes(mut self, bytes: Vec<u8>) -> Self {
self.file_descriptor_set_bytes = Some(bytes);
self
}
pub fn file_descriptor_set_path(mut self, path: impl AsRef<Path>) -> Result<Self, Error> {
let bytes = fs::read(path.as_ref()).map_err(|e| Error::Io {
path: path.as_ref().to_path_buf(),
source: e,
})?;
self.file_descriptor_set_bytes = Some(bytes);
Ok(self)
}
#[must_use]
pub fn out_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.out_dir = Some(path.into());
self
}
#[must_use]
pub fn extern_path(
mut self,
proto_path: impl Into<String>,
rust_path: impl Into<String>,
) -> Self {
self.extern_paths
.push((proto_path.into(), rust_path.into()));
self
}
#[must_use]
pub fn backend(mut self, backend: Backend) -> Self {
self.backend = backend;
self
}
#[must_use]
pub fn fail_on_runtime_only(mut self, fail: bool) -> Self {
self.fail_on_runtime_only = fail;
self
}
#[must_use]
pub fn runtime_bridge(mut self, enable: bool) -> Self {
self.runtime_bridge = enable;
self
}
pub fn compile(self) -> Result<(), Error> {
if self.runtime_bridge && self.fail_on_runtime_only {
return Err(Error::ConflictingOptions(
"runtime_bridge and fail_on_runtime_only are mutually exclusive".to_string(),
));
}
if self.runtime_bridge && self.backend != Backend::Buffa {
return Err(Error::ConflictingOptions(
"runtime_bridge is only supported with Backend::Buffa".to_string(),
));
}
let fds_bytes = self
.file_descriptor_set_bytes
.ok_or(Error::MissingDescriptorSet)?;
let pool = DescriptorPool::decode(fds_bytes.as_slice())
.map_err(|e| Error::Decode(e.to_string()))?;
let out_dir = match self.out_dir {
Some(dir) => dir,
None => PathBuf::from(std::env::var("OUT_DIR").map_err(|_| Error::MissingOutDir)?),
};
let naming_ctx = naming::NamingContext::new(&self.extern_paths, self.backend, &pool);
let generated = codegen::generate(
&pool,
&naming_ctx,
self.fail_on_runtime_only,
self.runtime_bridge,
)?;
let file = syn::parse2(generated.tokens).map_err(|e| Error::Codegen(e.to_string()))?;
let formatted = prettyplease::unparse(&file);
fs::create_dir_all(&out_dir).map_err(|e| Error::Io {
path: out_dir.clone(),
source: e,
})?;
let output_path = out_dir.join("validate_impl.rs");
fs::write(&output_path, formatted).map_err(|e| Error::Io {
path: output_path,
source: e,
})?;
if generated.needs_fds {
let fds_path = out_dir.join("prost_protovalidate_validate.fds");
fs::write(&fds_path, &fds_bytes).map_err(|e| Error::Io {
path: fds_path,
source: e,
})?;
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(
"no file descriptor set provided; call file_descriptor_set_bytes() or file_descriptor_set_path()"
)]
MissingDescriptorSet,
#[error("OUT_DIR environment variable not set; call out_dir() or run from build.rs")]
MissingOutDir,
#[error("failed to decode file descriptor set: {0}")]
Decode(String),
#[error("code generation error: {0}")]
Codegen(String),
#[error("I/O error at {path}: {source}")]
Io {
path: PathBuf,
source: std::io::Error,
},
#[error("constraint decode error: {0}")]
ConstraintDecode(String),
#[error(
"message requires runtime validation but fail_on_runtime_only is set: {0}; \
either drop the offending rule or validate this message with the runtime Validator"
)]
RuntimeOnly(String),
#[error("conflicting builder options: {0}")]
ConflictingOptions(String),
}