#![forbid(unsafe_code)]
use prost_types::DescriptorProto;
use crate::emit::{collect_map_entries, field_type_str_with_wkt};
use crate::options::{CodegenError, CodegenOptions};
use crate::type_registry::TypeRegistry;
pub(crate) fn emit_builder_for_message(
msg: &DescriptorProto,
type_name: &str,
_opts: &CodegenOptions,
file_package: &str,
registry: &TypeRegistry,
) -> Result<String, CodegenError> {
if msg.options.as_ref().is_some_and(|o| o.map_entry()) {
return Ok(String::new());
}
let builder_name = format!("{type_name}Builder");
let res_nums = reserved_numbers(msg);
let res_names = reserved_names(msg);
let map_entries = collect_map_entries(msg, file_package, registry);
let mut out = String::new();
out.push_str(&format!("pub struct {builder_name} {{\n"));
out.push_str(&format!(" inner: {type_name},\n"));
out.push_str("}\n");
out.push_str(&format!("impl Default for {builder_name} {{\n"));
out.push_str(" fn default() -> Self {\n");
out.push_str(&format!(
" Self {{ inner: {type_name}::default() }}\n"
));
out.push_str(" }\n");
out.push_str("}\n");
out.push_str(&format!("impl {builder_name} {{\n"));
out.push_str(" pub fn new() -> Self {\n");
out.push_str(" Self::default()\n");
out.push_str(" }\n");
for field in &msg.field {
let fname = field
.name
.as_deref()
.ok_or_else(|| CodegenError::InvalidDescriptor("field missing name".into()))?;
let field_number = field.number.unwrap_or(0);
if res_nums.contains(&field_number) || res_names.contains(fname) {
continue;
}
if field.oneof_index.is_some() {
continue;
}
if let Some(map_info) = map_entries.get(fname) {
let k = &map_info.key_type;
let v = &map_info.value_type;
out.push_str(&format!(
" pub fn insert_{fname}(mut self, k: {k}, v: {v}) -> Self {{\n"
));
out.push_str(&format!(" self.inner.{fname}.insert(k, v);\n"));
out.push_str(" self\n");
out.push_str(" }\n");
continue;
}
let rust_type = field_type_str_with_wkt(field, type_name, file_package, registry)?;
if rust_type.starts_with("Vec<") && rust_type.ends_with('>') {
let element_type = &rust_type[4..rust_type.len() - 1];
out.push_str(&format!(
" pub fn add_{fname}(mut self, v: {element_type}) -> Self {{\n"
));
out.push_str(&format!(" self.inner.{fname}.push(v);\n"));
out.push_str(" self\n");
out.push_str(" }\n");
continue;
}
out.push_str(&format!(
" pub fn {fname}(mut self, v: {rust_type}) -> Self {{\n"
));
out.push_str(&format!(" self.inner.{fname} = v;\n"));
out.push_str(" self\n");
out.push_str(" }\n");
}
out.push_str(&format!(" pub fn build(self) -> {type_name} {{\n"));
out.push_str(" self.inner\n");
out.push_str(" }\n");
out.push_str("}\n\n");
Ok(out)
}
fn reserved_numbers(msg: &DescriptorProto) -> std::collections::HashSet<i32> {
let mut set = std::collections::HashSet::new();
for range in &msg.reserved_range {
let start = range.start.unwrap_or(0);
let end = range.end.unwrap_or(0);
for n in start..end {
set.insert(n);
}
}
set
}
fn reserved_names(msg: &DescriptorProto) -> std::collections::HashSet<&str> {
msg.reserved_name.iter().map(|s| s.as_str()).collect()
}