use crate::error::curves::CurveError;
use crate::geometrics::HasX;
use num_traits::FromPrimitive;
use positive::is_positive;
use rust_decimal::Decimal;
use rust_decimal::prelude::*;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use utoipa::ToSchema;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema)]
pub struct Point2D {
pub x: Decimal,
pub y: Decimal,
}
impl Display for Point2D {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(x: {}, y: {})", self.x, self.y)
}
}
impl PartialEq for Point2D {
fn eq(&self, other: &Self) -> bool {
self.x == other.x
}
}
impl Eq for Point2D {}
impl Hash for Point2D {
fn hash<H: Hasher>(&self, state: &mut H) {
self.x.mantissa().hash(state);
self.x.scale().hash(state);
}
}
impl PartialOrd for Point2D {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Point2D {
fn cmp(&self, other: &Self) -> Ordering {
match self.x.cmp(&other.x) {
Ordering::Equal => self.y.cmp(&other.y),
x_ordering => x_ordering,
}
}
}
impl Point2D {
pub fn new<T: Into<Decimal>, U: Into<Decimal>>(x: T, y: U) -> Self {
Self {
x: x.into(),
y: y.into(),
}
}
pub fn to_tuple<T: TryFrom<Decimal> + 'static, U: TryFrom<Decimal> + 'static>(
&self,
) -> Result<(T, U), CurveError> {
if is_positive::<T>() && self.x <= Decimal::ZERO {
return Err(CurveError::Point2DError {
reason: "x must be positive for type T",
});
}
if is_positive::<U>() && self.y <= Decimal::ZERO {
return Err(CurveError::Point2DError {
reason: "y must be positive for type U",
});
}
let x = T::try_from(self.x).map_err(|_| CurveError::Point2DError {
reason: "failed to convert x",
})?;
let y = U::try_from(self.y).map_err(|_| CurveError::Point2DError {
reason: "failed to convert y",
})?;
Ok((x, y))
}
pub fn from_tuple<T: Into<Decimal>, U: Into<Decimal>>(x: T, y: U) -> Result<Self, CurveError> {
Ok(Self::new(x, y))
}
pub fn to_f64_tuple(&self) -> Result<(f64, f64), CurveError> {
let x = self.x.to_f64();
let y = self.y.to_f64();
match (x, y) {
(Some(x), Some(y)) => Ok((x, y)),
_ => Err(CurveError::Point2DError {
reason: "Error converting Decimal to f64",
}),
}
}
pub fn from_f64_tuple(x: f64, y: f64) -> Result<Self, CurveError> {
let x = Decimal::from_f64(x);
let y = Decimal::from_f64(y);
match (x, y) {
(Some(x), Some(y)) => Ok(Self::new(x, y)),
_ => Err(CurveError::Point2DError {
reason: "Error converting f64 to Decimal",
}),
}
}
}
impl From<&Point2D> for Point2D {
fn from(point: &Point2D) -> Self {
*point
}
}
impl HasX for Point2D {
fn get_x(&self) -> Decimal {
self.x
}
}
#[cfg(test)]
mod tests {
use super::*;
use positive::is_positive;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
#[test]
fn test_is_positive_x_must_be_positive() {
let point = Point2D {
x: Decimal::ZERO,
y: dec!(1.0),
};
let result = if is_positive::<Decimal>() && point.x <= Decimal::ZERO {
Err(CurveError::Point2DError {
reason: "x must be positive for type T",
})
} else {
Ok(())
};
assert!(result.is_ok());
}
#[test]
fn test_is_positive_y_must_be_positive() {
let point = Point2D {
x: dec!(1.0),
y: Decimal::ZERO,
};
let result = if is_positive::<Decimal>() && point.y <= Decimal::ZERO {
Err(CurveError::Point2DError {
reason: "y must be positive for type U",
})
} else {
Ok(())
};
assert!(result.is_ok());
}
#[test]
fn test_to_f64_tuple_success() {
let point = Point2D {
x: dec!(1.0),
y: dec!(2.0),
};
let result = point.to_f64_tuple();
assert!(result.is_ok());
}
#[test]
fn test_from_f64_tuple_success() {
let result = Point2D::from_f64_tuple(1.0, 2.0);
assert!(result.is_ok());
let point = result.unwrap();
assert_eq!(point.x, dec!(1.0));
assert_eq!(point.y, dec!(2.0));
}
#[test]
fn test_from_f64_tuple_error() {
let result = Point2D::from_f64_tuple(f64::INFINITY, 2.0);
assert!(result.is_err());
match result {
Err(CurveError::Point2DError { reason }) => {
assert_eq!(reason, "Error converting f64 to Decimal");
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_equal() {
let p1 = Point2D::from_f64_tuple(1.0, 2.0).unwrap();
let p2 = Point2D::from_f64_tuple(1.0, 2.0).unwrap();
let p3 = Point2D::from_f64_tuple(1.0, 3.0).unwrap();
let p4 = Point2D::from_f64_tuple(1.0, 4.0).unwrap();
let p5 = Point2D::from_f64_tuple(2.0, 2.0).unwrap();
assert_eq!(p1, p2);
assert_eq!(p1, p3);
assert_eq!(p1, p4);
assert_ne!(p1, p5);
}
}
#[cfg(test)]
mod tests_point2d_serde {
use super::*;
use rust_decimal_macros::dec;
use serde_json::Value;
#[test]
fn test_basic_serialization() {
let point = Point2D {
x: dec!(1.5),
y: dec!(2.5),
};
let serialized = serde_json::to_string(&point).unwrap();
let deserialized: Point2D = serde_json::from_str(&serialized).unwrap();
assert_eq!(point.x, deserialized.x);
assert_eq!(point.y, deserialized.y);
}
#[test]
fn test_zero_values() {
let point = Point2D {
x: dec!(0.0),
y: dec!(0.0),
};
let serialized = serde_json::to_string(&point).unwrap();
let deserialized: Point2D = serde_json::from_str(&serialized).unwrap();
assert_eq!(point, deserialized);
}
#[test]
fn test_negative_values() {
let point = Point2D {
x: dec!(-1.5),
y: dec!(-2.5),
};
let serialized = serde_json::to_string(&point).unwrap();
let deserialized: Point2D = serde_json::from_str(&serialized).unwrap();
assert_eq!(point, deserialized);
}
#[test]
fn test_high_precision_values() {
let point = Point2D {
x: dec!(1.12345678901234567890),
y: dec!(2.12345678901234567890),
};
let serialized = serde_json::to_string(&point).unwrap();
let deserialized: Point2D = serde_json::from_str(&serialized).unwrap();
assert_eq!(point.x, deserialized.x);
assert_eq!(point.y, deserialized.y);
}
#[test]
fn test_json_structure() {
let point = Point2D {
x: dec!(1.5),
y: dec!(2.5),
};
let serialized = serde_json::to_string(&point).unwrap();
let json_value: Value = serde_json::from_str(&serialized).unwrap();
assert!(json_value.is_object());
assert_eq!(json_value.as_object().unwrap().len(), 2);
assert!(json_value.get("x").is_some());
assert!(json_value.get("y").is_some());
}
#[test]
fn test_pretty_print() {
let point = Point2D {
x: dec!(1.5),
y: dec!(2.5),
};
let serialized = serde_json::to_string_pretty(&point).unwrap();
assert!(serialized.contains('\n'));
assert!(serialized.contains(" "));
let deserialized: Point2D = serde_json::from_str(&serialized).unwrap();
assert_eq!(point, deserialized);
}
#[test]
fn test_deserialize_from_integers() {
let json_str = r#"{"x": 1, "y": 2}"#;
let point: Point2D = serde_json::from_str(json_str).unwrap();
assert_eq!(point.x, dec!(1.0));
assert_eq!(point.y, dec!(2.0));
}
#[test]
fn test_deserialize_from_strings() {
let json_str = r#"{"x": "1.5", "y": "2.5"}"#;
let point: Point2D = serde_json::from_str(json_str).unwrap();
assert_eq!(point.x, dec!(1.5));
assert_eq!(point.y, dec!(2.5));
}
#[test]
fn test_invalid_json() {
let json_str = r#"{"x": 1.5}"#;
let result = serde_json::from_str::<Point2D>(json_str);
assert!(result.is_err());
let json_str = r#"{"x": "invalid", "y": 2.5}"#;
let result = serde_json::from_str::<Point2D>(json_str);
assert!(result.is_err());
let json_str = r#"{"x": true, "y": 2.5}"#;
let result = serde_json::from_str::<Point2D>(json_str);
assert!(result.is_err());
}
#[test]
fn test_max_values() {
let point = Point2D {
x: Decimal::MAX,
y: Decimal::MAX,
};
let serialized = serde_json::to_string(&point).unwrap();
let deserialized: Point2D = serde_json::from_str(&serialized).unwrap();
assert_eq!(point, deserialized);
}
#[test]
fn test_min_values() {
let point = Point2D {
x: Decimal::MIN,
y: Decimal::MIN,
};
let serialized = serde_json::to_string(&point).unwrap();
let deserialized: Point2D = serde_json::from_str(&serialized).unwrap();
assert_eq!(point, deserialized);
}
#[test]
fn test_json_to_vec() {
let points = vec![
Point2D {
x: dec!(1.0),
y: dec!(2.0),
},
Point2D {
x: dec!(3.0),
y: dec!(4.0),
},
];
let serialized = serde_json::to_string(&points).unwrap();
let deserialized: Vec<Point2D> = serde_json::from_str(&serialized).unwrap();
assert_eq!(points, deserialized);
}
#[test]
fn test_duplicate_fields() {
let json_str = r#"{"x": 1.5, "y": 2.5, "x": 3.5}"#;
let result = serde_json::from_str::<Point2D>(json_str);
assert!(result.is_err());
}
#[test]
fn test_extra_fields() {
let json_str = r#"{"x": 1.5, "y": 2.5, "z": 3.5}"#;
let result = serde_json::from_str::<Point2D>(json_str);
assert!(result.is_ok());
}
#[test]
fn test_unknown_fields() {
let json_str = r#"{"x": 1.5, "r": 2.5, "z": 3.5}"#;
let result = serde_json::from_str::<Point2D>(json_str);
assert!(result.is_err());
}
#[test]
fn test_array() {
let json_str = "[1.5, 2.5]";
let result = serde_json::from_str::<Point2D>(json_str);
assert!(result.is_ok());
}
}
#[cfg(test)]
mod tests_edge_cases {
use super::*;
use positive::Positive;
use rust_decimal_macros::dec;
#[test]
fn test_to_tuple_positive_constraint_x() {
let point = Point2D::new(dec!(-1.0), dec!(2.0));
let result: Result<(Positive, Decimal), _> = point.to_tuple();
assert!(result.is_err());
match result {
Err(CurveError::Point2DError { reason }) => {
assert_eq!(reason, "x must be positive for type T");
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_to_tuple_positive_constraint_y() {
let point = Point2D::new(dec!(1.0), dec!(-2.0));
let result: Result<(Decimal, Positive), _> = point.to_tuple();
assert!(result.is_err());
match result {
Err(CurveError::Point2DError { reason }) => {
assert_eq!(reason, "y must be positive for type U");
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_to_tuple_positive_constraint_both_pass() {
let point = Point2D::new(dec!(1.0), dec!(2.0));
let result: Result<(Positive, Positive), _> = point.to_tuple();
assert!(result.is_ok());
let (x, y) = result.unwrap();
assert_eq!(x.value(), dec!(1.0));
assert_eq!(y.value(), dec!(2.0));
}
#[test]
fn test_debug_output() {
let point = Point2D::new(dec!(3.5), dec!(-2.25));
let debug_str = format!("{point:?}");
assert!(debug_str.contains("3.5"));
assert!(debug_str.contains("-2.25"));
}
#[test]
fn test_comparison_operators() {
let p1 = Point2D::new(dec!(1.0), dec!(2.0));
let p2 = Point2D::new(dec!(2.0), dec!(1.0));
let p3 = Point2D::new(dec!(1.0), dec!(3.0));
assert!(p1 < p2);
assert!(p1 <= p2);
assert!(p2 > p1);
assert!(p2 >= p1);
assert!(p1 < p3);
assert!(p1 <= p3);
assert!(p3 > p1);
assert!(p3 >= p1);
}
#[test]
fn test_ordering_consistency() {
let points = vec![
Point2D::new(dec!(3.0), dec!(1.0)),
Point2D::new(dec!(1.0), dec!(3.0)),
Point2D::new(dec!(2.0), dec!(2.0)),
Point2D::new(dec!(1.0), dec!(1.0)),
];
let mut sorted = points.clone();
sorted.sort();
assert_eq!(sorted[0].x, dec!(1.0));
assert_eq!(sorted[0].y, dec!(1.0));
assert_eq!(sorted[1].x, dec!(1.0));
assert_eq!(sorted[1].y, dec!(3.0));
assert_eq!(sorted[2].x, dec!(2.0));
assert_eq!(sorted[2].y, dec!(2.0));
assert_eq!(sorted[3].x, dec!(3.0));
assert_eq!(sorted[3].y, dec!(1.0));
}
}
#[cfg(test)]
mod tests_performance {
use super::*;
use std::time::{Duration, Instant};
#[test]
#[ignore]
fn test_creation_performance() {
let start = Instant::now();
for i in 0..10000 {
let x = Decimal::from(i);
let y = Decimal::from(i * 2);
let _ = Point2D::new(x, y);
}
let duration = start.elapsed();
assert!(duration < Duration::from_millis(100));
}
#[test]
#[ignore]
fn test_to_tuple_performance() {
let points: Vec<Point2D> = (0..10000)
.map(|i| Point2D::new(Decimal::from(i), Decimal::from(i * 2)))
.collect();
let start = Instant::now();
for point in &points {
let _: Result<(Decimal, Decimal), _> = point.to_tuple();
}
let duration = start.elapsed();
assert!(duration < Duration::from_millis(100));
}
#[test]
#[ignore]
fn test_comparison_performance() {
let points: Vec<Point2D> = (0..10000)
.map(|i| Point2D::new(Decimal::from(i % 100), Decimal::from(i / 100)))
.collect();
let start = Instant::now();
let mut sorted = points.clone();
sorted.sort();
let duration = start.elapsed();
assert!(duration < Duration::from_millis(200));
}
#[test]
#[ignore]
fn test_from_f64_tuple_performance() {
let start = Instant::now();
for i in 0..10000 {
let _ = Point2D::from_f64_tuple(i as f64, (i * 2) as f64);
}
let duration = start.elapsed();
assert!(duration < Duration::from_millis(200));
}
#[test]
#[ignore]
fn test_to_f64_tuple_performance() {
let points: Vec<Point2D> = (0..10000)
.map(|i| Point2D::new(Decimal::from(i), Decimal::from(i * 2)))
.collect();
let start = Instant::now();
for point in &points {
let _ = point.to_f64_tuple();
}
let duration = start.elapsed();
assert!(duration < Duration::from_millis(200));
}
}
#[cfg(test)]
mod tests_point2d_specific_cases {
use super::*;
use positive::Positive;
use crate::error::CurveError;
use rust_decimal_macros::dec;
#[test]
fn test_to_tuple_positive_constraints() {
let point = Point2D::new(dec!(0.0), dec!(1.0));
let result: Result<(Positive, Decimal), _> = point.to_tuple();
assert!(result.is_err());
match result {
Err(CurveError::Point2DError { reason }) => {
assert_eq!(reason, "x must be positive for type T");
}
_ => panic!("Expected Point2DError"),
}
let point = Point2D::new(dec!(1.0), dec!(0.0));
let result: Result<(Decimal, Positive), _> = point.to_tuple();
assert!(result.is_err());
match result {
Err(CurveError::Point2DError { reason }) => {
assert_eq!(reason, "y must be positive for type U");
}
_ => panic!("Expected Point2DError"),
}
}
#[test]
fn test_from_f64_tuple_error_handling() {
let result = Point2D::from_f64_tuple(f64::INFINITY, 1.0);
assert!(result.is_err());
match result {
Err(CurveError::Point2DError { reason }) => {
assert_eq!(reason, "Error converting f64 to Decimal");
}
_ => panic!("Expected Point2DError"),
}
let result = Point2D::from_f64_tuple(1.0, f64::NAN);
assert!(result.is_err());
match result {
Err(CurveError::Point2DError { reason }) => {
assert_eq!(reason, "Error converting f64 to Decimal");
}
_ => panic!("Expected Point2DError"),
}
}
}