use crate::{
error::{FraiseQLError, Result},
schema::CompiledSchema,
security::SecurityContext,
};
#[must_use]
pub fn caller_holds(required: Option<&str>, security_context: Option<&SecurityContext>) -> bool {
required
.is_none_or(|role| security_context.is_some_and(|ctx| ctx.roles.iter().any(|r| r == role)))
}
#[must_use]
pub fn visible_schema(schema: &CompiledSchema, caller: Option<&SecurityContext>) -> CompiledSchema {
visible_schema_where(schema, |required| caller_holds(required, caller))
}
#[must_use]
pub(crate) fn visible_schema_where(
schema: &CompiledSchema,
holds: impl Fn(Option<&str>) -> bool,
) -> CompiledSchema {
let mut visible = schema.clone();
visible.queries.retain(|q| holds(q.requires_role.as_deref()));
visible.mutations.retain(|m| holds(m.requires_role.as_deref()));
visible.types.retain(|t| holds(t.requires_role.as_deref()));
let hidden: Vec<&str> = schema
.types
.iter()
.filter(|t| !holds(t.requires_role.as_deref()))
.map(|t| t.name.as_str())
.collect();
for union in &mut visible.unions {
union.member_types.retain(|member| !hidden.contains(&member.as_str()));
}
visible
.fact_tables
.retain(|_, table| table.type_name.as_deref().is_none_or(|name| !hidden.contains(&name)));
let hidden_rows: Vec<&str> = schema
.derived
.fact_roots
.iter()
.filter(|root| !visible.fact_tables.contains_key(&root.fact_table))
.filter_map(|root| root.row_type.as_deref())
.collect();
visible
.derived
.fact_roots
.retain(|root| visible.fact_tables.contains_key(&root.fact_table));
visible
.derived
.fact_rows
.retain(|row| !hidden_rows.contains(&row.name.as_str()));
if visible.queries.len() < schema.queries.len()
|| visible.mutations.len() < schema.mutations.len()
|| !hidden.is_empty()
{
visible.schema_sdl = None;
}
visible
}
pub fn enforce_requires_role(
operation_kind: &str,
operation_name: &str,
required: Option<&str>,
security_context: Option<&SecurityContext>,
) -> Result<()> {
if caller_holds(required, security_context) {
return Ok(());
}
Err(FraiseQLError::Validation {
message: format!("{operation_kind} '{operation_name}' not found in schema"),
path: None,
})
}
#[cfg(test)]
mod tests;