use std::collections::HashMap;
use serde::Deserialize;
use crate::query::error::QueryError;
use crate::query::ir::{EsStorage, FieldRef, FieldType, JunctionRef, MongoStorage, RelRef};
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct EntityRegistry {
pub entities: HashMap<String, Entity>,
pub unmapped: UnmappedPolicy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UnmappedPolicy {
Reject,
#[default]
Identity,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct Entity {
pub physical: Option<String>,
pub columns: HashMap<String, Column>,
pub relations: HashMap<String, Relation>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Column {
#[serde(default)]
pub name: Option<String>,
#[serde(rename = "type", default)]
pub ty: FieldType,
#[serde(default = "default_true")]
pub queryable: bool,
#[serde(default = "default_true")]
pub writable: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Deserialize)]
pub struct Relation {
pub to: String,
#[serde(default)]
pub kind: Cardinality,
pub local: String,
pub foreign: String,
#[serde(default)]
pub through: Option<Junction>,
#[serde(default)]
pub mongo: MongoStorage,
#[serde(default)]
pub es: EsStorage,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Cardinality {
HasOne,
#[default]
HasMany,
ManyToMany,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Junction {
pub table: String,
pub local: String,
pub foreign: String,
}
impl EntityRegistry {
pub fn from_json(v: &serde_json::Value) -> Result<Self, QueryError> {
serde_json::from_value(v.clone())
.map_err(|e| QueryError::InvalidEnvelope(format!("invalid schema: {e}")))
}
pub fn physical_table(&self, entity: &str) -> String {
self.entities
.get(entity)
.and_then(|e| e.physical.clone())
.unwrap_or_else(|| entity.to_string())
}
pub fn resolve_field(
&self,
entity: &str,
name: &str,
at: &str,
) -> Result<FieldRef, QueryError> {
if let Some(col) = self.entities.get(entity).and_then(|e| e.columns.get(name)) {
if !col.queryable {
return Err(QueryError::InvalidField {
field: name.to_string(),
at: at.to_string(),
});
}
return Ok(FieldRef {
path: vec![name.to_string()],
physical: col.name.clone().unwrap_or_else(|| name.to_string()),
ty: col.ty,
});
}
match self.unmapped {
UnmappedPolicy::Identity => Ok(FieldRef::identity(name)),
UnmappedPolicy::Reject => Err(QueryError::InvalidField {
field: name.to_string(),
at: at.to_string(),
}),
}
}
pub fn resolve_write_column(
&self,
entity: &str,
name: &str,
at: &str,
) -> Result<String, QueryError> {
if let Some(col) = self.entities.get(entity).and_then(|e| e.columns.get(name)) {
if !col.writable {
return Err(QueryError::InvalidField {
field: name.to_string(),
at: at.to_string(),
});
}
return Ok(col.name.clone().unwrap_or_else(|| name.to_string()));
}
match self.unmapped {
UnmappedPolicy::Identity => Ok(name.to_string()),
UnmappedPolicy::Reject => Err(QueryError::InvalidField {
field: name.to_string(),
at: at.to_string(),
}),
}
}
pub fn resolve_relation(
&self,
entity: &str,
name: &str,
at: &str,
) -> Result<(RelRef, String), QueryError> {
let rel = self
.entities
.get(entity)
.and_then(|e| e.relations.get(name))
.ok_or_else(|| QueryError::UnknownRelation {
relation: name.to_string(),
at: at.to_string(),
})?;
let through = rel.through.as_ref().map(|j| JunctionRef {
table: j.table.clone(),
local: j.local.clone(),
foreign: j.foreign.clone(),
});
Ok((
RelRef {
name: name.to_string(),
target_table: self.physical_table(&rel.to),
local: rel.local.clone(),
foreign: rel.foreign.clone(),
through,
mongo: rel.mongo,
es: rel.es,
},
rel.to.clone(),
))
}
}