use std::collections::BTreeSet;
use cratestack_core::{Field, Model};
use quote::quote;
use crate::shared::{ident, is_server_only_field, scalar_model_fields};
use super::super::prep::ModelHandlerPrep;
fn field_insert_tokens(
model: &Model,
model_names: &BTreeSet<&str>,
) -> Vec<proc_macro2::TokenStream> {
scalar_model_fields(model, model_names)
.into_iter()
.filter(|field| !is_server_only_field(field))
.map(|field: &Field| {
let field_ident = ident(&field.name);
let field_name = &field.name;
quote! {
object.insert(
#field_name.to_owned(),
::cratestack::ProjectedValue::leaf(record.#field_ident.clone()),
);
}
})
.collect()
}
pub(in super::super) fn build_projection_helpers(
p: &ModelHandlerPrep,
model: &Model,
model_names: &BTreeSet<&str>,
) -> proc_macro2::TokenStream {
let project_object_fields_ident = &p.project_object_fields_ident;
let project_serialized_value_ident = &p.project_serialized_value_ident;
let project_model_value_ident = &p.project_model_value_ident;
let model_ident = &p.model_ident;
let field_inserts = field_insert_tokens(model, model_names);
quote! {
fn #project_object_fields_ident(
object: ::std::collections::BTreeMap<String, ::cratestack::ProjectedValue>,
fields: &[String],
context: &str,
) -> Result<::std::collections::BTreeMap<String, ::cratestack::ProjectedValue>, CoolError> {
let mut object = object;
let mut projected = ::std::collections::BTreeMap::new();
for field in fields {
let value = object.remove(field).ok_or_else(|| {
CoolError::Internal(format!(
"serialized relation '{}' is missing field '{}'",
context,
field,
))
})?;
projected.insert(field.clone(), value);
}
Ok(projected)
}
fn #project_serialized_value_ident(
value: ::cratestack::ProjectedValue,
fields: Option<&[String]>,
context: &str,
) -> Result<::cratestack::ProjectedValue, CoolError> {
let Some(fields) = fields else {
return Ok(value);
};
match value {
::cratestack::ProjectedValue::Null => Ok(::cratestack::ProjectedValue::Null),
::cratestack::ProjectedValue::Object(object) => Ok(::cratestack::ProjectedValue::Object(
#project_object_fields_ident(object, fields, context)?,
)),
::cratestack::ProjectedValue::Array(values) => {
let mut projected = Vec::with_capacity(values.len());
for value in values {
projected.push(#project_serialized_value_ident(value, Some(fields), context)?);
}
Ok(::cratestack::ProjectedValue::Array(projected))
}
::cratestack::ProjectedValue::Leaf(_) => Err(CoolError::Internal(format!(
"included relation '{}' must serialize to an object, array, or null",
context,
))),
}
}
fn #project_model_value_ident(
record: &super::models::#model_ident,
fields: Option<&[String]>,
) -> Result<::std::collections::BTreeMap<String, ::cratestack::ProjectedValue>, CoolError> {
let mut object = ::std::collections::BTreeMap::new();
#(#field_inserts)*
let Some(fields) = fields else {
return Ok(object);
};
let mut projected = ::std::collections::BTreeMap::new();
for field in fields {
if let Some(value) = object.remove(field) {
projected.insert(field.clone(), value);
}
}
Ok(projected)
}
}
}