///|
/// SpecError is the common validation failure type for component
/// specifications.
///
/// Constructors carry a human-readable message so callers can report the exact
/// parameter that was rejected instead of guessing which check failed.
pub(all) enum SpecError {
InvalidSize(String)
UnsupportedLength(String)
InvalidDimension(String)
InvalidParameter(String)
}
///|
pub fn SpecError::message(self : SpecError) -> String {
match self {
InvalidSize(m) => m
UnsupportedLength(m) => m
InvalidDimension(m) => m
InvalidParameter(m) => m
}
}
///|
pub fn SpecError::to_string(self : SpecError) -> String {
self.message()
}
///|
/// is_finite rejects NaN and infinities so a component cannot be built from a
/// value that would corrupt the STEP geometry.
pub fn is_finite(v : Double) -> Bool {
v == v && v.abs() < 1.0e300
}
///|
/// require_positive validates a strictly positive, finite dimension.
pub fn require_positive(name : String, v : Double) -> Result[Unit, SpecError] {
if !is_finite(v) {
return Err(InvalidDimension("\{name} must be finite, got \{v}"))
}
if v <= 0.0 {
return Err(InvalidDimension("\{name} must be > 0, got \{v}"))
}
Ok(())
}
///|
/// require_non_negative validates a finite dimension that may be zero.
pub fn require_non_negative(
name : String,
v : Double,
) -> Result[Unit, SpecError] {
if !is_finite(v) {
return Err(InvalidDimension("\{name} must be finite, got \{v}"))
}
if v < 0.0 {
return Err(InvalidDimension("\{name} must be >= 0, got \{v}"))
}
Ok(())
}
///|
/// require_in_range validates a finite value inside an inclusive range.
pub fn require_in_range(
name : String,
v : Double,
min : Double,
max : Double,
) -> Result[Unit, SpecError] {
if !is_finite(v) {
return Err(InvalidDimension("\{name} must be finite, got \{v}"))
}
if v < min || v > max {
return Err(
InvalidParameter("\{name} must be within [\{min}, \{max}] mm, got \{v}"),
)
}
Ok(())
}
///|
/// near compares two lengths with an absolute tolerance in millimetres.
pub fn near(a : Double, b : Double, tol~ : Double = 1.0e-9) -> Bool {
(a - b).abs() <= tol
}
///|
/// expect_ok unwraps a validated specification, aborting with the validation
/// message when it is invalid. Use this at the top of an executable where a bad
/// command-line value should stop the program.
pub fn[T] expect_ok(r : Result[T, SpecError]) -> T {
match r {
Ok(v) => v
Err(e) => abort(e.message())
}
}
///|
/// expect_mm converts a Length to millimetres, rejecting non-finite values.
pub fn length_mm(name : String, v : @cad.Length) -> Result[Double, SpecError] {
let mm = v.to_mm()
match require_positive(name, mm) {
Ok(_) => Ok(mm)
Err(e) => Err(e)
}
}