diff --git a/yopa-web/resources/templates/index.html.tera b/yopa-web/resources/templates/index.html.tera
index 86b626b..3a3e3b1 100644
--- a/yopa-web/resources/templates/index.html.tera
+++ b/yopa-web/resources/templates/index.html.tera
@@ -45,7 +45,7 @@
{% for rel in model.relations %}
-
- "{{rel.model.name}}", pointing to: {{rel.related_name}}
+ "{{rel.model.name}}", pointing to: {{rel.related_name}} (reciprocal as "{{rel.model.reciprocal_name}}")
{%- if rel.model.optional %}, OPTIONAL{% endif %}
{%- if rel.model.multiple %}, MULTIPLE{% endif %}
diff --git a/yopa-web/resources/templates/relation_create.html.tera b/yopa-web/resources/templates/relation_create.html.tera
index b84ac8f..fd4aa5d 100644
--- a/yopa-web/resources/templates/relation_create.html.tera
+++ b/yopa-web/resources/templates/relation_create.html.tera
@@ -20,6 +20,9 @@ Define relation
+
+
+
diff --git a/yopa-web/src/main.rs b/yopa-web/src/main.rs
index c892905..fad43a2 100644
--- a/yopa-web/src/main.rs
+++ b/yopa-web/src/main.rs
@@ -168,6 +168,7 @@ fn init_yopa() -> YopaStoreWrapper {
id: Default::default(),
object: id_recipe,
name: "book reference".to_string(),
+ reciprocal_name: "recipes".to_string(),
optional: true,
multiple: true,
related: id_book
@@ -187,6 +188,7 @@ fn init_yopa() -> YopaStoreWrapper {
id: Default::default(),
object: id_recipe,
name: "related recipe".to_string(),
+ reciprocal_name: "related recipe".to_string(),
optional: true,
multiple: true,
related: id_recipe
diff --git a/yopa-web/src/routes.rs b/yopa-web/src/routes.rs
index a415229..3b165fb 100644
--- a/yopa-web/src/routes.rs
+++ b/yopa-web/src/routes.rs
@@ -178,6 +178,7 @@ pub(crate) async fn relation_model_create_form(
pub(crate) struct RelationModelCreate {
pub object : ID,
pub name : String,
+ pub reciprocal_name : String,
pub optional : Option,
pub multiple : Option,
pub related : ID,
@@ -195,6 +196,7 @@ pub(crate) async fn relation_model_create(
id: Default::default(),
object: form.object,
name: form.name.clone(),
+ reciprocal_name: form.reciprocal_name.clone(),
optional: form.optional.unwrap_or_default() != 0,
multiple: form.multiple.unwrap_or_default() != 0,
related: form.related
diff --git a/yopa/src/lib.rs b/yopa/src/lib.rs
index c8a5857..821d484 100644
--- a/yopa/src/lib.rs
+++ b/yopa/src/lib.rs
@@ -92,12 +92,22 @@ impl Storage {
return Err(StorageError::NotExist(format!("related object model {}", rel.related).into()));
}
- if self.rel_models.iter().find(|(_, t)| t.name == rel.name && t.object == rel.object).is_some() {
+ if let Some((_, colliding)) = self.rel_models.iter().find(|(_, other)| {
+ (other.name == rel.name && other.object == rel.object) // Exact match
+ || (other.name == rel.reciprocal_name && other.object == rel.related) // Our reciprocal name collides with related's own relation name
+ || (other.reciprocal_name == rel.name && other.related == rel.object) // Our name name collides with a reciprocal name on the other relation
+ || (other.reciprocal_name == rel.reciprocal_name && other.related == rel.related) // Reciprocal names collide for the same destination
+ }) {
return Err(StorageError::ConstraintViolation(
- format!("relation with the name \"{}\" and on model {} already exists", rel.name, rel.object).into()));
+ format!("name collision (\"{}\" / \"{}\") with existing relation (\"{}\" / \"{}\")",
+ rel.name, rel.reciprocal_name,
+ colliding.name, colliding.reciprocal_name
+ ).into()));
}
- debug!("Define relation model \"{}\" from {} to {}", rel.name, self.describe_model(rel.object), self.describe_model(rel.related));
+ debug!("Define relation model \"{}\" from {} to {}, reciprocal name \"{}\"",
+ rel.name, self.describe_model(rel.object), self.describe_model(rel.related), rel.reciprocal_name);
+
let id = next_id();
rel.id = id;
self.rel_models.insert(id, rel);
diff --git a/yopa/src/model.rs b/yopa/src/model.rs
index acfd88a..1182404 100644
--- a/yopa/src/model.rs
+++ b/yopa/src/model.rs
@@ -35,6 +35,8 @@ pub struct RelationModel {
pub object: ID,
/// Relation name, unique within the parent object
pub name: String,
+ /// Relation name when viewed from the other side, unique within the related object's relations
+ pub reciprocal_name: String,
/// Relation is optional
pub optional: bool,
/// Relation can be multiple