use std::any::TypeId;
use crate::graphql::naming::scalar_type_name;
use crate::read_model::RelationalReadModel;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphqlTypeField {
pub name: String,
pub type_name: String,
pub nullable: bool,
pub list: bool,
pub item_nullable: bool,
pub nested: Option<Box<GraphqlTypeDef>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphqlTypeDef {
pub name: String,
pub fields: Vec<GraphqlTypeField>,
pub type_id: Option<TypeId>,
}
impl GraphqlTypeDef {
pub fn new(name: impl Into<String>, fields: Vec<GraphqlTypeField>) -> Self {
Self {
name: name.into(),
fields,
type_id: None,
}
}
pub fn with_type_id(mut self, id: TypeId) -> Self {
self.type_id = Some(id);
self
}
pub fn transitive_nested(&self) -> Vec<GraphqlTypeDef> {
let mut out = Vec::new();
let mut seen = std::collections::BTreeSet::new();
self.collect_nested(&mut out, &mut seen);
out
}
fn collect_nested(
&self,
out: &mut Vec<GraphqlTypeDef>,
seen: &mut std::collections::BTreeSet<String>,
) {
for field in &self.fields {
if let Some(nested) = &field.nested {
if seen.insert(nested.name.clone()) {
out.push((**nested).clone());
nested.collect_nested(out, seen);
}
}
}
}
}
pub trait GraphqlInputType {
fn graphql_type() -> GraphqlTypeDef;
}
pub trait GraphqlOutputType {
fn graphql_type() -> GraphqlTypeDef;
}
pub(crate) fn read_model_graphql_type<M>() -> GraphqlTypeDef
where
M: RelationalReadModel + 'static,
{
let schema = M::schema();
let fields = schema
.columns
.iter()
.filter(|column| !column.skipped)
.map(|column| {
let type_name = scalar_type_name(&column.column_type).unwrap_or_else(|| {
panic!(
"read model `{}` column `{}` has no GraphQL scalar mapping",
schema.model_name, column.column_name
)
});
GraphqlTypeField {
name: column.column_name.clone(),
type_name: type_name.into(),
nullable: column.nullable,
list: false,
item_nullable: false,
nested: None,
}
})
.collect();
GraphqlTypeDef::new(schema.model_name.clone(), fields).with_type_id(TypeId::of::<M>())
}
#[allow(dead_code)]
pub fn scalar_for_rust_type(ty: &str) -> Option<&'static str> {
match ty {
"String" | "str" => Some("String"),
"bool" => Some("Boolean"),
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" | "isize" | "usize" => {
Some("BigInt")
}
"f32" | "f64" => Some("Float"),
"Value" | "serde_json::Value" => Some("JSON"),
_ => None,
}
}