use crate::schema::object::to_pascal_case;
use postrust_core::schema_cache::Relationship;
fn get_constraint_name(rel: &Relationship) -> &str {
match rel {
Relationship::ForeignKey { constraint_name, .. } => constraint_name,
Relationship::Computed { function, .. } => &function.name,
}
}
#[derive(Debug, Clone)]
pub struct RelationshipField {
pub name: String,
pub target_type: String,
pub is_list: bool,
pub relationship: Relationship,
pub description: Option<String>,
}
impl RelationshipField {
pub fn from_relationship(rel: &Relationship) -> Self {
let foreign_table = rel.foreign_table();
let is_list = !rel.is_to_one();
let name = if is_list {
pluralize(&foreign_table.name)
} else {
singularize(&foreign_table.name)
};
let target_type = to_pascal_case(&foreign_table.name);
let description = Some(format!(
"Related {} via {}",
if is_list { "records" } else { "record" },
get_constraint_name(rel)
));
Self {
name,
target_type,
is_list,
relationship: rel.clone(),
description,
}
}
pub fn type_string(&self) -> String {
if self.is_list {
format!("[{}!]!", self.target_type)
} else {
self.target_type.clone()
}
}
pub fn join_columns(&self) -> Vec<(String, String)> {
self.relationship.join_columns()
}
}
fn pluralize(s: &str) -> String {
if s.ends_with('s') && !s.ends_with("ss") {
return s.to_string();
}
if s.ends_with('x') || s.ends_with("ch") || s.ends_with("sh") || s.ends_with("ss") {
format!("{}es", s)
} else if s.ends_with('y') && !s.ends_with("ey") && !s.ends_with("ay") && !s.ends_with("oy") {
format!("{}ies", &s[..s.len() - 1])
} else {
format!("{}s", s)
}
}
fn singularize(s: &str) -> String {
if s.ends_with("ies") {
format!("{}y", &s[..s.len() - 3])
} else if s.ends_with("es") && (s.ends_with("ses") || s.ends_with("xes") || s.ends_with("ches") || s.ends_with("shes")) {
s[..s.len() - 2].to_string()
} else if s.ends_with('s') && !s.ends_with("ss") {
s[..s.len() - 1].to_string()
} else {
s.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use postrust_core::api_request::QualifiedIdentifier;
use postrust_core::schema_cache::Cardinality;
use pretty_assertions::assert_eq;
fn create_m2o_relationship() -> Relationship {
Relationship::ForeignKey {
table: QualifiedIdentifier::new("public", "orders"),
foreign_table: QualifiedIdentifier::new("public", "users"),
is_self: false,
cardinality: Cardinality::M2O {
constraint: "orders_user_id_fkey".into(),
columns: vec![("user_id".into(), "id".into())],
},
table_is_view: false,
foreign_table_is_view: false,
constraint_name: "orders_user_id_fkey".into(),
}
}
fn create_o2m_relationship() -> Relationship {
Relationship::ForeignKey {
table: QualifiedIdentifier::new("public", "users"),
foreign_table: QualifiedIdentifier::new("public", "orders"),
is_self: false,
cardinality: Cardinality::O2M {
constraint: "orders_user_id_fkey".into(),
columns: vec![("id".into(), "user_id".into())],
},
table_is_view: false,
foreign_table_is_view: false,
constraint_name: "orders_user_id_fkey".into(),
}
}
fn create_o2o_relationship() -> Relationship {
Relationship::ForeignKey {
table: QualifiedIdentifier::new("public", "users"),
foreign_table: QualifiedIdentifier::new("public", "user_profiles"),
is_self: false,
cardinality: Cardinality::O2O {
constraint: "user_profiles_user_id_fkey".into(),
columns: vec![("id".into(), "user_id".into())],
is_parent: true,
},
table_is_view: false,
foreign_table_is_view: false,
constraint_name: "user_profiles_user_id_fkey".into(),
}
}
#[test]
fn test_pluralize() {
assert_eq!(pluralize("user"), "users");
assert_eq!(pluralize("order"), "orders");
assert_eq!(pluralize("category"), "categories");
assert_eq!(pluralize("box"), "boxes");
assert_eq!(pluralize("match"), "matches");
assert_eq!(pluralize("dish"), "dishes");
assert_eq!(pluralize("key"), "keys"); assert_eq!(pluralize("day"), "days"); }
#[test]
fn test_singularize() {
assert_eq!(singularize("users"), "user");
assert_eq!(singularize("orders"), "order");
assert_eq!(singularize("categories"), "category");
assert_eq!(singularize("boxes"), "box");
assert_eq!(singularize("matches"), "match");
assert_eq!(singularize("class"), "class"); }
#[test]
fn test_m2o_relationship_field() {
let rel = create_m2o_relationship();
let field = RelationshipField::from_relationship(&rel);
assert_eq!(field.name, "user"); assert_eq!(field.target_type, "Users");
assert!(!field.is_list); }
#[test]
fn test_o2m_relationship_field() {
let rel = create_o2m_relationship();
let field = RelationshipField::from_relationship(&rel);
assert_eq!(field.name, "orders"); assert_eq!(field.target_type, "Orders");
assert!(field.is_list); }
#[test]
fn test_o2o_relationship_field() {
let rel = create_o2o_relationship();
let field = RelationshipField::from_relationship(&rel);
assert_eq!(field.name, "user_profile"); assert_eq!(field.target_type, "UserProfiles");
assert!(!field.is_list); }
#[test]
fn test_relationship_type_string_list() {
let rel = create_o2m_relationship();
let field = RelationshipField::from_relationship(&rel);
assert_eq!(field.type_string(), "[Orders!]!");
}
#[test]
fn test_relationship_type_string_single() {
let rel = create_m2o_relationship();
let field = RelationshipField::from_relationship(&rel);
assert_eq!(field.type_string(), "Users");
}
#[test]
fn test_relationship_join_columns() {
let rel = create_m2o_relationship();
let field = RelationshipField::from_relationship(&rel);
let columns = field.join_columns();
assert_eq!(columns.len(), 1);
assert_eq!(columns[0], ("user_id".into(), "id".into()));
}
#[test]
fn test_relationship_description() {
let rel = create_m2o_relationship();
let field = RelationshipField::from_relationship(&rel);
assert!(field.description.is_some());
assert!(field.description.as_ref().unwrap().contains("orders_user_id_fkey"));
}
#[test]
fn test_m2m_relationship_field() {
let rel = Relationship::ForeignKey {
table: QualifiedIdentifier::new("public", "users"),
foreign_table: QualifiedIdentifier::new("public", "tags"),
is_self: false,
cardinality: Cardinality::M2M(postrust_core::schema_cache::Junction {
table: QualifiedIdentifier::new("public", "user_tags"),
constraint1: "user_tags_user_id_fkey".into(),
constraint2: "user_tags_tag_id_fkey".into(),
source_columns: vec![("id".into(), "user_id".into())],
target_columns: vec![("tag_id".into(), "id".into())],
}),
table_is_view: false,
foreign_table_is_view: false,
constraint_name: "user_tags_user_id_fkey".into(),
};
let field = RelationshipField::from_relationship(&rel);
assert_eq!(field.name, "tags"); assert_eq!(field.target_type, "Tags");
assert!(field.is_list);
}
}