use std::collections::BTreeSet;
use cratestack_core::{Field, Model, TypeDecl};
pub(crate) fn visible_model_fields(model: &Model) -> Vec<&Field> {
model
.fields
.iter()
.filter(|field| !is_server_only_field(field))
.collect()
}
pub(crate) fn visible_type_fields(ty: &TypeDecl) -> Vec<&Field> {
ty.fields
.iter()
.filter(|field| !is_server_only_field(field))
.collect()
}
pub(crate) fn scalar_model_fields<'a>(
model: &'a Model,
model_names: &BTreeSet<&str>,
) -> Vec<&'a Field> {
model
.fields
.iter()
.filter(|field| !is_relation_field(model_names, field) && !is_server_only_field(field))
.collect()
}
fn is_relation_field(model_names: &BTreeSet<&str>, field: &Field) -> bool {
model_names.contains(field.ty.name.as_str())
}
fn is_server_only_field(field: &Field) -> bool {
field
.attributes
.iter()
.any(|attribute| attribute.raw == "@server_only")
}
pub(crate) fn model_allows_create(model: &Model) -> bool {
model
.attributes
.iter()
.filter_map(|attribute| allow_action(&attribute.raw))
.any(|action| action == "create" || action == "all")
}
fn allow_action(raw: &str) -> Option<&str> {
let inner = raw.trim().strip_prefix("@@allow(")?;
let quote = inner.chars().next()?;
if quote != '"' && quote != '\'' {
return None;
}
let rest = &inner[quote.len_utf8()..];
let end = rest.find(quote)?;
Some(&rest[..end])
}
#[cfg(test)]
mod tests {
use super::*;
fn model(source: &str) -> Model {
cratestack_parser::parse_schema(source)
.expect("schema should parse")
.models
.remove(0)
}
#[test]
fn create_gate_requires_allow_create_or_all() {
let none = model("model Widget {\n id Int @id\n}\n");
assert!(!model_allows_create(&none));
let allow_create =
model("model Widget {\n id Int @id\n\n @@allow(\"create\", true)\n}\n");
assert!(model_allows_create(&allow_create));
let allow_all = model("model Widget {\n id Int @id\n\n @@allow(\"all\", true)\n}\n");
assert!(model_allows_create(&allow_all));
let allow_read_only =
model("model Widget {\n id Int @id\n\n @@allow(\"read\", true)\n}\n");
assert!(!model_allows_create(&allow_read_only));
}
#[test]
fn scalar_model_fields_excludes_relations_and_server_only() {
let schema = cratestack_parser::parse_schema(
r#"
model Author {
id Int @id
}
model Post {
id Int @id
title String
secret String @server_only
authorId Int
author Author @relation(fields:[authorId],references:[id])
}
"#,
)
.expect("schema should parse");
let model_names: BTreeSet<&str> = schema.models.iter().map(|m| m.name.as_str()).collect();
let post = schema.models.iter().find(|m| m.name == "Post").unwrap();
let fields = scalar_model_fields(post, &model_names);
let names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
assert_eq!(names, vec!["id", "title", "authorId"]);
}
}