use cratestack_core::Model;
use cratestack_core::route_naming::model_route_segment;
use crate::diagnostics::{SchemaError, span_error};
use crate::validate::snake_case_collisions::find_collision_by;
pub(super) fn validate_model_route_collisions(models: &[Model]) -> Result<(), SchemaError> {
let entries = models
.iter()
.map(|model| (model.name.as_str(), model.name_span));
if let Some((existing, colliding, span, normalized)) =
find_collision_by(entries, model_route_segment)
{
return Err(span_error(
format!(
"model `{colliding}` collides with model `{existing}` — both route to \
`/{normalized}` (see `cratestack_core::route_naming::model_route_segment`); \
rename one of them",
),
span,
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use cratestack_core::SourceSpan;
use super::*;
fn model(name: &str) -> Model {
Model {
docs: Vec::new(),
name: name.to_owned(),
name_span: SourceSpan {
start: 0,
end: 0,
line: 1,
},
fields: Vec::new(),
attributes: Vec::new(),
span: SourceSpan {
start: 0,
end: 0,
line: 1,
},
}
}
#[test]
fn bus_and_buse_collide_on_the_pluralized_route_segment() {
let error = validate_model_route_collisions(&[model("Bus"), model("Buse")])
.expect_err("`Bus`/`Buse` both route to `/buses`");
let message = error.to_string();
assert!(message.contains("Bus") && message.contains("Buse") && message.contains("buses"));
}
#[test]
fn distinct_route_segments_pass() {
assert!(validate_model_route_collisions(&[model("Bus"), model("Car")]).is_ok());
}
}