use serde_json::{Map, Value};
use super::types::{DefaultValue, FieldSpec, IndexKind, TableSchema, SCHEMA_VERSION};
fn index_kind_str(kind: IndexKind) -> &'static str {
match kind {
IndexKind::Exact => "exact",
IndexKind::Sorted => "sorted",
}
}
pub fn field_document(spec: &FieldSpec) -> Value {
let mut doc = Map::new();
doc.insert("type".to_string(), Value::from(spec.field_type.as_str()));
if spec.required {
doc.insert("required".to_string(), Value::Bool(true));
}
if spec.unique {
doc.insert("unique".to_string(), Value::Bool(true));
}
if spec.fulltext {
doc.insert("fulltext".to_string(), Value::Bool(true));
}
if spec.cypher {
doc.insert("cypher".to_string(), Value::Bool(true));
}
if !spec.addressable {
doc.insert("addressable".to_string(), Value::Bool(false));
}
if let Some(index) = spec.index {
doc.insert("index".to_string(), Value::from(index_kind_str(index)));
}
if let Some(values) = &spec.values {
doc.insert("values".to_string(), Value::from(values.clone()));
}
match &spec.default {
Some(DefaultValue::Now) => {
doc.insert("default".to_string(), Value::from("now"));
}
Some(DefaultValue::Value(value)) => {
doc.insert("default".to_string(), value.clone());
}
None => {}
}
Value::Object(doc)
}
pub fn schema_document(schema: &TableSchema) -> Value {
let mut doc = Map::new();
doc.insert("schemaVersion".to_string(), Value::from(SCHEMA_VERSION));
doc.insert("table".to_string(), Value::from(schema.table.clone()));
doc.insert("revision".to_string(), Value::from(schema.revision));
doc.insert("primaryKey".to_string(), Value::from(schema.primary_key.clone()));
if let Some(partition_by) = &schema.partition_by {
doc.insert("partitionBy".to_string(), Value::from(partition_by.clone()));
}
let mut fields = Map::new();
for (name, spec) in &schema.fields {
fields.insert(name.clone(), field_document(spec));
}
doc.insert("fields".to_string(), Value::Object(fields));
Value::Object(doc)
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::super::parse::parse_schema;
use super::*;
fn parsed(document: Value) -> TableSchema {
parse_schema(&document).expect("the fixture is a usable schema")
}
#[test]
fn renders_only_the_modifiers_a_field_actually_carries() {
let schema = parsed(json!({
"schemaVersion": 1,
"table": "users",
"primaryKey": "id",
"fields": {
"id": { "type": "string" },
"bio": { "type": "string" },
},
}));
let document = schema_document(&schema);
assert_eq!(document["fields"]["bio"], json!({ "type": "string" }));
assert_eq!(
document["fields"]["id"],
json!({ "type": "string", "required": true, "unique": true, "index": "exact" })
);
}
#[test]
fn keeps_the_key_order_the_ts_document_has() {
let schema = parsed(json!({
"schemaVersion": 1,
"table": "users",
"revision": 4,
"primaryKey": "id",
"partitionBy": "tenant",
"fields": {
"id": { "type": "string" },
"tenant": { "type": "string", "required": true },
"role": { "type": "enum", "values": ["admin"], "index": "exact", "default": "admin" },
},
}));
let document = schema_document(&schema);
let keys: Vec<&String> = document.as_object().unwrap().keys().collect();
assert_eq!(
keys,
vec![
"schemaVersion",
"table",
"revision",
"primaryKey",
"partitionBy",
"fields"
]
);
let role: Vec<&String> = document["fields"]["role"].as_object().unwrap().keys().collect();
assert_eq!(role, vec!["type", "index", "values", "default"]);
let fields: Vec<&String> = document["fields"].as_object().unwrap().keys().collect();
assert_eq!(fields, vec!["id", "role", "tenant"]);
}
#[test]
fn leaves_partition_by_out_of_a_table_that_declares_none() {
let schema = parsed(json!({
"schemaVersion": 1,
"table": "users",
"primaryKey": "id",
"fields": { "id": { "type": "string" } },
}));
let document = schema_document(&schema);
assert!(document.as_object().unwrap().get("partitionBy").is_none());
assert_eq!(document["revision"], json!(1));
}
#[test]
fn renders_a_cypher_field_as_the_bare_fact_of_it() {
let schema = parsed(json!({
"schemaVersion": 1,
"table": "secrets",
"primaryKey": "id",
"fields": {
"id": { "type": "string" },
"token": { "type": "string", "cypher": true },
},
}));
let document = schema_document(&schema);
assert_eq!(document["fields"]["token"], json!({ "type": "string", "cypher": true }));
}
#[test]
fn round_trips_through_parse_schema() {
let schema = parsed(json!({
"schemaVersion": 1,
"table": "everything",
"revision": 2,
"primaryKey": "id",
"partitionBy": "tenant",
"fields": {
"id": { "type": "string" },
"tenant": { "type": "string", "required": true },
"page": { "type": "markdown" },
"cover": { "type": "image" },
"tags": { "type": "string[]", "index": "exact", "default": ["new"] },
"seenAt": { "type": "timestamp", "default": "now", "index": "sorted" },
"live": { "type": "bool", "default": false },
"notes": { "type": "string", "fulltext": true },
"slug": { "type": "string", "unique": true },
},
}));
let again = parse_schema(&schema_document(&schema)).expect("the rendered document parses");
assert_eq!(again, schema);
}
}