use valence_core::{
DatabaseEvaluator, ForeignKeyRef, OwnershipConfig, Schema, SchemaConnection, SchemaEdge,
SchemaField, SchemaMeta, SchemaPolicies, SchemaPrivacy, SchemaTtlPolicy, DEFAULT_IN_MEMORY,
};
use valence_schema_dsl::{
ParsedConnection, ParsedField, ParsedOwnershipConfig, ParsedPolicies, ParsedPolicyRules,
ParsedSchema, ParsedTraitSchema, ParsedTtlPolicy,
};
use super::ParsedTraitDef;
pub fn lower_parsed_schema(parsed: &ParsedSchema) -> Schema {
let (fields, edges) = lower_fields(&parsed.fields);
let mut connections = lower_connections(&parsed.connections, &parsed.table_name);
if connections.is_empty() {
connections = edges
.iter()
.map(|e| SchemaConnection {
name: e.from_field.clone(),
from_table: parsed.table_name.clone(),
from_field: e.from_field.clone(),
to_table: e.to_table.clone(),
cardinality: "HasOne".to_string(),
required: true,
on_delete: "Cascade".to_string(),
label: e.label.clone(),
model_path: None,
reverse_field: None,
edge_table: None,
target_trait: None,
})
.collect();
}
Schema {
name: parsed.table_name.clone(),
version: parsed.version.clone(),
databases: vec![DEFAULT_IN_MEMORY.name().to_string()],
database_evaluator: &DEFAULT_IN_MEMORY,
privacy: SchemaPrivacy {
read: "public".to_string(),
write: "service".to_string(),
},
policies: parsed.policies.as_ref().map(lower_policies_names_only),
fields,
edges,
connections,
side_effects: parsed.side_effects.clone(),
iters: parsed.iters.clone(),
composite_key: parsed.composite_key.clone(),
traits: parsed.traits.clone(),
ttl: parsed.ttl.as_ref().map(lower_ttl),
ownership: parsed.ownership.as_ref().map(lower_ownership),
meta: SchemaMeta {
retention: "365 days".to_string(),
row_count: 0,
owner: "system".to_string(),
description: parsed.description.clone(),
},
}
}
pub fn lower_parsed_trait(parsed: &ParsedTraitSchema) -> ParsedTraitDef {
let (fields, _edges) = lower_fields(&parsed.fields);
let connections = lower_connections(&parsed.connections, "__trait__");
ParsedTraitDef {
name: parsed.name.clone(),
fields,
connections,
}
}
fn lower_ttl(ttl: &ParsedTtlPolicy) -> SchemaTtlPolicy {
SchemaTtlPolicy {
seconds: ttl.seconds,
mode: ttl.mode.clone(),
}
}
fn lower_ownership(o: &ParsedOwnershipConfig) -> OwnershipConfig {
OwnershipConfig {
system_owned: o.system_owned,
resolve: o.resolve.clone(),
}
}
fn lower_policies_names_only(policies: &ParsedPolicies) -> SchemaPolicies {
SchemaPolicies {
read: policies.read.as_ref().map(rules_names_only),
create: policies.create.as_ref().map(rules_names_only),
update: policies.update.as_ref().map(rules_names_only),
delete: policies.delete.as_ref().map(rules_names_only),
}
}
fn rules_names_only(rules: &ParsedPolicyRules) -> valence_core::SchemaPolicyRules {
valence_core::SchemaPolicyRules {
always_allow: rule_names(&rules.always_allow),
allow: rule_names(&rules.allow),
block: rule_names(&rules.block),
always_block: rule_names(&rules.always_block),
}
}
fn rule_names(rules: &[proc_macro2::TokenStream]) -> Vec<valence_core::SchemaPolicyRule> {
rules
.iter()
.map(|ts| valence_core::SchemaPolicyRule {
name: ts.to_string().replace(' ', ""),
description: None,
evaluator: None,
})
.collect()
}
fn lower_fields(fields: &[ParsedField]) -> (Vec<SchemaField>, Vec<SchemaEdge>) {
let mut out_fields = Vec::new();
let mut edges = Vec::new();
for field in fields {
let (enum_variants, enum_type) = enum_meta(&field.field_type);
let fk = record_table(&field.field_type).map(|ref_table| ForeignKeyRef {
ref_table: ref_table.to_string(),
field: "id".to_string(),
});
if let Some(ref fk_ref) = fk {
edges.push(SchemaEdge {
from_field: field.name.clone(),
to_table: fk_ref.ref_table.clone(),
label: edge_label(&field.name),
});
}
out_fields.push(SchemaField {
name: field.name.clone(),
field_type: field.field_type.clone(),
primary: field.primary_key,
nullable: !field.required,
indexed: false,
unique: field.unique,
default: field.default.clone(),
fk,
validations: field.validations.clone(),
policies: None,
encrypted: field.encrypted,
enum_variants,
enum_type,
});
}
(out_fields, edges)
}
fn lower_connections(connections: &[ParsedConnection], from_table: &str) -> Vec<SchemaConnection> {
connections
.iter()
.map(|c| SchemaConnection {
name: c.name.clone(),
from_table: from_table.to_string(),
from_field: c.name.clone(),
to_table: c.table.clone(),
cardinality: c.cardinality.clone(),
required: c.required,
on_delete: c.on_delete.clone(),
label: connection_display_label(&c.name),
model_path: c.model.clone(),
reverse_field: c.reverse_field.clone(),
edge_table: c.edge_table.clone(),
target_trait: c.target_trait.clone(),
})
.collect()
}
fn enum_meta(field_type: &str) -> (Vec<String>, Option<String>) {
if let Some(rest) = field_type.strip_prefix("enum:") {
let variants = rest
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
return (variants, None);
}
if let Some(path) = field_type.strip_prefix("ext_enum:") {
return (Vec::new(), Some(path.to_string()));
}
(Vec::new(), None)
}
fn record_table(field_type: &str) -> Option<&str> {
field_type
.strip_prefix("record<")
.and_then(|s| s.strip_suffix('>'))
}
fn edge_label(field_name: &str) -> String {
field_name
.strip_suffix("_id")
.unwrap_or(field_name)
.split('_')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ")
}
fn connection_display_label(name: &str) -> String {
name.split('_')
.map(|w| {
let mut c = w.chars();
match c.next() {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ")
}