use cratestack_core::{Model, TypeDecl, View};
use quote::quote;
use crate::policy::{generate_denies_for_actions, generate_policies_for_actions};
use crate::shared::{ident, pluralize, to_snake_case};
pub(crate) fn generate_view_descriptor(
view: &View,
models: &[Model],
types: &[TypeDecl],
auth: Option<&cratestack_core::AuthBlock>,
) -> Result<proc_macro2::TokenStream, String> {
let view_ident = ident(&view.name);
let descriptor_ident = ident(&format!(
"{}_VIEW",
to_snake_case(&view.name).to_uppercase()
));
let view_sql_name = pluralize(&to_snake_case(&view.name));
let primary_key_sql = if view.no_unique() {
String::new()
} else {
let pk_field = view
.fields
.iter()
.find(|field| field.attributes.iter().any(|attr| attr.raw == "@id"))
.ok_or_else(|| {
format!(
"view `{}` has no @id field and is not @@no_unique (validator should have caught this)",
view.name
)
})?;
to_snake_case(&pk_field.name)
};
let primary_key_type = if view.no_unique() {
quote! { () }
} else {
let pk_field = view
.fields
.iter()
.find(|field| field.attributes.iter().any(|attr| attr.raw == "@id"))
.expect("validated view has @id when not @@no_unique");
crate::shared::rust_type_tokens(&pk_field.ty)
};
let columns: Vec<_> = view
.fields
.iter()
.map(|field| {
let rust_name = &field.name;
let sql_name = to_snake_case(&field.name);
quote! {
::cratestack::ModelColumn {
rust_name: #rust_name,
sql_name: #sql_name,
}
}
})
.collect();
let allowed_fields: Vec<_> = view
.fields
.iter()
.map(|field| {
let name = &field.name;
quote! { #name }
})
.collect();
let source_tables: Vec<_> = view
.sources
.iter()
.map(|source| {
let name = crate::shared::pluralize(&to_snake_case(&source.name));
quote! { #name }
})
.collect();
let is_materialized = view.is_materialized();
let synthetic = view_as_model(view);
let read_allow = generate_policies_for_actions(&synthetic, models, types, auth, &["read"])?;
let read_deny = generate_denies_for_actions(&synthetic, models, types, auth, &["read"])?;
let detail_allow = read_allow.clone();
let detail_deny = read_deny.clone();
Ok(quote! {
pub const #descriptor_ident: ::cratestack::ViewDescriptor<#view_ident, #primary_key_type> =
::cratestack::ViewDescriptor::new(
"public",
#view_sql_name,
&[#(#columns),*],
#primary_key_sql,
&[#(#allowed_fields),*],
&[#(#allowed_fields),*],
&[#(#read_allow),*],
&[#(#read_deny),*],
&[#(#detail_allow),*],
&[#(#detail_deny),*],
#is_materialized,
&[#(#source_tables),*],
);
})
}
fn view_as_model(view: &View) -> Model {
Model {
docs: view.docs.clone(),
name: view.name.clone(),
name_span: view.name_span,
fields: view.fields.clone(),
attributes: view.attributes.clone(),
span: view.span,
}
}