use std::collections::BTreeSet;
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use super::super::{doc_tokens, format_tokens};
use crate::analysis::{GenerationPlan, ServicePlan};
use crate::codegen::{CodeGenConfig, PythonOptions, Runtime};
use crate::parsing::types::{BaseType, JsonType, UnifiedType};
use crate::parsing::{
CodeGenMetadata, EnumInfo, EnumValue, MessageField, MessageInfo, OneofVariant,
};
use crate::utils::{extract_qualified_type_name, extract_simple_type_name, metadata_key};
pub(crate) fn generate_pyo3_impls(
plan: &GenerationPlan,
metadata: &CodeGenMetadata,
config: &CodeGenConfig,
) -> crate::error::Result<String> {
let runtime = config.runtime;
let options = config.python_options();
let services: Vec<&ServicePlan> = plan.services.iter().collect();
let classes = ModelClasses::new(&services, metadata);
let ctx = Ctx {
options,
classes: &classes,
enums_module: format!("{}._enums", options.package.name),
};
let mut items: Vec<TokenStream> = Vec::new();
for name in &classes.enums {
let Some(rust_path) = type_rust_path(name, &classes.packages) else {
continue;
};
let info = &metadata.enums[*name];
items.push(str_enum_wrapper(name, info, &rust_path, runtime, &ctx)?);
}
for name in &classes.messages {
let Some(rust_path) = type_rust_path(name, &classes.packages) else {
continue;
};
let info = &metadata.messages[*name];
items.push(message_wrapper(
name, info, &rust_path, runtime, metadata, &ctx,
)?);
}
if items.is_empty() {
return Ok(String::new());
}
items.push(python_repr_fn());
if runtime == Runtime::Buffa && crate::codegen::metadata_has_json_fields(metadata) {
items.push(json_conversions());
}
let tokens = quote! {
#(#items)*
};
format_tokens(tokens)
}
pub(crate) fn json_from_py(
json: JsonType,
ty: &UnifiedType,
value: TokenStream,
arg: bool,
) -> TokenStream {
let from_py = format_ident!("json_{}_from_py", json.fn_suffix());
if ty.is_repeated {
let collection = if arg {
quote! { ::std::vec::Vec<_> }
} else {
quote! { _ }
};
quote! {{
let items: ::std::vec::Vec<::pyo3::Bound<'_, ::pyo3::PyAny>> =
::pyo3::types::PyAnyMethods::extract(&#value)?;
items.iter().map(#from_py).collect::<::pyo3::PyResult<#collection>>()?
}}
} else if matches!(ty.base_type, BaseType::Map(..)) {
let collection = if arg {
quote! { ::std::vec::Vec<_> }
} else {
quote! { _ }
};
quote! {{
let items: ::std::collections::HashMap<::std::string::String, ::pyo3::Bound<'_, ::pyo3::PyAny>> =
::pyo3::types::PyAnyMethods::extract(&#value)?;
items
.iter()
.map(|(key, item)| ::core::result::Result::Ok((key.clone(), #from_py(item)?)))
.collect::<::pyo3::PyResult<#collection>>()?
}}
} else {
quote! { ::buffa::MessageField::some(#from_py(&#value)?) }
}
}
fn json_conversions() -> TokenStream {
quote! {
pub trait JsonTypeHint {
const HINT: ::pyo3::inspect::PyStaticExpr;
}
pub struct JsonAny;
impl JsonTypeHint for JsonAny {
const HINT: ::pyo3::inspect::PyStaticExpr = ::pyo3::type_hint_identifier!("typing", "Any");
}
pub struct JsonDict<V>(::core::marker::PhantomData<V>);
impl<V: JsonTypeHint> JsonTypeHint for JsonDict<V> {
const HINT: ::pyo3::inspect::PyStaticExpr = ::pyo3::type_hint_subscript!(
::pyo3::type_hint_identifier!("builtins", "dict"),
::pyo3::type_hint_identifier!("builtins", "str"),
V::HINT
);
}
pub struct JsonList<V>(::core::marker::PhantomData<V>);
impl<V: JsonTypeHint> JsonTypeHint for JsonList<V> {
const HINT: ::pyo3::inspect::PyStaticExpr = ::pyo3::type_hint_subscript!(
::pyo3::type_hint_identifier!("builtins", "list"),
V::HINT
);
}
pub struct PyJson<H>(::pyo3::Py<::pyo3::PyAny>, ::core::marker::PhantomData<H>);
impl<H> PyJson<H> {
fn new(object: ::pyo3::Py<::pyo3::PyAny>) -> Self {
Self(object, ::core::marker::PhantomData)
}
}
impl<'py, H: JsonTypeHint> ::pyo3::IntoPyObject<'py> for PyJson<H> {
type Target = ::pyo3::PyAny;
type Output = ::pyo3::Bound<'py, ::pyo3::PyAny>;
type Error = ::core::convert::Infallible;
const OUTPUT_TYPE: ::pyo3::inspect::PyStaticExpr = H::HINT;
fn into_pyobject(self, py: ::pyo3::Python<'py>) -> ::core::result::Result<Self::Output, Self::Error> {
::core::result::Result::Ok(self.0.into_bound(py))
}
}
pub fn json_value_to_py(
py: ::pyo3::Python<'_>,
value: &::buffa_types::google::protobuf::Value,
) -> ::pyo3::PyResult<::pyo3::Py<::pyo3::PyAny>> {
use ::buffa_types::google::protobuf::value::Kind;
use ::pyo3::IntoPyObjectExt;
match &value.kind {
::core::option::Option::None | ::core::option::Option::Some(Kind::NullValue(_)) => {
::core::result::Result::Ok(py.None())
}
::core::option::Option::Some(Kind::BoolValue(b)) => b.into_py_any(py),
::core::option::Option::Some(Kind::NumberValue(n))
if n.fract() == 0.0 && n.abs() <= 9_007_199_254_740_992.0 =>
{
(*n as i64).into_py_any(py)
}
::core::option::Option::Some(Kind::NumberValue(n)) => n.into_py_any(py),
::core::option::Option::Some(Kind::StringValue(s)) => s.into_py_any(py),
::core::option::Option::Some(Kind::StructValue(s)) => json_struct_to_py(py, s),
::core::option::Option::Some(Kind::ListValue(l)) => json_list_to_py(py, l),
}
}
pub fn json_struct_to_py(
py: ::pyo3::Python<'_>,
value: &::buffa_types::google::protobuf::Struct,
) -> ::pyo3::PyResult<::pyo3::Py<::pyo3::PyAny>> {
let dict = ::pyo3::types::PyDict::new(py);
for (key, item) in &value.fields {
::pyo3::types::PyDictMethods::set_item(&dict, key, json_value_to_py(py, item)?)?;
}
::core::result::Result::Ok(dict.into_any().unbind())
}
pub fn json_list_to_py(
py: ::pyo3::Python<'_>,
value: &::buffa_types::google::protobuf::ListValue,
) -> ::pyo3::PyResult<::pyo3::Py<::pyo3::PyAny>> {
let items = value
.values
.iter()
.map(|item| json_value_to_py(py, item))
.collect::<::pyo3::PyResult<::std::vec::Vec<_>>>()?;
::core::result::Result::Ok(::pyo3::types::PyList::new(py, items)?.into_any().unbind())
}
pub fn json_value_from_py(
obj: &::pyo3::Bound<'_, ::pyo3::PyAny>,
) -> ::pyo3::PyResult<::buffa_types::google::protobuf::Value> {
use ::buffa_types::google::protobuf::Value;
use ::pyo3::types::{PyAnyMethods, PyTypeMethods};
if obj.is_none() {
return ::core::result::Result::Ok(Value::null());
}
if obj.is_instance_of::<::pyo3::types::PyBool>() {
return ::core::result::Result::Ok(Value::from(obj.extract::<bool>()?));
}
if obj.is_instance_of::<::pyo3::types::PyInt>()
|| obj.is_instance_of::<::pyo3::types::PyFloat>()
{
let number = obj.extract::<f64>()?;
if !number.is_finite() {
return ::core::result::Result::Err(::pyo3::exceptions::PyValueError::new_err(
::std::format!("not a JSON number: {number}"),
));
}
return ::core::result::Result::Ok(Value::from(number));
}
if obj.is_instance_of::<::pyo3::types::PyString>() {
return ::core::result::Result::Ok(Value::from(obj.extract::<::std::string::String>()?));
}
if obj.is_instance_of::<::pyo3::types::PyDict>() {
return ::core::result::Result::Ok(Value::from(json_struct_from_py(obj)?));
}
if obj.is_instance_of::<::pyo3::types::PyList>()
|| obj.is_instance_of::<::pyo3::types::PyTuple>()
{
return ::core::result::Result::Ok(Value::from(json_list_from_py(obj)?));
}
::core::result::Result::Err(::pyo3::exceptions::PyTypeError::new_err(::std::format!(
"not a JSON value: {}",
obj.get_type().name()?
)))
}
pub fn json_struct_from_py(
obj: &::pyo3::Bound<'_, ::pyo3::PyAny>,
) -> ::pyo3::PyResult<::buffa_types::google::protobuf::Struct> {
let items: ::std::collections::HashMap<::std::string::String, ::pyo3::Bound<'_, ::pyo3::PyAny>> =
::pyo3::types::PyAnyMethods::extract(obj)?;
let mut value = ::buffa_types::google::protobuf::Struct::default();
for (key, item) in &items {
value.fields.insert(key.clone(), json_value_from_py(item)?);
}
::core::result::Result::Ok(value)
}
pub fn json_list_from_py(
obj: &::pyo3::Bound<'_, ::pyo3::PyAny>,
) -> ::pyo3::PyResult<::buffa_types::google::protobuf::ListValue> {
let items: ::std::vec::Vec<::pyo3::Bound<'_, ::pyo3::PyAny>> =
::pyo3::types::PyAnyMethods::extract(obj)?;
let mut value = ::buffa_types::google::protobuf::ListValue::default();
for item in &items {
value.values.push(json_value_from_py(item)?);
}
::core::result::Result::Ok(value)
}
}
}
fn wrapper_ident(fq_name: &str) -> proc_macro2::Ident {
format_ident!("Py{}", extract_qualified_type_name(fq_name))
}
pub(crate) fn python_class_name(fq_name: &str) -> String {
extract_qualified_type_name(fq_name)
}
pub(crate) struct ModelClasses<'a> {
packages: BTreeSet<&'a str>,
pub(crate) enums: Vec<&'a String>,
messages: Vec<&'a String>,
inputs: BTreeSet<String>,
}
impl<'a> ModelClasses<'a> {
pub(crate) fn new(services: &[&'a ServicePlan], metadata: &'a CodeGenMetadata) -> Self {
let packages: BTreeSet<&str> = services.iter().map(|s| s.package.as_str()).collect();
let mut enums: Vec<&String> = metadata
.enums
.iter()
.filter(|(name, info)| {
!info.values.is_empty() && type_rust_path(name, &packages).is_some()
})
.map(|(name, _)| name)
.collect();
enums.sort();
let inputs = input_messages(services, metadata);
let surfaced = surfaced_messages(services, metadata, &inputs);
let mut messages: Vec<&String> = metadata
.messages
.keys()
.filter(|name| type_rust_path(name, &packages).is_some() && surfaced.contains(*name))
.collect();
messages.sort();
Self {
packages,
enums,
messages,
inputs,
}
}
pub(crate) fn pyclasses(&self) -> impl Iterator<Item = (&'a str, proc_macro2::Ident)> {
self.messages.iter().filter_map(|name| {
let package = package_of(name, &self.packages)?;
Some((super::package_segment(package), wrapper_ident(name)))
})
}
pub(crate) fn public_names(&self) -> impl Iterator<Item = (&'a str, String, bool)> {
let enums = self.enums.iter().map(|name| (*name, true));
let messages = self.messages.iter().map(|name| (*name, false));
enums.chain(messages).filter_map(|(name, is_enum)| {
let package = package_of(name, &self.packages)?;
Some((
super::package_segment(package),
python_class_name(name),
is_enum,
))
})
}
}
pub(crate) fn hidden_value(value: &EnumValue) -> bool {
value.number == 0 && value.name.ends_with("_UNSPECIFIED")
}
fn has_hidden_zero(info: &EnumInfo) -> bool {
info.values.iter().any(hidden_value)
}
fn package_of<'a>(fq_name: &str, project_packages: &BTreeSet<&'a str>) -> Option<&'a str> {
let stripped = fq_name.strip_prefix('.').unwrap_or(fq_name);
project_packages
.iter()
.filter(|pkg| {
stripped
.strip_prefix(**pkg)
.is_some_and(|rest| rest.starts_with('.'))
})
.max_by_key(|pkg| pkg.len())
.copied()
}
fn input_messages(services: &[&ServicePlan], metadata: &CodeGenMetadata) -> BTreeSet<String> {
let mut queue = Vec::new();
for method in services.iter().flat_map(|s| &s.methods) {
if let Some(info) = metadata
.messages
.get(&metadata_key(&method.metadata.input_type))
{
push_field_messages(info, &mut queue);
}
}
reachable(queue, metadata)
}
fn surfaced_messages(
services: &[&ServicePlan],
metadata: &CodeGenMetadata,
inputs: &BTreeSet<String>,
) -> BTreeSet<String> {
let outputs = services
.iter()
.flat_map(|s| &s.methods)
.map(|m| metadata_key(&m.metadata.output_type))
.filter(|name| !name.ends_with(".Empty"))
.collect();
let mut surfaced = reachable(outputs, metadata);
surfaced.extend(inputs.iter().cloned());
surfaced
}
fn reachable(start: Vec<String>, metadata: &CodeGenMetadata) -> BTreeSet<String> {
let mut queue = start;
let mut seen = BTreeSet::new();
while let Some(name) = queue.pop() {
if !seen.insert(name.clone()) {
continue;
}
if let Some(info) = metadata.messages.get(&name) {
push_field_messages(info, &mut queue);
}
}
seen
}
fn push_field_messages(info: &MessageInfo, queue: &mut Vec<String>) {
for field in &info.fields {
let types = std::iter::once(&field.unified_type)
.chain(field.oneof_variants.iter().flatten().map(|v| &v.field_type));
for ty in types {
let ty = match &ty.base_type {
BaseType::Map(_, value) => value,
_ => ty,
};
if let BaseType::Message(name) = &ty.base_type {
queue.push(metadata_key(name));
}
}
}
}
struct Ctx<'a> {
options: &'a PythonOptions,
classes: &'a ModelClasses<'a>,
enums_module: String,
}
impl Ctx<'_> {
fn module_arg(&self, fq_name: &str) -> Option<TokenStream> {
let package = package_of(fq_name, &self.classes.packages)?;
Some(super::module_arg(
self.options,
Some(super::package_segment(package)),
))
}
fn has_constructor(&self, fq_name: &str) -> bool {
self.classes.inputs.contains(&metadata_key(fq_name))
}
}
fn python_repr_fn() -> TokenStream {
quote! {
fn __python_repr(
obj: &::pyo3::Bound<'_, ::pyo3::PyAny>,
fields: &[(&str, bool)],
) -> ::pyo3::PyResult<::std::string::String> {
use ::pyo3::types::{PyAnyMethods, PyTypeMethods};
let mut shown = ::std::vec::Vec::with_capacity(fields.len());
for (field, redact) in fields {
let value = obj.getattr(*field)?;
if value.is_none() {
continue;
}
if *redact {
shown.push(::std::format!("{field}='***'"));
} else {
shown.push(::std::format!("{field}={}", value.repr()?));
}
}
Ok(::std::format!("{}({})", obj.get_type().name()?, shown.join(", ")))
}
}
}
fn str_enum_wrapper(
fq_name: &str,
info: &EnumInfo,
rust_path: &str,
runtime: Runtime,
ctx: &Ctx<'_>,
) -> crate::error::Result<TokenStream> {
let wrapper = wrapper_ident(fq_name);
let class_name = python_class_name(fq_name);
let inner: syn::Path = parse_path(rust_path, fq_name)?;
let enums_module = ctx.enums_module.as_str();
let shown: Vec<&EnumValue> = info.values.iter().filter(|v| !hidden_value(v)).collect();
let names: Vec<&str> = shown.iter().map(|v| v.name.as_str()).collect();
let numbers: Vec<proc_macro2::Literal> = shown
.iter()
.map(|v| proc_macro2::Literal::i32_suffixed(v.number))
.collect();
let invalid = format!(
"invalid {class_name}: {{:?}}; expected one of: {}",
names.join(", ")
);
let no_member = format!("{class_name} value {{}} has no Python member");
let (inner_to_i32, i32_to_inner): (TokenStream, TokenStream) = match runtime {
Runtime::Buffa => (
quote! { <#inner as ::buffa::Enumeration>::to_i32(&self.0) },
quote! { <#inner as ::buffa::Enumeration>::from_i32(n).unwrap_or_default() },
),
Runtime::Prost => (
quote! { self.0 as i32 },
quote! { <#inner as ::core::convert::TryFrom<i32>>::try_from(n).unwrap_or_default() },
),
};
let doc = format!(" A member of `{enums_module}.{class_name}`, or its name.");
Ok(quote! {
#[doc = #doc]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct #wrapper(pub #inner);
impl #wrapper {
fn __name(&self) -> ::core::option::Option<&'static str> {
match #inner_to_i32 {
#(#numbers => ::core::option::Option::Some(#names),)*
_ => ::core::option::Option::None,
}
}
}
impl ::core::convert::From<#inner> for #wrapper {
fn from(value: #inner) -> Self {
Self(value)
}
}
impl ::core::convert::From<#wrapper> for #inner {
fn from(value: #wrapper) -> Self {
value.0
}
}
impl<'a, 'py> ::pyo3::FromPyObject<'a, 'py> for #wrapper {
type Error = ::pyo3::PyErr;
const INPUT_TYPE: ::pyo3::inspect::PyStaticExpr = {
use ::pyo3::type_hint_union;
type_hint_union!(
::pyo3::type_hint_identifier!(#enums_module, #class_name),
::pyo3::type_hint_identifier!("builtins", "str")
)
};
fn extract(obj: ::pyo3::Borrowed<'a, 'py, ::pyo3::PyAny>) -> ::pyo3::PyResult<Self> {
let name: ::pyo3::pybacked::PyBackedStr = ::pyo3::types::PyAnyMethods::extract(&*obj)?;
let n: i32 = match &*name {
#(#names => #numbers,)*
other => {
return ::core::result::Result::Err(
::pyo3::exceptions::PyValueError::new_err(::std::format!(#invalid, other)),
);
}
};
::core::result::Result::Ok(Self(#i32_to_inner))
}
}
impl<'py> ::pyo3::IntoPyObject<'py> for #wrapper {
type Target = ::pyo3::PyAny;
type Output = ::pyo3::Bound<'py, ::pyo3::PyAny>;
type Error = ::pyo3::PyErr;
const OUTPUT_TYPE: ::pyo3::inspect::PyStaticExpr =
::pyo3::type_hint_identifier!(#enums_module, #class_name);
fn into_pyobject(self, py: ::pyo3::Python<'py>) -> ::pyo3::PyResult<Self::Output> {
static CLASS: ::pyo3::sync::PyOnceLock<::pyo3::Py<::pyo3::types::PyType>> =
::pyo3::sync::PyOnceLock::new();
let class = CLASS.import(py, #enums_module, #class_name)?;
let name = self.__name().ok_or_else(|| {
::pyo3::exceptions::PyValueError::new_err(::std::format!(#no_member, #inner_to_i32))
})?;
::pyo3::types::PyAnyMethods::getattr(class.as_any(), name)
}
}
})
}
fn message_wrapper(
fq_name: &str,
info: &MessageInfo,
rust_path: &str,
runtime: Runtime,
metadata: &CodeGenMetadata,
ctx: &Ctx<'_>,
) -> crate::error::Result<TokenStream> {
let wrapper = wrapper_ident(fq_name);
let class_name = python_class_name(fq_name);
let inner: syn::Path = parse_path(rust_path, fq_name)?;
let plain_fields: Vec<&MessageField> = info
.fields
.iter()
.filter(|f| f.oneof_variants.is_none())
.collect();
let oneof_fields: Vec<&MessageField> = info
.fields
.iter()
.filter(|f| f.oneof_variants.is_some())
.collect();
let packages = metadata_packages(metadata);
let project_pkg_refs: BTreeSet<&str> = packages.iter().map(String::as_str).collect();
let boxed = boxed_fields(metadata, runtime);
let accessors: Vec<FieldAccessor> = plain_fields
.iter()
.map(|f| {
let is_boxed = boxed.contains(&(fq_name.to_string(), f.name.clone()));
let hidden_zero = match &f.unified_type.base_type {
BaseType::Enum(n) => metadata
.enums
.get(&metadata_key(n))
.is_some_and(has_hidden_zero),
_ => false,
};
FieldAccessor::new(
f,
runtime,
&packages,
&project_pkg_refs,
is_boxed,
hidden_zero,
)
})
.collect();
let oneofs: Vec<OneofAccessor> = oneof_fields
.iter()
.map(|f| OneofAccessor::new(f, &inner, runtime, &packages))
.collect();
let getters = accessors.iter().map(FieldAccessor::getter);
let oneof_getters = oneofs.iter().flat_map(OneofAccessor::variant_getters);
let ctor = ctx
.has_constructor(fq_name)
.then(|| constructor(&accessors, &oneofs, &inner, ctx.options.keyword_only));
let repr_fields = accessors
.iter()
.map(|a| (a.ident.clone(), a.sensitive))
.chain(
oneofs
.iter()
.flat_map(OneofAccessor::variant_param_idents)
.map(|ident| (ident, false)),
)
.map(|(ident, redact)| {
let name = syn::ext::IdentExt::unraw(&ident).to_string();
quote! { (#name, #redact) }
});
let module_arg = ctx.module_arg(fq_name);
let doc = doc_tokens(info.documentation.as_deref());
Ok(quote! {
#doc
#[::pyo3::pyclass(name = #class_name, eq, frozen, #module_arg from_py_object)]
#[derive(Clone, Debug, PartialEq)]
pub struct #wrapper(pub #inner);
#[allow(clippy::too_many_arguments, clippy::useless_conversion)]
#[::pyo3::pymethods]
impl #wrapper {
#ctor
#(#getters)*
#(#oneof_getters)*
fn __repr__(slf: &::pyo3::Bound<'_, Self>) -> ::pyo3::PyResult<::std::string::String> {
__python_repr(slf.as_any(), &[#(#repr_fields),*])
}
}
impl ::core::convert::From<#inner> for #wrapper {
fn from(value: #inner) -> Self {
Self(value)
}
}
impl ::core::convert::From<#wrapper> for #inner {
fn from(value: #wrapper) -> Self {
value.0
}
}
})
}
fn metadata_packages(metadata: &CodeGenMetadata) -> BTreeSet<String> {
metadata
.services
.values()
.map(|s| s.package.clone())
.collect()
}
fn boxed_fields(metadata: &CodeGenMetadata, runtime: Runtime) -> BTreeSet<(String, String)> {
let mut boxed = BTreeSet::new();
if runtime != Runtime::Prost {
return boxed;
}
for (msg_name, info) in &metadata.messages {
for field in &info.fields {
if field.unified_type.is_repeated
|| matches!(field.unified_type.base_type, BaseType::Map(_, _))
{
continue;
}
let BaseType::Message(target) = &field.unified_type.base_type else {
continue;
};
if reaches(metadata, target, msg_name) {
boxed.insert((msg_name.clone(), field.name.clone()));
}
}
}
boxed
}
fn reaches(metadata: &CodeGenMetadata, from: &str, target: &str) -> bool {
let mut stack = vec![from.to_string()];
let mut seen = BTreeSet::new();
while let Some(cur) = stack.pop() {
if cur == target {
return true;
}
if !seen.insert(cur.clone()) {
continue;
}
let Some(info) = metadata.messages.get(&cur) else {
continue;
};
for field in &info.fields {
if field.unified_type.is_repeated
|| matches!(field.unified_type.base_type, BaseType::Map(_, _))
{
continue;
}
if let BaseType::Message(next) = &field.unified_type.base_type {
stack.push(next.clone());
}
}
}
false
}
struct FieldAccessor {
ident: proc_macro2::Ident,
ty: UnifiedType,
runtime: Runtime,
in_project: bool,
inner_rust_path: Option<TokenStream>,
is_boxed: bool,
hidden_zero: bool,
documentation: Option<String>,
json: Option<JsonType>,
required: bool,
sensitive: bool,
}
impl FieldAccessor {
fn new(
field: &MessageField,
runtime: Runtime,
packages: &BTreeSet<String>,
project_pkg_refs: &BTreeSet<&str>,
is_boxed: bool,
hidden_zero: bool,
) -> Self {
let ident = crate::utils::field_ident(&super::sanitize_python_field_name(&field.name));
let element = match &field.unified_type.base_type {
BaseType::Map(_, value) => &value.base_type,
base => base,
};
let in_project = match element {
BaseType::Message(n) | BaseType::Enum(n) => is_in_project(n, packages),
_ => false,
};
let inner_rust_path = match &field.unified_type.base_type {
BaseType::Message(n) | BaseType::Enum(n) if in_project => {
type_rust_path(n, project_pkg_refs)
.and_then(|p| syn::parse_str::<syn::Path>(&p).ok())
.map(|p| quote! { #p })
}
_ => None,
};
Self {
ident,
ty: field.unified_type.clone(),
runtime,
in_project,
inner_rust_path,
is_boxed,
hidden_zero,
documentation: field.documentation.clone(),
json: JsonType::of_field(&field.unified_type).filter(|_| runtime == Runtime::Buffa),
required: field
.field_behavior
.contains(&crate::google::api::FieldBehavior::Required),
sensitive: field.is_sensitive,
}
}
fn json_getter(&self, json: JsonType) -> TokenStream {
let ident = &self.ident;
let doc = doc_tokens(self.documentation.as_deref());
let to_py = format_ident!("json_{}_to_py", json.fn_suffix());
let item = match json {
JsonType::Value => quote! { JsonAny },
JsonType::Struct => quote! { JsonDict<JsonAny> },
JsonType::ListValue => quote! { JsonList<JsonAny> },
};
let (ret, body) = if self.ty.is_repeated {
(
quote! { PyJson<JsonList<#item>> },
quote! {
let items = self.0.#ident
.iter()
.map(|value| #to_py(py, value))
.collect::<::pyo3::PyResult<::std::vec::Vec<_>>>()?;
::core::result::Result::Ok(PyJson::new(
::pyo3::types::PyList::new(py, items)?.into_any().unbind(),
))
},
)
} else if matches!(self.ty.base_type, BaseType::Map(..)) {
(
quote! { PyJson<JsonDict<#item>> },
quote! {
let dict = ::pyo3::types::PyDict::new(py);
for (key, value) in &self.0.#ident {
::pyo3::types::PyDictMethods::set_item(&dict, key, #to_py(py, value)?)?;
}
::core::result::Result::Ok(PyJson::new(dict.into_any().unbind()))
},
)
} else if json == JsonType::Value {
(
quote! { PyJson<#item> },
quote! {
let value = self.0.#ident.as_option().map(|value| #to_py(py, value)).transpose()?;
::core::result::Result::Ok(PyJson::new(value.unwrap_or_else(|| py.None())))
},
)
} else {
(
quote! { ::core::option::Option<PyJson<#item>> },
quote! {
self.0.#ident
.as_option()
.map(|value| #to_py(py, value).map(PyJson::new))
.transpose()
},
)
};
quote! {
#doc
#[getter]
fn #ident(&self, py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<#ret> {
#body
}
}
}
fn json_store(&self, json: JsonType) -> TokenStream {
json_from_py(json, &self.ty, quote! { value }, false)
}
fn py_type(&self) -> TokenStream {
let base = self.base_py_type();
if self.ty.is_repeated {
return quote! { ::std::vec::Vec<#base> };
}
if let BaseType::Map(_, v) = &self.ty.base_type {
let val = py_type_for(v, self.in_project);
return quote! { ::std::collections::HashMap<::std::string::String, #val> };
}
if self.is_singular_message() {
return quote! { ::core::option::Option<#base> };
}
if self.is_optional_scalar() || self.is_hidden_zero_enum() {
return quote! { ::core::option::Option<#base> };
}
base
}
fn is_hidden_zero_enum(&self) -> bool {
self.hidden_zero && !self.ty.is_repeated && !self.ty.is_optional
}
fn hidden_zero_filter(&self) -> Option<TokenStream> {
let inner = self.inner_rust_path.as_ref();
self.hidden_zero.then(|| match self.runtime {
Runtime::Buffa => {
quote! { .filter(|e| <#inner as ::buffa::Enumeration>::to_i32(e) != 0) }
}
Runtime::Prost => quote! { .filter(|e| *e as i32 != 0) },
})
}
fn base_py_type(&self) -> TokenStream {
py_type_for(&self.ty, self.in_project)
}
fn is_singular_message(&self) -> bool {
!self.ty.is_repeated
&& matches!(self.ty.base_type, BaseType::Message(_) | BaseType::OneOf(_))
}
fn py_type_is_optional(&self) -> bool {
self.is_singular_message() || self.is_optional_scalar() || self.is_hidden_zero_enum()
}
fn is_optional_scalar(&self) -> bool {
self.ty.is_optional
&& !self.ty.is_repeated
&& !matches!(
self.ty.base_type,
BaseType::Message(_) | BaseType::OneOf(_) | BaseType::Map(_, _)
)
}
fn getter(&self) -> TokenStream {
if let Some(json) = self.json {
return self.json_getter(json);
}
let ident = &self.ident;
let (ret, body) = match self.ty.is_singular_bytes() {
true => self.bytes_getter(),
false => (self.py_type(), self.read_expr()),
};
let doc = doc_tokens(self.documentation.as_deref());
quote! {
#doc
#[getter]
fn #ident(&self) -> #ret {
#body
}
}
}
fn bytes_getter(&self) -> (TokenStream, TokenStream) {
let ident = &self.ident;
match self.is_optional_scalar() {
true => (
quote! { ::core::option::Option<::std::borrow::Cow<'_, [u8]>> },
quote! { self.0.#ident.as_deref().map(::std::borrow::Cow::Borrowed) },
),
false => (
quote! { ::std::borrow::Cow<'_, [u8]> },
quote! { ::std::borrow::Cow::Borrowed(&self.0.#ident) },
),
}
}
fn input_type(&self) -> TokenStream {
match self.ty.is_singular_bytes() {
true => super::bindings::py_bytes_input(self.is_optional_scalar()),
false => self.py_type(),
}
}
fn input_base_type(&self) -> TokenStream {
match self.ty.is_singular_bytes() {
true => super::bindings::py_bytes_input(false),
false => self.base_py_type(),
}
}
fn read_expr(&self) -> TokenStream {
let ident = &self.ident;
let access = quote! { self.0.#ident };
if self.ty.is_repeated {
return self.read_repeated(&access);
}
if let BaseType::Map(_, v) = &self.ty.base_type {
return read_map(&access, v, self.in_project);
}
if self.is_optional_scalar() {
return self.read_optional_scalar(&access);
}
match &self.ty.base_type {
BaseType::String => quote! { #access.clone() },
BaseType::Bytes => quote! { #access.clone() },
BaseType::Int32
| BaseType::Int64
| BaseType::Bool
| BaseType::Float32
| BaseType::Float64 => quote! { #access },
BaseType::Enum(_) => self.read_enum(&access),
BaseType::Message(_) | BaseType::OneOf(_) => self.read_message(&access),
BaseType::Unit | BaseType::Map(_, _) => quote! { #access.clone() },
}
}
fn read_repeated(&self, access: &TokenStream) -> TokenStream {
let base = self.base_py_type();
let inner = self.inner_rust_path.as_ref();
match &self.ty.base_type {
BaseType::Enum(_) => {
let hidden = self.hidden_zero_filter();
match self.runtime {
Runtime::Buffa => quote! {
#access.iter().filter_map(|e| e.as_known()) #hidden .map(#base::from).collect()
},
Runtime::Prost => quote! {
#access.iter()
.filter_map(|&v| <#inner as ::core::convert::TryFrom<i32>>::try_from(v).ok())
#hidden
.map(#base::from)
.collect()
},
}
}
BaseType::Message(_) => quote! {
#access.iter().cloned().map(#base::from).collect()
},
BaseType::String | BaseType::Bytes => quote! { #access.clone() },
_ => quote! { #access.clone() },
}
}
fn read_enum(&self, access: &TokenStream) -> TokenStream {
let base = self.base_py_type();
let inner = self.inner_rust_path.as_ref();
if self.is_hidden_zero_enum() {
let hidden = self.hidden_zero_filter();
return match self.runtime {
Runtime::Buffa => quote! { #access.as_known() #hidden .map(#base::from) },
Runtime::Prost => quote! {
<#inner as ::core::convert::TryFrom<i32>>::try_from(#access).ok() #hidden .map(#base::from)
},
};
}
match self.runtime {
Runtime::Buffa => quote! {
#base::from(#access.as_known().unwrap_or_default())
},
Runtime::Prost => quote! {
#base::from(
<#inner as ::core::convert::TryFrom<i32>>::try_from(#access)
.unwrap_or_default()
)
},
}
}
fn read_message(&self, access: &TokenStream) -> TokenStream {
let base = self.base_py_type();
match self.runtime {
Runtime::Buffa => quote! {
#access.clone().into_option().map(#base::from)
},
Runtime::Prost if self.is_boxed => quote! {
#access.clone().map(|b| #base::from(*b))
},
Runtime::Prost => quote! {
#access.clone().map(#base::from)
},
}
}
fn read_optional_scalar(&self, access: &TokenStream) -> TokenStream {
let base = self.base_py_type();
let inner = self.inner_rust_path.as_ref();
match &self.ty.base_type {
BaseType::Enum(_) => {
let hidden = self.hidden_zero_filter();
match self.runtime {
Runtime::Buffa => quote! {
#access.as_ref().and_then(|e| e.as_known()) #hidden .map(#base::from)
},
Runtime::Prost => quote! {
#access.and_then(|v| <#inner as ::core::convert::TryFrom<i32>>::try_from(v).ok())
#hidden
.map(#base::from)
},
}
}
BaseType::Int32
| BaseType::Int64
| BaseType::Bool
| BaseType::Float32
| BaseType::Float64 => quote! { #access },
_ => quote! { #access.clone() },
}
}
fn write_expr(&self) -> TokenStream {
if self.ty.is_repeated {
return self.write_repeated();
}
if self.ty.is_singular_bytes() {
return super::bindings::bytes_to_vec(quote! { value }, self.is_optional_scalar());
}
if let BaseType::Map(_, value) = &self.ty.base_type {
return if self.in_project && matches!(value.base_type, BaseType::Message(_)) {
quote! { value.into_iter().map(|(k, v)| (k, ::core::convert::Into::into(v))).collect() }
} else {
quote! { value }
};
}
if self.is_optional_scalar() {
return self.write_optional_scalar();
}
match &self.ty.base_type {
BaseType::Enum(_) => self.write_enum(),
BaseType::Message(_) | BaseType::OneOf(_) => self.write_message(),
_ => quote! { value },
}
}
fn write_optional_scalar(&self) -> TokenStream {
let inner = self.inner_rust_path.as_ref();
match &self.ty.base_type {
BaseType::Enum(_) => match self.runtime {
Runtime::Buffa => quote! {
value.map(|e| ::buffa::EnumValue::Known(<#inner as ::core::convert::From<_>>::from(e)))
},
Runtime::Prost => quote! {
value.map(|e| <#inner as ::core::convert::From<_>>::from(e) as i32)
},
},
_ => quote! { value },
}
}
fn write_repeated(&self) -> TokenStream {
let inner = self.inner_rust_path.as_ref();
match &self.ty.base_type {
BaseType::Enum(_) => match self.runtime {
Runtime::Buffa => quote! {
value.into_iter()
.map(|e| ::buffa::EnumValue::Known(<#inner as ::core::convert::From<_>>::from(e)))
.collect()
},
Runtime::Prost => quote! {
value.into_iter()
.map(|e| <#inner as ::core::convert::From<_>>::from(e) as i32)
.collect()
},
},
BaseType::Message(_) => quote! {
value.into_iter().map(::core::convert::Into::into).collect()
},
_ => quote! { value },
}
}
fn write_enum(&self) -> TokenStream {
let inner = self.inner_rust_path.as_ref();
if self.is_hidden_zero_enum() {
return match self.runtime {
Runtime::Buffa => quote! {
value
.map(|e| ::buffa::EnumValue::Known(<#inner as ::core::convert::From<_>>::from(e)))
.unwrap_or_default()
},
Runtime::Prost => quote! {
value.map(|e| <#inner as ::core::convert::From<_>>::from(e) as i32).unwrap_or(0)
},
};
}
match self.runtime {
Runtime::Buffa => quote! {
::buffa::EnumValue::Known(<#inner as ::core::convert::From<_>>::from(value))
},
Runtime::Prost => quote! {
<#inner as ::core::convert::From<_>>::from(value) as i32
},
}
}
fn write_message(&self) -> TokenStream {
match self.runtime {
Runtime::Buffa => quote! {
value.map(|w| ::buffa::MessageField::some(w.into())).unwrap_or_default()
},
Runtime::Prost if self.is_boxed => quote! {
value.map(|w| ::std::boxed::Box::new(w.into()))
},
Runtime::Prost => quote! {
value.map(|w| w.into())
},
}
}
}
struct OneofAccessor {
field_ident: proc_macro2::Ident,
enum_path: TokenStream,
runtime: Runtime,
variants: Vec<OneofVariantAccessor>,
}
struct OneofVariantAccessor {
variant_ident: proc_macro2::Ident,
param_ident: proc_macro2::Ident,
payload_ty: TokenStream,
is_message: bool,
is_enum: bool,
documentation: Option<String>,
}
impl OneofAccessor {
fn new(
field: &MessageField,
inner: &syn::Path,
runtime: Runtime,
packages: &BTreeSet<String>,
) -> Self {
let field_ident =
crate::utils::field_ident(&super::sanitize_python_field_name(&field.name));
let BaseType::OneOf(enum_name) = &field.unified_type.base_type else {
unreachable!("OneofAccessor::new called on a non-oneof field");
};
let enum_path = oneof_enum_path(inner, enum_name);
let variants = field
.oneof_variants
.as_deref()
.unwrap_or(&[])
.iter()
.map(|v| OneofVariantAccessor::new(v, packages))
.collect();
Self {
field_ident,
enum_path,
runtime,
variants,
}
}
fn variant_param_idents(&self) -> Vec<proc_macro2::Ident> {
self.variants
.iter()
.map(|v| v.param_ident.clone())
.collect()
}
fn ctor_params(&self) -> Vec<TokenStream> {
self.variants
.iter()
.map(|v| {
let ident = &v.param_ident;
let ty = &v.payload_ty;
quote! { #ident: ::core::option::Option<#ty> }
})
.collect()
}
fn ctor_assigns(&self) -> Vec<TokenStream> {
let field_ident = &self.field_ident;
self.variants
.iter()
.map(|v| {
let param = &v.param_ident;
let store = v.write_variant(&self.enum_path, self.runtime);
quote! {
if let ::core::option::Option::Some(value) = #param {
inner.#field_ident = ::core::option::Option::Some(#store);
}
}
})
.collect()
}
fn variant_getters(&self) -> Vec<TokenStream> {
let field_ident = &self.field_ident;
self.variants
.iter()
.map(|v| {
let getter = &v.param_ident;
let ty = &v.payload_ty;
let variant = &v.variant_ident;
let enum_path = &self.enum_path;
let read = v.read_variant(self.runtime);
let doc = v
.documentation
.as_deref()
.map(|d| quote! { #[doc = #d] })
.unwrap_or_default();
quote! {
#doc
#[getter]
fn #getter(&self) -> ::core::option::Option<#ty> {
match &self.0.#field_ident {
::core::option::Option::Some(#enum_path::#variant(value)) => {
::core::option::Option::Some(#read)
}
_ => ::core::option::Option::None,
}
}
}
})
.collect()
}
}
impl OneofVariantAccessor {
fn new(variant: &OneofVariant, packages: &BTreeSet<String>) -> Self {
let param_ident =
crate::utils::field_ident(&super::sanitize_python_field_name(&variant.field_name));
let variant_ident = format_ident!("{}", variant.variant_name);
let (is_message, is_enum) = match &variant.field_type.base_type {
BaseType::Message(n) => (is_in_project(n.as_str(), packages), false),
BaseType::Enum(n) => (false, is_in_project(n.as_str(), packages)),
_ => (false, false),
};
let in_project = is_message || is_enum;
let payload_ty = py_type_for(&variant.field_type, in_project);
Self {
variant_ident,
param_ident,
payload_ty,
is_message,
is_enum,
documentation: variant.documentation.clone(),
}
}
fn write_variant(&self, enum_path: &TokenStream, runtime: Runtime) -> TokenStream {
let variant = &self.variant_ident;
if self.is_message {
match runtime {
Runtime::Buffa => quote! {
#enum_path::#variant(::std::boxed::Box::new(value.into()))
},
Runtime::Prost => quote! {
#enum_path::#variant(value.into())
},
}
} else if self.is_enum {
quote! {
#enum_path::#variant(value.into())
}
} else {
quote! {
#enum_path::#variant(value)
}
}
}
fn read_variant(&self, runtime: Runtime) -> TokenStream {
let ty = &self.payload_ty;
if self.is_message {
match runtime {
Runtime::Buffa => quote! { <#ty>::from((**value).clone()) },
Runtime::Prost => quote! { <#ty>::from((*value).clone()) },
}
} else if self.is_enum {
quote! { <#ty>::from(*value) }
} else {
quote! { ::core::clone::Clone::clone(value) }
}
}
}
fn oneof_enum_path(inner: &syn::Path, enum_name: &str) -> TokenStream {
let parent_segments = inner
.segments
.iter()
.take(inner.segments.len().saturating_sub(1))
.map(|s| s.ident.clone());
let tail: syn::Path =
syn::parse_str(enum_name).expect("oneof enum IR name is a valid `snake::Pascal` path");
quote! { #(#parent_segments::)* #tail }
}
fn py_type_for(ty: &UnifiedType, in_project: bool) -> TokenStream {
match &ty.base_type {
BaseType::String => quote! { ::std::string::String },
BaseType::Bytes => quote! { ::std::vec::Vec<u8> },
BaseType::Int32 => quote! { i32 },
BaseType::Int64 => quote! { i64 },
BaseType::Bool => quote! { bool },
BaseType::Float32 => quote! { f32 },
BaseType::Float64 => quote! { f64 },
BaseType::Unit => quote! { () },
BaseType::Message(n) | BaseType::Enum(n) => {
if in_project {
let w = wrapper_ident(n);
quote! { #w }
} else {
let id = format_ident!("{}", extract_simple_type_name(n));
quote! { #id }
}
}
BaseType::OneOf(n) => {
let id = format_ident!("{}", extract_simple_type_name(n));
quote! { #id }
}
BaseType::Map(_, v) => {
let val = py_type_for(v, in_project);
quote! { ::std::collections::HashMap<::std::string::String, #val> }
}
}
}
fn read_map(access: &TokenStream, value: &UnifiedType, in_project: bool) -> TokenStream {
match &value.base_type {
BaseType::Message(_) if in_project => {
let w = py_type_for(value, true);
quote! {
#access.iter().map(|(k, v)| (k.clone(), #w::from(v.clone()))).collect()
}
}
_ => quote! { #access.clone() },
}
}
fn bind_value(ident: &proc_macro2::Ident) -> Option<TokenStream> {
(ident != "value").then(|| quote! { let value = #ident; })
}
fn constructor(
accessors: &[FieldAccessor],
oneofs: &[OneofAccessor],
inner: &syn::Path,
keyword_only: bool,
) -> TokenStream {
if accessors.is_empty() && oneofs.is_empty() {
return quote! {
#[new]
fn new() -> Self {
Self(::core::default::Default::default())
}
};
}
let is_required = |a: &&FieldAccessor| keyword_only && a.required;
let (required, optional): (Vec<&FieldAccessor>, Vec<&FieldAccessor>) =
accessors.iter().partition(is_required);
let required_params = required.iter().map(|a| {
let ident = &a.ident;
if a.json.is_some() {
return quote! { #ident: ::pyo3::Bound<'_, ::pyo3::PyAny> };
}
let ty = if a.py_type_is_optional() {
a.input_base_type()
} else {
a.input_type()
};
quote! { #ident: #ty }
});
let optional_params = optional.iter().map(|a| {
let ident = &a.ident;
if a.json.is_some() {
return quote! { #ident: ::core::option::Option<::pyo3::Bound<'_, ::pyo3::PyAny>> };
}
let ty = a.input_type();
if a.py_type_is_optional() {
quote! { #ident: #ty }
} else {
quote! { #ident: ::core::option::Option<#ty> }
}
});
let oneof_params = oneofs.iter().flat_map(OneofAccessor::ctor_params);
let params = required_params.chain(optional_params).chain(oneof_params);
let optional_idents: Vec<proc_macro2::Ident> = optional
.iter()
.map(|a| a.ident.clone())
.chain(oneofs.iter().flat_map(OneofAccessor::variant_param_idents))
.collect();
let mut sig_parts: Vec<TokenStream> = required
.iter()
.map(|a| {
let ident = &a.ident;
quote! { #ident }
})
.collect();
if keyword_only && !optional_idents.is_empty() {
sig_parts.push(quote! { * });
}
sig_parts.extend(optional_idents.iter().map(|ident| quote! { #ident = None }));
let sig = quote! { #(#sig_parts),* };
let required_assigns = required.iter().map(|a| {
let ident = &a.ident;
let store = match a.json {
Some(json) => a.json_store(json),
None => a.write_expr(),
};
if a.json.is_none() && a.py_type_is_optional() {
quote! {
{
let value = ::core::option::Option::Some(#ident);
inner.#ident = #store;
}
}
} else {
let bind = bind_value(ident);
quote! {
{
#bind
inner.#ident = #store;
}
}
}
});
let optional_assigns = optional.iter().map(|a| {
let ident = &a.ident;
if let Some(json) = a.json {
let store = a.json_store(json);
return quote! {
if let ::core::option::Option::Some(value) = #ident {
inner.#ident = #store;
}
};
}
let store = a.write_expr();
if a.py_type_is_optional() {
let bind = bind_value(ident);
quote! {
{
#bind
inner.#ident = #store;
}
}
} else {
quote! {
if let ::core::option::Option::Some(value) = #ident {
inner.#ident = #store;
}
}
}
});
let oneof_assigns = oneofs.iter().flat_map(OneofAccessor::ctor_assigns);
let (ret, result) = if accessors.iter().any(|a| a.json.is_some()) {
(
quote! { ::pyo3::PyResult<Self> },
quote! { ::core::result::Result::Ok(Self(inner)) },
)
} else {
(quote! { Self }, quote! { Self(inner) })
};
quote! {
#[new]
#[pyo3(signature = (#sig))]
fn new(#(#params,)*) -> #ret {
let mut inner = <#inner as ::core::default::Default>::default();
#(#required_assigns)*
#(#optional_assigns)*
#(#oneof_assigns)*
#result
}
}
}
fn parse_path(rust_path: &str, fq_name: &str) -> crate::error::Result<syn::Path> {
syn::parse_str(rust_path).map_err(|e| crate::error::Error::InvalidRustPath {
path: rust_path.to_string(),
message: fq_name.to_string(),
source: e,
})
}
fn is_in_project(fq_name: &str, packages: &BTreeSet<String>) -> bool {
let stripped = fq_name.strip_prefix('.').unwrap_or(fq_name);
packages.iter().any(|pkg| {
stripped == pkg
|| stripped
.strip_prefix(pkg.as_str())
.is_some_and(|rest| rest.starts_with('.'))
})
}
fn type_rust_path(fq_name: &str, project_packages: &BTreeSet<&str>) -> Option<String> {
let stripped = fq_name.strip_prefix('.').unwrap_or(fq_name);
let package = package_of(fq_name, project_packages)?;
let remainder = stripped.strip_prefix(package)?.trim_start_matches('.');
if remainder.is_empty() {
return None;
}
let pkg_parts: Vec<&str> = package.split('.').collect();
let module_path = if pkg_parts.len() >= 2 {
format!(
"{}::{}",
pkg_parts[pkg_parts.len() - 2],
pkg_parts[pkg_parts.len() - 1]
)
} else {
pkg_parts[0].to_string()
};
let segments: Vec<&str> = remainder.split('.').collect();
let (leaf, parents) = segments.split_last()?;
let type_path = parents
.iter()
.map(|seg| seg.to_case(Case::Snake))
.chain(std::iter::once((*leaf).to_string()))
.collect::<Vec<_>>()
.join("::");
Some(format!("super::{module_path}::{type_path}"))
}
#[cfg(test)]
mod tests {
use super::*;
fn pkgs(list: &[&'static str]) -> BTreeSet<&'static str> {
list.iter().copied().collect()
}
#[test]
fn oneof_enum_path_splices_enum_name_onto_message_parent_module() {
let inner: syn::Path = syn::parse_str("super::catalog::v1::StorageConfig").unwrap();
let path = oneof_enum_path(&inner, "storage_config::Provider");
assert_eq!(
path.to_string().replace(' ', ""),
"super::catalog::v1::storage_config::Provider"
);
}
#[test]
fn oneof_enum_path_handles_top_level_message() {
let inner: syn::Path = syn::parse_str("Shape").unwrap();
let path = oneof_enum_path(&inner, "shape::Kind");
assert_eq!(path.to_string().replace(' ', ""), "shape::Kind");
}
#[test]
fn three_segment_package_maps_to_service_version_module() {
let p = pkgs(&["example.catalog.v1"]);
assert_eq!(
type_rust_path(".example.catalog.v1.Catalog", &p).as_deref(),
Some("super::catalog::v1::Catalog")
);
}
#[test]
fn two_segment_package_keeps_first_segment_as_module() {
let p = pkgs(&["demo.v1"]);
assert_eq!(
type_rust_path(".demo.v1.Widget", &p).as_deref(),
Some("super::demo::v1::Widget")
);
}
#[test]
fn nested_type_lives_in_snake_cased_parent_module() {
let p = pkgs(&["example.catalog.v1"]);
assert_eq!(
type_rust_path(".example.catalog.v1.Catalog.Nested", &p).as_deref(),
Some("super::catalog::v1::catalog::Nested")
);
}
#[test]
fn deeply_nested_type_snake_cases_every_parent() {
let p = pkgs(&["example.creds.v1"]);
assert_eq!(
type_rust_path(
".example.creds.v1.GenerateTableCredentialsRequest.Operation",
&p
)
.as_deref(),
Some("super::creds::v1::generate_table_credentials_request::Operation")
);
}
#[test]
fn foreign_package_is_skipped() {
let p = pkgs(&["example.catalog.v1"]);
assert_eq!(type_rust_path(".google.protobuf.Any", &p), None);
}
#[test]
fn wrapper_ident_is_py_prefixed_qualified_name() {
assert_eq!(
wrapper_ident(".example.catalog.v1.Catalog").to_string(),
"PyCatalog"
);
assert_eq!(
wrapper_ident(".example.catalog.v1.Catalog.Nested").to_string(),
"PyCatalogNested"
);
}
fn msg_field(name: &str, ty: UnifiedType) -> MessageField {
MessageField {
name: name.to_string(),
json_name: None,
unified_type: ty,
documentation: None,
oneof_variants: None,
field_behavior: vec![],
is_sensitive: false,
resource_reference: None,
}
}
fn singular_message(target: &str) -> UnifiedType {
UnifiedType {
base_type: BaseType::Message(target.to_string()),
is_optional: true,
is_repeated: false,
}
}
fn repeated_message(target: &str) -> UnifiedType {
UnifiedType {
base_type: BaseType::Message(target.to_string()),
is_optional: false,
is_repeated: true,
}
}
fn metadata_with(messages: Vec<(&str, Vec<MessageField>)>) -> CodeGenMetadata {
let mut meta = CodeGenMetadata::default();
for (name, fields) in messages {
meta.messages.insert(
name.to_string(),
MessageInfo {
name: name.to_string(),
fields,
resource_descriptor: None,
documentation: None,
},
);
}
meta
}
#[test]
fn non_recursive_singular_message_is_not_boxed() {
let meta = metadata_with(vec![
(".pkg.A", vec![msg_field("f", singular_message(".pkg.B"))]),
(".pkg.B", vec![]),
]);
assert!(boxed_fields(&meta, Runtime::Prost).is_empty());
}
#[test]
fn direct_self_recursion_boxes_the_field() {
let meta = metadata_with(vec![(
".pkg.A",
vec![msg_field("next", singular_message(".pkg.A"))],
)]);
let boxed = boxed_fields(&meta, Runtime::Prost);
assert!(boxed.contains(&(".pkg.A".to_string(), "next".to_string())));
assert_eq!(boxed.len(), 1);
}
#[test]
fn mutual_recursion_boxes_both_back_edges() {
let meta = metadata_with(vec![
(".pkg.A", vec![msg_field("b", singular_message(".pkg.B"))]),
(".pkg.B", vec![msg_field("a", singular_message(".pkg.A"))]),
]);
let boxed = boxed_fields(&meta, Runtime::Prost);
assert!(boxed.contains(&(".pkg.A".to_string(), "b".to_string())));
assert!(boxed.contains(&(".pkg.B".to_string(), "a".to_string())));
}
#[test]
fn repeated_field_breaks_the_cycle_and_is_not_boxed() {
let meta = metadata_with(vec![(
".pkg.A",
vec![msg_field("items", repeated_message(".pkg.A"))],
)]);
assert!(boxed_fields(&meta, Runtime::Prost).is_empty());
}
#[test]
fn buffa_runtime_never_boxes() {
let meta = metadata_with(vec![(
".pkg.A",
vec![msg_field("next", singular_message(".pkg.A"))],
)]);
assert!(boxed_fields(&meta, Runtime::Buffa).is_empty());
}
}