use serde::{Deserialize, Serialize};
use vantage_vista::VistaSpec;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SurrealTableExtras {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub surreal: Option<SurrealTableBlock>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SurrealTableBlock {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub table: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rhai: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub inherit: Option<InheritBlock>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub modify: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct InheritBlock {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub columns: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub relations: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SurrealColumnExtras {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub surreal: Option<SurrealColumnBlock>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SurrealColumnBlock {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SurrealReferenceExtras {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub surreal: Option<SurrealRefBlock>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SurrealRefBlock {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rhai: Option<String>,
}
pub type SurrealVistaSpec =
VistaSpec<SurrealTableExtras, SurrealColumnExtras, SurrealReferenceExtras>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn yaml_parses_into_surreal_vista_spec() {
let yaml = r#"
name: client
columns:
id:
type: thing
flags: [id]
name:
type: string
flags: [title, searchable]
is_paying_client:
type: bool
surreal:
table: clients
"#;
let spec: SurrealVistaSpec = serde_yaml_ng::from_str(yaml).unwrap();
assert_eq!(spec.name, "client");
assert_eq!(spec.columns.len(), 3);
assert_eq!(
spec.driver
.surreal
.as_ref()
.and_then(|m| m.table.as_deref()),
Some("clients")
);
assert_eq!(spec.columns["id"].col_type.as_deref(), Some("thing"));
assert!(spec.columns["name"].flags.contains(&"title".to_string()));
}
#[test]
fn yaml_rejects_unknown_surreal_block_field() {
let yaml = r#"
name: client
columns:
id: { type: thing, flags: [id] }
surreal:
table: clients
bogus: 1
"#;
let err = serde_yaml_ng::from_str::<SurrealVistaSpec>(yaml).unwrap_err();
assert!(err.to_string().contains("bogus") || err.to_string().contains("unknown"));
}
#[test]
fn yaml_table_defaults_to_spec_name_when_block_omitted() {
let yaml = r#"
name: clients
columns:
id: { type: thing, flags: [id] }
"#;
let spec: SurrealVistaSpec = serde_yaml_ng::from_str(yaml).unwrap();
assert!(spec.driver.surreal.is_none());
}
#[test]
fn yaml_parses_base_inherit_and_rhai() {
let yaml = r#"
name: vip_clients
columns: {}
surreal:
base: client
inherit:
columns: [id, name]
relations: [orders]
rhai: |
base.where(expr("name = 'Alice'"))
"#;
let spec: SurrealVistaSpec = serde_yaml_ng::from_str(yaml).unwrap();
let block = spec.driver.surreal.as_ref().expect("surreal block");
assert_eq!(block.base.as_deref(), Some("client"));
assert!(block.rhai.as_deref().unwrap().contains("base.where"));
let inherit = block.inherit.as_ref().expect("inherit block");
assert_eq!(inherit.columns, vec!["id", "name"]);
assert_eq!(inherit.relations, vec!["orders"]);
}
#[test]
fn yaml_parses_references_block() {
let yaml = r#"
name: bakery
columns:
id: { type: thing, flags: [id] }
references:
clients:
table: client
kind: has_many
foreign_key: bakery
primary_product:
table: product
kind: has_one
foreign_key: primary_product
"#;
let spec: SurrealVistaSpec = serde_yaml_ng::from_str(yaml).unwrap();
assert_eq!(spec.references.len(), 2);
let clients = &spec.references["clients"];
assert_eq!(clients.table, "client");
assert_eq!(clients.kind, vantage_vista::ReferenceKind::HasMany);
assert_eq!(clients.foreign_key.as_deref(), Some("bakery"));
let primary = &spec.references["primary_product"];
assert_eq!(primary.table, "product");
assert_eq!(primary.kind, vantage_vista::ReferenceKind::HasOne);
}
}