#![forbid(unsafe_code)]
use super::{
tds::{EntityKind, SimplexKey, TdsConstructionError},
traits::{DataDeserialize, DataSerialize},
util::{UuidValidationError, make_uuid, validate_uuid},
};
use crate::geometry::{
point::Point,
traits::coordinate::{Coordinate, CoordinateConversionError, CoordinateValidationError},
};
use serde::{
Deserialize, Serialize,
de::{self, IgnoredAny, MapAccess, Visitor},
ser::SerializeStruct,
};
use std::{
cmp::Ordering,
collections::HashMap,
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
};
use thiserror::Error;
use uuid::Uuid;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum VertexValidationError {
#[error("Invalid point: {source}")]
InvalidPoint {
#[from]
source: CoordinateValidationError,
},
#[error("Invalid UUID: {source}")]
InvalidUuid {
#[from]
source: UuidValidationError,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VertexValidationReport {
pub violations: Vec<VertexValidationError>,
}
impl VertexValidationReport {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.violations.is_empty()
}
#[must_use]
pub fn violations(&self) -> &[VertexValidationError] {
&self.violations
}
}
#[macro_export]
macro_rules! vertex {
($coords:expr; data = $data:expr $(,)?) => {
$crate::tds::Vertex::<_, _>::try_new_with_data($coords, $data)
};
($($coord:expr),+; data = $data:expr $(,)?) => {
$crate::tds::Vertex::<_, _>::try_new_with_data([$($coord),+], $data)
};
($coords:expr $(,)?) => {
$crate::tds::Vertex::<(), _>::try_new($coords)
};
($($coord:expr),+ $(,)?) => {
$crate::tds::Vertex::<(), _>::try_new([$($coord),+])
};
}
#[derive(Clone, Copy, Debug)]
pub struct Vertex<U, const D: usize> {
point: Point<D>,
uuid: Uuid,
pub(crate) incident_simplex: Option<SimplexKey>,
pub(crate) data: Option<U>,
}
impl<U, const D: usize> Vertex<U, D> {
#[inline]
pub const fn uuid(&self) -> Uuid {
self.uuid
}
#[inline]
pub const fn dim(&self) -> usize {
D
}
#[inline]
pub const fn point(&self) -> &Point<D> {
&self.point
}
#[inline]
#[must_use]
pub const fn incident_simplex(&self) -> Option<SimplexKey> {
self.incident_simplex
}
#[inline]
#[must_use]
pub const fn data(&self) -> Option<&U> {
self.data.as_ref()
}
#[inline]
pub(crate) const fn set_incident_simplex(&mut self, incident_simplex: Option<SimplexKey>) {
self.incident_simplex = incident_simplex;
}
}
impl<U, const D: usize> Serialize for Vertex<U, D>
where
U: DataSerialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let field_count = if self.data.is_some() { 3 } else { 2 };
let mut state = serializer.serialize_struct("Vertex", field_count)?;
state.serialize_field("point", &self.point)?;
state.serialize_field("uuid", &self.uuid)?;
if self.data.is_some() {
state.serialize_field("data", &self.data)?;
}
state.end()
}
}
impl<'de, U, const D: usize> Deserialize<'de> for Vertex<U, D>
where
U: DataDeserialize,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: serde::Deserializer<'de>,
{
struct VertexVisitor<U, const D: usize>
where
U: DataDeserialize,
{
_phantom: PhantomData<U>,
}
impl<'de, U, const D: usize> Visitor<'de> for VertexVisitor<U, D>
where
U: DataDeserialize,
{
type Value = Vertex<U, D>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a Vertex struct")
}
fn visit_map<V>(self, mut map: V) -> Result<Vertex<U, D>, V::Error>
where
V: MapAccess<'de>,
{
let mut point: Option<Point<D>> = None;
let mut uuid = None;
let mut data = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"point" => {
if point.is_some() {
return Err(de::Error::duplicate_field("point"));
}
point = Some(map.next_value()?);
}
"uuid" => {
if uuid.is_some() {
return Err(de::Error::duplicate_field("uuid"));
}
uuid = Some(map.next_value()?);
}
"incident_simplex" => {
return Err(de::Error::custom(
"incident_simplex is a storage-local slotmap key and must not be deserialized; deserialize Tds so incident mappings can be reconstructed",
));
}
"data" => {
if data.is_some() {
return Err(de::Error::duplicate_field("data"));
}
data = Some(map.next_value()?);
}
_ => {
let _ = map.next_value::<IgnoredAny>()?;
}
}
}
let point = point.ok_or_else(|| de::Error::missing_field("point"))?;
let uuid: Uuid = uuid.ok_or_else(|| de::Error::missing_field("uuid"))?;
validate_uuid(&uuid)
.map_err(|e| de::Error::custom(format!("invalid uuid: {e}")))?;
let data = data.unwrap_or(None);
Ok(Vertex {
point,
uuid,
incident_simplex: None,
data,
})
}
}
const FIELDS: &[&str] = &["point", "uuid", "data"];
deserializer.deserialize_struct(
"Vertex",
FIELDS,
VertexVisitor {
_phantom: PhantomData,
},
)
}
}
impl<U, const D: usize> Vertex<U, D> {
#[inline]
#[must_use]
pub(crate) fn from_validated_point(point: Point<D>, data: Option<U>) -> Self {
Self {
point,
uuid: make_uuid(),
incident_simplex: None,
data,
}
}
#[inline]
pub fn try_new<T>(coords: [T; D]) -> Result<Self, CoordinateConversionError>
where
T: num_traits::cast::NumCast + Copy + fmt::Debug + PartialEq,
{
Point::try_from(coords).map(|point| Self::from_validated_point(point, None))
}
#[inline]
pub fn try_new_with_data<T>(coords: [T; D], data: U) -> Result<Self, CoordinateConversionError>
where
T: num_traits::cast::NumCast + Copy + fmt::Debug + PartialEq,
{
Point::try_from(coords).map(|point| Self::from_validated_point(point, Some(data)))
}
#[inline]
pub fn try_into_hashmap<I>(vertices: I) -> Result<HashMap<Uuid, Self>, TdsConstructionError>
where
I: IntoIterator<Item = Self>,
{
let iter = vertices.into_iter();
let mut map = HashMap::with_capacity(iter.size_hint().0);
for vertex in iter {
let uuid = vertex.uuid();
if map.insert(uuid, vertex).is_some() {
return Err(TdsConstructionError::DuplicateUuid {
entity: EntityKind::Vertex,
uuid,
});
}
}
Ok(map)
}
pub fn is_valid(&self) -> Result<(), VertexValidationError> {
self.point
.validate()
.map_err(|source| VertexValidationError::InvalidPoint { source })?;
validate_uuid(&self.uuid())?;
Ok(())
}
#[must_use]
pub fn vertex_diagnostic(&self) -> Option<VertexValidationError> {
self.is_valid().err()
}
pub fn vertex_report(&self) -> Result<(), VertexValidationReport> {
let mut violations = Vec::new();
if let Err(source) = self.point.validate() {
violations.push(VertexValidationError::InvalidPoint { source });
}
if let Err(source) = validate_uuid(&self.uuid()) {
violations.push(VertexValidationError::InvalidUuid { source });
}
if violations.is_empty() {
Ok(())
} else {
Err(VertexValidationReport { violations })
}
}
pub fn try_new_with_uuid(
point: Point<D>,
uuid: Uuid,
data: Option<U>,
) -> Result<Self, VertexValidationError> {
validate_uuid(&uuid)?;
Ok(Self::from_validated_point_with_uuid(point, uuid, data))
}
pub(crate) const fn from_validated_point_with_uuid(
point: Point<D>,
uuid: Uuid,
data: Option<U>,
) -> Self {
Self {
point,
uuid,
incident_simplex: None,
data,
}
}
}
impl<U, const D: usize> PartialEq for Vertex<U, D> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.point.ordered_equals(&other.point)
}
}
impl<U, const D: usize> PartialOrd for Vertex<U, D> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.point.partial_cmp(&other.point)
}
}
impl<U, const D: usize> From<Vertex<U, D>> for [f64; D] {
#[inline]
fn from(vertex: Vertex<U, D>) -> [f64; D] {
vertex.point.into()
}
}
impl<U, const D: usize> From<&Vertex<U, D>> for [f64; D] {
#[inline]
fn from(vertex: &Vertex<U, D>) -> [f64; D] {
vertex.point().into()
}
}
impl<U, const D: usize> From<&Vertex<U, D>> for Point<D> {
#[inline]
fn from(vertex: &Vertex<U, D>) -> Self {
*vertex.point()
}
}
impl<U, const D: usize> Eq for Vertex<U, D> {
}
impl<U, const D: usize> Hash for Vertex<U, D> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.point.hash_coordinate(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::collections::{FastHashMap, FastHashSet};
use crate::core::tds::SimplexKey;
use crate::core::traits::DataType;
use crate::core::util::{UuidValidationError, make_uuid, usize_to_u8};
use crate::core::vertex::Vertex;
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::{
Coordinate, CoordinateValidationError, InvalidCoordinateValue,
};
use approx::{assert_abs_diff_eq, assert_relative_eq};
use serde::{Deserialize, Serialize};
use slotmap::KeyData;
use std::assert_matches;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum PointType {
Origin = 0,
Boundary = 1,
Interior = 2,
Corner = 3,
}
impl From<PointType> for u8 {
fn from(point_type: PointType) -> Self {
point_type as Self
}
}
impl Serialize for PointType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(u8::from(*self))
}
}
impl<'de> Deserialize<'de> for PointType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
match value {
0 => Ok(Self::Origin),
1 => Ok(Self::Boundary),
2 => Ok(Self::Interior),
3 => Ok(Self::Corner),
_ => Err(serde::de::Error::custom(format!(
"Invalid PointType: {value}"
))),
}
}
}
fn assert_vertex_properties<U, const D: usize>(vertex: &Vertex<U, D>, expected_coords: [f64; D])
where
U: DataType,
{
assert_abs_diff_eq!(
vertex.point().coords().as_slice(),
expected_coords.as_slice()
);
assert_eq!(vertex.dim(), D);
assert!(!vertex.uuid().is_nil());
assert!(vertex.incident_simplex.is_none());
}
#[test]
fn test_vertex_try_new() {
let v: Vertex<(), 3> = Vertex::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates");
assert_relative_eq!(
v.point().coords().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
assert!(!v.uuid().is_nil());
assert!(v.incident_simplex.is_none());
assert!(v.data.is_none());
}
#[test]
fn test_vertex_try_new_with_data() {
let v: Vertex<i32, 2> =
Vertex::try_new_with_data([0.0, 1.0], 42).expect("finite point coordinates");
assert_relative_eq!(
v.point().coords().as_slice(),
[0.0, 1.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(v.data, Some(42));
}
#[test]
fn test_vertex_try_new_with_data_stores_exact_metadata_type() {
let v: Vertex<String, 2> =
Vertex::try_new_with_data([0.0, 1.0], String::from("standalone-label"))
.expect("finite point coordinates");
assert_eq!(v.data().map(String::as_str), Some("standalone-label"));
}
#[test]
fn test_vertex_try_new_rejects_invalid_coordinates() {
let result = Vertex::<(), 3>::try_new([1.0, f64::NAN, 3.0]);
assert!(result.is_err());
}
#[test]
fn test_vertex_data_accessor() {
let v_with: Vertex<i32, 2> = Vertex::<_, _>::try_new_with_data([1.0, 2.0], 42).unwrap();
assert_eq!(v_with.data(), Some(&42));
let v_without: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
assert_eq!(v_without.data(), None);
}
#[test]
fn test_vertex_try_new_constructor_variants() {
let v1: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
assert_relative_eq!(
v1.point().coords().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(v1.dim(), 3);
assert!(!v1.uuid().is_nil());
assert!(v1.data.is_none());
let v2: Vertex<i32, 2> = Vertex::<_, _>::try_new_with_data([0.0, 1.0], 99).unwrap();
assert_relative_eq!(
v2.point().coords().as_slice(),
[0.0, 1.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(v2.dim(), 2);
assert!(!v2.uuid().is_nil());
assert_eq!(v2.data.unwrap(), 99);
let v3: Vertex<u32, 4> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0, 4.0], 42u32).unwrap();
assert_relative_eq!(
v3.point().coords().as_slice(),
[1.0f64, 2.0f64, 3.0f64, 4.0f64].as_slice(),
epsilon = 1e-9
);
assert_eq!(v3.dim(), 4);
assert_eq!(v3.data.unwrap(), 42u32);
}
#[test]
fn test_pointtype_u8_conversion() {
assert_eq!(u8::from(PointType::Origin), 0);
assert_eq!(u8::from(PointType::Boundary), 1);
assert_eq!(u8::from(PointType::Interior), 2);
assert_eq!(u8::from(PointType::Corner), 3);
let origin_json = serde_json::to_string(&PointType::Origin).unwrap();
assert_eq!(origin_json, "0");
let corner_json = serde_json::to_string(&PointType::Corner).unwrap();
assert_eq!(corner_json, "3");
let original = PointType::Boundary;
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: PointType = serde_json::from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn test_vertex_basic_operations() {
let vertex: Vertex<u8, 4> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0, 4.0], 4u8).unwrap();
let vertex_copy = vertex;
assert_eq!(vertex, vertex_copy);
assert_relative_eq!(
vertex_copy.point().coords().as_slice(),
[1.0, 2.0, 3.0, 4.0].as_slice(),
epsilon = 1e-9
);
let points = [
Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates"),
Point::try_new([4.0, 5.0, 6.0]).expect("finite point coordinates"),
Point::try_new([7.0, 8.0, 9.0]).expect("finite point coordinates"),
];
let mut vertices: Vec<Vertex<(), 3>> = points
.iter()
.copied()
.map(|point| Vertex::from_validated_point(point, None))
.collect();
assert_eq!(vertices.len(), 3);
assert_relative_eq!(
vertices[0].point().coords().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(vertices[0].dim(), 3);
assert_relative_eq!(
vertices[1].point().coords().as_slice(),
[4.0, 5.0, 6.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(vertices[1].dim(), 3);
assert_relative_eq!(
vertices[2].point().coords().as_slice(),
[7.0, 8.0, 9.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(vertices[2].dim(), 3);
let hashmap = Vertex::try_into_hashmap(vertices.iter().copied()).unwrap();
let mut values: Vec<Vertex<(), 3>> = hashmap.into_values().collect();
assert_eq!(values.len(), 3);
values.sort_by_key(Vertex::uuid);
vertices.sort_by_key(Vertex::uuid);
assert_eq!(values, vertices);
let empty_hashmap = Vertex::<(), 3>::try_into_hashmap([]).unwrap();
assert!(empty_hashmap.is_empty());
let single_vertex: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let uuid = single_vertex.uuid();
let single_hashmap = Vertex::try_into_hashmap([single_vertex]).unwrap();
assert_eq!(single_hashmap.len(), 1);
assert!(single_hashmap.contains_key(&uuid));
assert_relative_eq!(
single_hashmap
.get(&uuid)
.unwrap()
.point()
.coords()
.as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
}
#[test]
fn test_try_into_hashmap_rejects_duplicate_uuid() {
let uuid = make_uuid();
let first: Vertex<(), 2> = Vertex::from_validated_point_with_uuid(
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
uuid,
None,
);
let second: Vertex<(), 2> = Vertex::from_validated_point_with_uuid(
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
uuid,
None,
);
assert_matches!(
Vertex::try_into_hashmap([first, second]),
Err(TdsConstructionError::DuplicateUuid {
entity: EntityKind::Vertex,
uuid: duplicate_uuid,
}) if duplicate_uuid == uuid
);
}
#[test]
fn test_try_new_with_uuid_rejects_nil_uuid() {
let point = Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates");
let result = Vertex::<(), 3>::try_new_with_uuid(point, uuid::Uuid::nil(), None);
assert_eq!(
result.unwrap_err(),
VertexValidationError::InvalidUuid {
source: UuidValidationError::NilUuid,
}
);
}
#[test]
fn test_try_new_with_uuid_preserves_valid_uuid() {
let point = Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates");
let uuid = make_uuid();
let vertex = Vertex::<u8, 3>::try_new_with_uuid(point, uuid, Some(7)).unwrap();
assert_eq!(vertex.uuid(), uuid);
assert_eq!(vertex.point(), &point);
assert_eq!(vertex.data(), Some(&7));
}
#[test]
fn test_vertex_serialization_roundtrip() {
let vertex: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let serialized = serde_json::to_string(&vertex).unwrap();
assert!(serialized.contains("point"));
assert!(serialized.contains("[1.0,2.0,3.0]"));
let deserialized: Vertex<(), 3> = serde_json::from_str(&serialized).unwrap();
assert_relative_eq!(
deserialized.point().coords().as_slice(),
vertex.point().coords().as_slice(),
epsilon = f64::EPSILON
);
assert_eq!(deserialized.dim(), vertex.dim());
assert_eq!(deserialized.incident_simplex, vertex.incident_simplex);
assert_eq!(deserialized.data, vertex.data);
assert_eq!(deserialized.uuid(), vertex.uuid());
let vertex_with_data: Vertex<i32, 3> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0], 42).unwrap();
let serialized_with_data = serde_json::to_string(&vertex_with_data).unwrap();
assert!(serialized_with_data.contains("\"data\":"));
assert!(serialized_with_data.contains("42"));
let deserialized_with_data: Vertex<i32, 3> =
serde_json::from_str(&serialized_with_data).unwrap();
assert_eq!(deserialized_with_data.data, Some(42));
assert_relative_eq!(
deserialized_with_data.point().coords().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = f64::EPSILON
);
let vertex_no_data: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let serialized_no_data = serde_json::to_string(&vertex_no_data).unwrap();
assert!(!serialized_no_data.contains("\"data\":"));
let deserialized_no_data: Vertex<(), 3> =
serde_json::from_str(&serialized_no_data).unwrap();
assert_eq!(deserialized_no_data.data, None);
let json_with_incident_simplex = r#"{"point":[1.0,2.0,3.0],"uuid":"550e8400-e29b-41d4-a716-446655440000","incident_simplex":{"idx":1,"version":1}}"#;
let incident_simplex_error =
serde_json::from_str::<Vertex<(), 3>>(json_with_incident_simplex).unwrap_err();
assert!(
incident_simplex_error
.to_string()
.contains("incident_simplex is a storage-local slotmap key")
);
let json_with_null =
r#"{"point":[1.0,2.0,3.0],"uuid":"550e8400-e29b-41d4-a716-446655440000","data":null}"#;
let vertex_null_data: Vertex<(), 3> = serde_json::from_str(json_with_null).unwrap();
assert_eq!(vertex_null_data.data, None);
let vertex_char: Vertex<char, 4> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0, 4.0], 'A').unwrap();
let serialized_char = serde_json::to_string(&vertex_char).unwrap();
let deserialized_char: Vertex<char, 4> = serde_json::from_str(&serialized_char).unwrap();
assert_eq!(deserialized_char.data, Some('A'));
assert_relative_eq!(
deserialized_char.point().coords().as_slice(),
[1.0, 2.0, 3.0, 4.0].as_slice(),
epsilon = f64::EPSILON
);
}
#[test]
fn test_vertex_equality_and_hashing() {
let v1: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let v2: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let v3: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 4.0]).unwrap();
assert_eq!(v1, v2);
assert!(v1.eq(&v2));
assert!(v2.eq(&v1));
assert_ne!(v1, v3);
assert_ne!(v2, v3);
assert!(!v1.eq(&v3));
assert!(!v2.eq(&v3));
assert_eq!(v1, v1);
assert!(v1.eq(&v1));
let v4: Vertex<i32, 2> = Vertex::<_, _>::try_new_with_data([1.0, 2.0], 42).unwrap();
let v5: Vertex<i32, 2> = Vertex::<_, _>::try_new_with_data([1.0, 2.0], 99).unwrap();
assert_ne!(v4.uuid(), v5.uuid());
assert_ne!(v4.data, v5.data);
assert_eq!(v4, v5);
let v6: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
let v7: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
assert_eq!(v6, v7);
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
v1.hash(&mut hasher1);
v1.hash(&mut hasher2);
let hash1 = hasher1.finish();
let hash2 = hasher2.finish();
assert_eq!(hash1, hash2);
let mut hasher3 = DefaultHasher::new();
v2.hash(&mut hasher3);
let hash3 = hasher3.finish();
assert_eq!(v1, v2); assert_eq!(hash1, hash3);
let mut hasher4 = DefaultHasher::new();
v3.hash(&mut hasher4);
let hash4 = hasher4.finish();
assert_ne!(v1, v3);
assert_ne!(hash1, hash4);
let mut hasher5 = DefaultHasher::new();
let mut hasher6 = DefaultHasher::new();
v4.hash(&mut hasher5);
v5.hash(&mut hasher6);
let hash5 = hasher5.finish();
let hash6 = hasher6.finish();
assert_eq!(v4, v5); assert_eq!(hash5, hash6); assert_ne!(v4.uuid(), v5.uuid()); assert_ne!(v4.data, v5.data);
let test_cases: Vec<([f64; 2], [f64; 2])> = vec![
([0.0, 0.0], [0.0, 0.0]),
([1.0, 2.0], [1.0, 2.0]),
([-1.0, -2.0], [-1.0, -2.0]),
];
for (coords1, coords2) in test_cases {
let v_a: Vertex<(), 2> = Vertex::<(), _>::try_new(coords1).unwrap();
let v_b: Vertex<(), 2> = Vertex::<(), _>::try_new(coords2).unwrap();
assert_eq!(v_a, v_b);
let mut hasher_a = DefaultHasher::new();
let mut hasher_b = DefaultHasher::new();
v_a.hash(&mut hasher_a);
v_b.hash(&mut hasher_b);
assert_eq!(hasher_a.finish(), hasher_b.finish());
}
}
#[test]
fn test_vertex_signed_zero_eq_hash_and_order_are_consistent() {
let positive_zero: Vertex<(), 2> = Vertex::<(), _>::try_new([0.0, -0.0]).unwrap();
let negative_zero: Vertex<(), 2> = Vertex::<(), _>::try_new([-0.0, 0.0]).unwrap();
assert_eq!(positive_zero, negative_zero);
assert_eq!(
positive_zero.partial_cmp(&negative_zero),
Some(Ordering::Equal)
);
let mut positive_hash = DefaultHasher::new();
let mut negative_hash = DefaultHasher::new();
positive_zero.hash(&mut positive_hash);
negative_zero.hash(&mut negative_hash);
assert_eq!(positive_hash.finish(), negative_hash.finish());
}
#[test]
fn test_vertex_collections() {
let mut set: FastHashSet<Vertex<(), 2>> = FastHashSet::default();
let v1: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
let v2: Vertex<(), 2> = Vertex::<(), _>::try_new([3.0, 4.0]).unwrap();
let v3: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
assert!(set.insert(v1)); assert!(set.insert(v2)); assert!(!set.insert(v3));
assert_eq!(set.len(), 2);
assert!(set.contains(&v1));
assert!(set.contains(&v2));
assert!(set.contains(&v3));
let v4: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
assert!(set.contains(&v4));
let mut map: FastHashMap<Vertex<(), 2>, i32> = FastHashMap::default();
let v5: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
let v6: Vertex<(), 2> = Vertex::<(), _>::try_new([3.0, 4.0]).unwrap();
map.insert(v5, 10);
map.insert(v6, 20);
assert_eq!(map.get(&v5), Some(&10));
assert_eq!(map.get(&v6), Some(&20));
assert_eq!(map.len(), 2);
let v7: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
assert_eq!(map.get(&v7), Some(&10));
let old_value = map.insert(v7, 30);
assert_eq!(old_value, Some(10)); assert_eq!(map.len(), 2); assert_eq!(map.get(&v5), Some(&30));
let v8: Vertex<u16, 2> = Vertex::<_, _>::try_new_with_data([1.0, 2.0], 999u16).unwrap();
let v9: Vertex<i32, 2> = Vertex::<_, _>::try_new_with_data([3.0, 4.0], -42i32).unwrap();
let mut map1: FastHashMap<Vertex<u16, 2>, &str> = FastHashMap::default();
map1.insert(v8, "first");
assert_eq!(map1.len(), 1);
let mut map2: FastHashMap<Vertex<i32, 2>, bool> = FastHashMap::default();
map2.insert(v9, true);
assert_eq!(map2.len(), 1);
}
macro_rules! test_vertex_dimensions {
($(
$test_name:ident => $dim:expr => [$($coord:expr),+ $(,)?]
),+ $(,)?) => {
$(
#[test]
fn $test_name() {
let vertex: Vertex<(), $dim> = Vertex::<(), _>::try_new([$($coord),+]).unwrap();
assert_vertex_properties(&vertex, [$($coord),+]);
assert!(vertex.data.is_none());
}
pastey::paste! {
#[test]
fn [<$test_name _with_data>]() {
let vertex: Vertex<i32, $dim> = Vertex::<_, _>::try_new_with_data([$($coord),+], 42).unwrap();
assert_vertex_properties(&vertex, [$($coord),+]);
assert_eq!(vertex.data, Some(42));
}
#[test]
fn [<$test_name _serialization_roundtrip>]() {
let vertex_with_data: Vertex<i32, $dim> = Vertex::<_, _>::try_new_with_data([$($coord),+], 99).unwrap();
let serialized = serde_json::to_string(&vertex_with_data).unwrap();
assert!(serialized.contains("\"data\":"));
let deserialized: Vertex<i32, $dim> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, Some(99));
assert_vertex_properties(&deserialized, [$($coord),+]);
let vertex_no_data: Vertex<(), $dim> = Vertex::<(), _>::try_new([$($coord),+]).unwrap();
let serialized = serde_json::to_string(&vertex_no_data).unwrap();
assert!(!serialized.contains("\"data\":"));
let deserialized: Vertex<(), $dim> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, None);
}
#[test]
fn [<$test_name _uuid_uniqueness>]() {
let v1: Vertex<(), $dim> = Vertex::<(), _>::try_new([$($coord),+]).unwrap();
let v2: Vertex<(), $dim> = Vertex::<(), _>::try_new([$($coord),+]).unwrap();
assert_ne!(v1.uuid(), v2.uuid());
assert!(!v1.uuid().is_nil());
assert!(!v2.uuid().is_nil());
}
}
)+
};
}
test_vertex_dimensions! {
vertex_2d => 2 => [1.0, 2.0],
vertex_3d => 3 => [1.0, 2.0, 3.0],
vertex_4d => 4 => [1.0, 2.0, 3.0, 4.0],
vertex_5d => 5 => [1.0, 2.0, 3.0, 4.0, 5.0],
}
#[test]
fn vertex_1d() {
let vertex: Vertex<(), 1> = Vertex::<(), _>::try_new([42.0]).unwrap();
assert_vertex_properties(&vertex, [42.0]);
assert!(vertex.data.is_none());
}
#[test]
fn test_vertex_data_types_and_ordering() {
let vertex_tuple: Vertex<(i32, i32), 2> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0], (42, 84)).unwrap();
assert_vertex_properties(&vertex_tuple, [1.0, 2.0]);
assert_eq!(vertex_tuple.data.unwrap(), (42, 84));
let vertex_debug: Vertex<i32, 3> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0], 42).unwrap();
let debug_str = format!("{vertex_debug:?}");
assert!(debug_str.contains("Vertex"));
assert!(debug_str.contains("point"));
assert!(debug_str.contains("uuid"));
assert!(debug_str.contains("1.0"));
assert!(debug_str.contains("2.0"));
assert!(debug_str.contains("3.0"));
let vertex1: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
let vertex2: Vertex<(), 2> = Vertex::<(), _>::try_new([1.0, 2.0]).unwrap();
assert_ne!(vertex1.partial_cmp(&vertex2), Some(Ordering::Less));
assert_ne!(vertex2.partial_cmp(&vertex1), Some(Ordering::Less));
assert_matches!(
vertex1.partial_cmp(&vertex2),
Some(Ordering::Less | Ordering::Equal)
);
assert_matches!(
vertex2.partial_cmp(&vertex1),
Some(Ordering::Less | Ordering::Equal)
);
assert_matches!(
vertex1.partial_cmp(&vertex2),
Some(Ordering::Greater | Ordering::Equal)
);
assert_matches!(
vertex2.partial_cmp(&vertex1),
Some(Ordering::Greater | Ordering::Equal)
);
}
#[test]
fn test_vertex_coordinate_values() {
let vertex_neg: Vertex<(), 3> = Vertex::<(), _>::try_new([-1.0, -2.0, -3.0]).unwrap();
assert_relative_eq!(
vertex_neg.point().coords().as_slice(),
[-1.0, -2.0, -3.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(vertex_neg.dim(), 3);
let vertex_zero: Vertex<(), 3> = Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap();
let origin_vertex: Vertex<(), 3> = Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap();
assert_eq!(vertex_zero.point(), origin_vertex.point());
let vertex_large: Vertex<(), 3> = Vertex::<(), _>::try_new([1e6, 2e6, 3e6]).unwrap();
assert_relative_eq!(
vertex_large.point().coords().as_slice(),
[1_000_000.0, 2_000_000.0, 3_000_000.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(vertex_large.dim(), 3);
let vertex_small: Vertex<(), 3> = Vertex::<(), _>::try_new([1e-6, 2e-6, 3e-6]).unwrap();
assert_relative_eq!(
vertex_small.point().coords().as_slice(),
[0.000_001, 0.000_002, 0.000_003].as_slice(),
epsilon = 1e-9
);
assert_eq!(vertex_small.dim(), 3);
let vertex_mixed: Vertex<(), 4> = Vertex::<(), _>::try_new([1.0, -2.0, 3.0, -4.0]).unwrap();
assert_relative_eq!(
vertex_mixed.point().coords().as_slice(),
[1.0, -2.0, 3.0, -4.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(vertex_mixed.dim(), 4);
}
#[test]
fn test_vertex_properties() {
let vertex1: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let vertex2: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
assert_ne!(vertex1.uuid(), vertex2.uuid());
assert!(!vertex1.uuid().is_nil());
assert!(!vertex2.uuid().is_nil());
}
#[test]
fn test_vertex_type_conversions() {
let vertex_coords: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let coords_owned: [f64; 3] = vertex_coords.into();
assert_relative_eq!(
coords_owned.as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
let vertex_ref_coords: Vertex<(), 3> = Vertex::<(), _>::try_new([4.0, 5.0, 6.0]).unwrap();
let coords_ref: [f64; 3] = (&vertex_ref_coords).into();
assert_relative_eq!(
coords_ref.as_slice(),
[4.0, 5.0, 6.0].as_slice(),
epsilon = 1e-9
);
assert_relative_eq!(
vertex_ref_coords.point().coords().as_slice(),
[4.0, 5.0, 6.0].as_slice(),
epsilon = 1e-9
);
let vertex_point: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let point_from_vertex: Point<3> = (&vertex_point).into();
assert_relative_eq!(
point_from_vertex.coords().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(point_from_vertex, *vertex_point.point());
assert_relative_eq!(
vertex_point.point().coords().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
let vertex_2d: Vertex<(), 2> = Vertex::<(), _>::try_new([10.5, -5.3]).unwrap();
let point_2d: Point<2> = (&vertex_2d).into();
assert_relative_eq!(
point_2d.coords().as_slice(),
[10.5, -5.3].as_slice(),
epsilon = 1e-9
);
assert_eq!(point_2d, *vertex_2d.point());
}
#[test]
fn test_vertex_validation() {
let valid_f64: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
assert!(valid_f64.is_valid().is_ok());
let valid_negative: Vertex<(), 3> = Vertex::<(), _>::try_new([-1.0, -2.0, -3.0]).unwrap();
assert!(valid_negative.is_valid().is_ok());
let valid_zero: Vertex<(), 3> = Vertex::<(), _>::try_new([0.0, 0.0, 0.0]).unwrap();
assert!(valid_zero.is_valid().is_ok());
let valid_1d: Vertex<(), 1> = Vertex::<(), _>::try_new([42.0]).unwrap();
assert!(valid_1d.is_valid().is_ok());
let valid_5d: Vertex<(), 5> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
assert!(valid_5d.is_valid().is_ok());
assert!(Point::<3>::try_new([1.0, f64::NAN, 3.0]).is_err());
assert!(Point::<3>::try_new([f64::NAN, f64::NAN, f64::NAN]).is_err());
assert!(Point::<1>::try_new([f64::NAN]).is_err());
assert!(Point::<5>::try_new([1.0, 2.0, f64::NAN, 4.0, 5.0]).is_err());
assert!(Point::<3>::try_new([1.0, f64::INFINITY, 3.0]).is_err());
assert!(Point::<3>::try_new([1.0, f64::NEG_INFINITY, 3.0]).is_err());
assert!(Point::<3>::try_new([f64::NAN, f64::INFINITY, 1.0]).is_err());
let valid_vertex: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
assert!(valid_vertex.is_valid().is_ok());
assert!(!valid_vertex.uuid().is_nil());
let invalid_uuid_vertex: Vertex<(), 3> = Vertex {
point: Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates"),
uuid: uuid::Uuid::nil(),
incident_simplex: None,
data: None,
};
match invalid_uuid_vertex.is_valid() {
Err(VertexValidationError::InvalidUuid { source: _ }) => (), other => panic!("Expected InvalidUuid error, got: {other:?}"),
}
assert!(invalid_uuid_vertex.point().validate().is_ok());
assert!(invalid_uuid_vertex.uuid().is_nil());
assert!(Point::<3>::try_new([f64::NAN, 2.0, 3.0]).is_err());
}
#[test]
fn vertex_string_data_usage_examples() {
let mut label_lookup: FastHashMap<u32, String> = FastHashMap::default();
label_lookup.insert(0, "center".to_string());
label_lookup.insert(1, "corner".to_string());
label_lookup.insert(2, "edge_midpoint".to_string());
label_lookup.insert(3, "boundary_point".to_string());
let vertices_with_ids: Vec<Vertex<u32, 2>> = vec![
Vertex::<_, _>::try_new_with_data([0.5, 0.5], 0u32).unwrap(), Vertex::<_, _>::try_new_with_data([1.0, 1.0], 1u32).unwrap(), Vertex::<_, _>::try_new_with_data([0.5, 1.0], 2u32).unwrap(), Vertex::<_, _>::try_new_with_data([0.0, 0.5], 3u32).unwrap(), ];
for (v, expected_label) in
vertices_with_ids
.iter()
.zip(["center", "corner", "edge_midpoint", "boundary_point"])
{
let label_id = v.data.unwrap();
let label = label_lookup.get(&label_id).unwrap();
assert_eq!(label, expected_label);
}
assert_eq!(vertices_with_ids.len(), 4);
let coords = vertices_with_ids[0].point().coords();
assert_abs_diff_eq!(coords[0], 0.5, epsilon = f64::EPSILON);
assert_abs_diff_eq!(coords[1], 0.5, epsilon = f64::EPSILON);
assert_eq!(vertices_with_ids[1].data.unwrap(), 1u32);
let vertex_set: FastHashSet<Vertex<u32, 2>> = vertices_with_ids.iter().copied().collect();
assert_eq!(vertex_set.len(), 4);
let vertices_with_chars: Vec<Vertex<char, 2>> = vec![
Vertex::<_, _>::try_new_with_data([0.0, 0.0], 'A').unwrap(),
Vertex::<_, _>::try_new_with_data([1.0, 0.0], 'B').unwrap(),
Vertex::<_, _>::try_new_with_data([0.0, 1.0], 'C').unwrap(),
];
for (i, v) in vertices_with_chars.iter().enumerate() {
let expected_char =
char::from(b'A' + usize_to_u8(i, 26).expect("Index should fit in u8"));
assert_eq!(v.data.unwrap(), expected_char);
}
let vertices_with_enums: Vec<Vertex<PointType, 2>> = vec![
Vertex::<_, _>::try_new_with_data([0.0, 0.0], PointType::Origin).unwrap(),
Vertex::<_, _>::try_new_with_data([1.0, 0.0], PointType::Corner).unwrap(),
Vertex::<_, _>::try_new_with_data([0.5, 0.5], PointType::Interior).unwrap(),
];
assert_eq!(vertices_with_enums[0].data.unwrap(), PointType::Origin);
assert_eq!(vertices_with_enums[1].data.unwrap(), PointType::Corner);
assert_eq!(vertices_with_enums[2].data.unwrap(), PointType::Interior);
}
#[test]
fn vertex_hash_with_copy_data() {
let vertex1: Vertex<u16, 2> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0], 999u16).unwrap();
let vertex2: Vertex<i32, 2> = Vertex::<_, _>::try_new_with_data([3.0, 4.0], 42).unwrap();
let mut map: FastHashMap<Vertex<u16, 2>, i32> = FastHashMap::default();
map.insert(vertex1, 100);
let mut map2: FastHashMap<Vertex<i32, 2>, u8> = FastHashMap::default();
map2.insert(vertex2, 255u8);
assert_eq!(map.len(), 1);
assert_eq!(map2.len(), 1);
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "Comprehensive deserialization edge-case test"
)]
fn test_vertex_deserialization_edge_cases() {
let json_minimal = r#"{
"point": [10.0, 20.0],
"uuid": "550e8400-e29b-41d4-a716-446655440000"
}"#;
let result: Result<Vertex<(), 2>, _> = serde_json::from_str(json_minimal);
assert!(result.is_ok());
let vertex = result.unwrap();
assert_relative_eq!(
vertex.point().coords().as_slice(),
[10.0, 20.0].as_slice(),
epsilon = 1e-9
);
assert_eq!(
vertex.uuid().to_string(),
"550e8400-e29b-41d4-a716-446655440000"
);
assert!(vertex.incident_simplex.is_none());
assert!(vertex.data.is_none());
let point = Point::try_new([1.5, 2.5, 3.5]).expect("finite point coordinates");
let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
let uuid = uuid::Uuid::parse_str(uuid_str).unwrap();
let simplex_key = SimplexKey::from(KeyData::from_ffi(42u64));
let vertex_with_all = Vertex {
point,
uuid,
incident_simplex: Some(simplex_key),
data: Some(123i32),
};
assert_relative_eq!(
vertex_with_all.point().coords().as_slice(),
[1.5, 2.5, 3.5].as_slice(),
epsilon = 1e-9
);
assert_eq!(vertex_with_all.uuid().to_string(), uuid_str);
assert!(vertex_with_all.incident_simplex.is_some());
assert_eq!(vertex_with_all.incident_simplex.unwrap(), simplex_key);
assert_eq!(vertex_with_all.data.unwrap(), 123);
let json_with_unknown = r#"{
"point": [1.0, 2.0, 3.0],
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"data": null,
"unknown_field": "this should be ignored"
}"#;
let result: Result<Vertex<(), 3>, _> = serde_json::from_str(json_with_unknown);
assert!(result.is_ok());
let vertex = result.unwrap();
assert_relative_eq!(
vertex.point().coords().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
let test_cases = vec![
(
r#"{"point": [1.0, 2.0, 3.0], "point": [4.0, 5.0, 6.0], "uuid": "550e8400-e29b-41d4-a716-446655440000"}"#,
"duplicate point",
"duplicate field `point`",
),
(
r#"{"point": [1.0, 2.0, 3.0], "uuid": "550e8400-e29b-41d4-a716-446655440000", "uuid": "550e8400-e29b-41d4-a716-446655440001"}"#,
"duplicate uuid",
"duplicate field `uuid`",
),
(
r#"{"point": [1.0, 2.0, 3.0], "uuid": "550e8400-e29b-41d4-a716-446655440000", "incident_simplex": null}"#,
"forbidden incident_simplex",
"storage-local slotmap key",
),
(
r#"{"point": [1.0, 2.0, 3.0], "uuid": "550e8400-e29b-41d4-a716-446655440000", "data": null, "data": null}"#,
"duplicate data",
"duplicate field `data`",
),
(
r#"{"uuid": "550e8400-e29b-41d4-a716-446655440000"}"#,
"missing point",
"missing field `point`",
),
(
r#"{"point": [1.0, 2.0, 3.0]}"#,
"missing uuid",
"missing field `uuid`",
),
];
for (json, description, expected_fragment) in test_cases {
let result: Result<Vertex<(), 3>, _> = serde_json::from_str(json);
assert!(
result.is_err(),
"Expected error for {description}, but got success"
);
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains(expected_fragment),
"Error message for {description} should contain {expected_fragment:?}: {error_message}"
);
}
let invalid_json = r#"["not", "a", "vertex", "object"]"#;
let result: Result<Vertex<(), 3>, _> = serde_json::from_str(invalid_json);
assert!(result.is_err());
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains("Vertex") || error_message.to_lowercase().contains("struct"),
"Error message should mention Vertex struct: {error_message}"
);
let vertex_with_nil_uuid = Vertex {
point: Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates"),
uuid: uuid::Uuid::nil(),
incident_simplex: None,
data: None::<()>,
};
let validation_result = vertex_with_nil_uuid.is_valid();
assert!(validation_result.is_err());
match validation_result.unwrap_err() {
VertexValidationError::InvalidUuid { source: _ } => (), other @ VertexValidationError::InvalidPoint { .. } => {
panic!("Expected InvalidUuid error, got: {other:?}")
}
}
}
#[test]
fn test_vertex_validation_error_display() {
let point_error = CoordinateValidationError::InvalidCoordinate {
coordinate_index: 1,
coordinate_value: InvalidCoordinateValue::Nan,
dimension: 3,
};
let vertex_error = VertexValidationError::InvalidPoint {
source: point_error,
};
let error_string = format!("{vertex_error}");
assert!(error_string.contains("Invalid point"));
let uuid_error = VertexValidationError::InvalidUuid {
source: UuidValidationError::NilUuid,
};
let uuid_error_string = format!("{uuid_error}");
assert!(uuid_error_string.contains("Invalid UUID"));
}
#[test]
fn test_vertex_validation_error_equality() {
let error1 = VertexValidationError::InvalidUuid {
source: UuidValidationError::NilUuid,
};
let error2 = VertexValidationError::InvalidUuid {
source: UuidValidationError::NilUuid,
};
assert_eq!(error1, error2);
let point_error = CoordinateValidationError::InvalidCoordinate {
coordinate_index: 1,
coordinate_value: InvalidCoordinateValue::Nan,
dimension: 3,
};
let error3 = VertexValidationError::InvalidPoint {
source: point_error.clone(),
};
let error4 = VertexValidationError::InvalidPoint {
source: point_error,
};
assert_eq!(error3, error4);
assert_ne!(error1, error3);
}
#[test]
fn test_serialization_deserialization_roundtrip() {
let original_vertex: Vertex<char, 4> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0, 4.0], 'A').unwrap();
let serialized = serde_json::to_string(&original_vertex).unwrap();
let deserialized_vertex: Vertex<char, 4> = serde_json::from_str(&serialized).unwrap();
assert_relative_eq!(
original_vertex.point().coords().as_slice(),
deserialized_vertex.point().coords().as_slice(),
epsilon = 1e-9
);
assert_eq!(original_vertex.uuid(), deserialized_vertex.uuid());
assert_eq!(
original_vertex.incident_simplex,
deserialized_vertex.incident_simplex
);
assert_eq!(original_vertex.data, deserialized_vertex.data);
}
#[test]
fn test_serialization_with_some_data_includes_field() {
let vertex: Vertex<i32, 3> =
Vertex::<_, _>::try_new_with_data([1.0, 2.0, 3.0], 42).unwrap();
let serialized = serde_json::to_string(&vertex).unwrap();
assert!(
serialized.contains("\"data\":"),
"JSON should include data field when Some"
);
assert!(serialized.contains("42"), "JSON should include data value");
let deserialized: Vertex<i32, 3> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, Some(42));
assert_relative_eq!(
vertex.point().coords().as_slice(),
deserialized.point().coords().as_slice(),
epsilon = 1e-9
);
}
#[test]
fn test_serialization_with_none_data_omits_field() {
let vertex: Vertex<(), 3> = Vertex::<(), _>::try_new([1.0, 2.0, 3.0]).unwrap();
let serialized = serde_json::to_string(&vertex).unwrap();
assert!(
!serialized.contains("\"data\":"),
"JSON should omit data field when None"
);
let deserialized: Vertex<(), 3> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, None);
assert_relative_eq!(
vertex.point().coords().as_slice(),
deserialized.point().coords().as_slice(),
epsilon = 1e-9
);
}
#[test]
fn test_deserialization_with_explicit_null_data() {
let json_with_null =
r#"{"point":[1.0,2.0,3.0],"uuid":"550e8400-e29b-41d4-a716-446655440000","data":null}"#;
let vertex: Vertex<(), 3> = serde_json::from_str(json_with_null).unwrap();
assert_eq!(vertex.data, None);
assert_relative_eq!(
vertex.point().coords().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = 1e-9
);
}
}