oxiproj-core 0.1.0

Foundation types for OxiProj: coordinates, errors, ellipsoids, datums, and units.
Documentation
//! Error type ported from PROJ `src/proj.h` (PROJ_ERR_* codes).

use thiserror::Error;

/// Error type mirroring PROJ's `PROJ_ERR_*` codes.
///
/// The numeric codes are grouped by base values defined in `src/proj.h`:
/// - `PROJ_ERR_INVALID_OP` (1024): invalid operation / wrong setup.
/// - `PROJ_ERR_COORD_TRANSFM` (2048): coordinate transformation failures.
/// - `PROJ_ERR_OTHER` (4096): all other errors.
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ProjError {
    /// Generic invalid operation (`PROJ_ERR_INVALID_OP` = 1024).
    #[error("invalid PROJ string syntax or operation")]
    InvalidOp,

    /// Invalid PROJ string syntax (`PROJ_ERR_INVALID_OP_WRONG_SYNTAX` = 1025).
    #[error("invalid PROJ string syntax")]
    WrongSyntax,

    /// Missing required operation parameter
    /// (`PROJ_ERR_INVALID_OP_MISSING_ARG` = 1026).
    #[error("missing required operation parameter")]
    MissingArg,

    /// One of the operation parameters has an illegal value
    /// (`PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE` = 1027).
    #[error("one of the operation parameters has an illegal value")]
    IllegalArgValue,

    /// Mutually exclusive arguments
    /// (`PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS` = 1028).
    #[error("mutually exclusive arguments")]
    MutuallyExclusiveArgs,

    /// File not found or with invalid content
    /// (`PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID` = 1029).
    #[error("file not found or with invalid content")]
    FileNotFound,

    /// Value does not correspond to an ellipsoid (maps to code 1027,
    /// same as [`ProjError::IllegalArgValue`]).
    #[error("value does not correspond to an ellipsoid")]
    NotEllipsoid,

    /// Generic error of coordinate transformation
    /// (`PROJ_ERR_COORD_TRANSFM` = 2048).
    #[error("generic error of coordinate transformation")]
    CoordTransfm,

    /// Invalid coordinate (`PROJ_ERR_COORD_TRANSFM_INVALID_COORD` = 2049).
    #[error("invalid coordinate")]
    InvalidCoord,

    /// Coordinate outside projection domain
    /// (`PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN` = 2050).
    #[error("coordinate outside projection domain")]
    OutsideProjectionDomain,

    /// No operation found matching criteria
    /// (`PROJ_ERR_COORD_TRANSFM_NO_OPERATION` = 2051).
    #[error("no operation found matching criteria")]
    NoOperation,

    /// Point to transform falls outside grid or subgrid
    /// (`PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID` = 2052).
    #[error("point to transform falls outside grid or subgrid")]
    OutsideGrid,

    /// Point to transform falls in a grid cell that evaluates to nodata
    /// (`PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA` = 2053).
    #[error("point to transform falls in a grid cell that evaluates to nodata")]
    GridAtNodata,

    /// Iterative algorithm did not converge
    /// (`PROJ_ERR_COORD_TRANSFM_NO_CONVERGENCE` = 2054).
    #[error("iterative algorithm did not converge")]
    NoConvergence,

    /// Missing required time coordinate
    /// (`PROJ_ERR_COORD_TRANSFM_MISSING_TIME` = 2055).
    #[error("missing required time coordinate")]
    MissingTime,

    /// Other error (`PROJ_ERR_OTHER` = 4096).
    #[error("other error")]
    Other,

    /// Error caused by incorrect use of PROJ API
    /// (`PROJ_ERR_OTHER_API_MISUSE` = 4097).
    #[error("error caused by incorrect use of PROJ API")]
    ApiMisuse,

    /// No inverse operation (`PROJ_ERR_OTHER_NO_INVERSE_OP` = 4098).
    #[error("no inverse operation")]
    NoInverseOp,

    /// Failure when accessing a network resource
    /// (`PROJ_ERR_OTHER_NETWORK_ERROR` = 4099).
    #[error("failure when accessing a network resource")]
    NetworkError,
}

impl ProjError {
    /// Ported from src/proj.h (PROJ_ERR_* numeric codes)
    pub fn code(&self) -> i32 {
        match self {
            ProjError::InvalidOp => 1024,
            ProjError::WrongSyntax => 1025,
            ProjError::MissingArg => 1026,
            ProjError::IllegalArgValue => 1027,
            ProjError::MutuallyExclusiveArgs => 1028,
            ProjError::FileNotFound => 1029,
            ProjError::NotEllipsoid => 1027,
            ProjError::CoordTransfm => 2048,
            ProjError::InvalidCoord => 2049,
            ProjError::OutsideProjectionDomain => 2050,
            ProjError::NoOperation => 2051,
            ProjError::OutsideGrid => 2052,
            ProjError::GridAtNodata => 2053,
            ProjError::NoConvergence => 2054,
            ProjError::MissingTime => 2055,
            ProjError::Other => 4096,
            ProjError::ApiMisuse => 4097,
            ProjError::NoInverseOp => 4098,
            ProjError::NetworkError => 4099,
        }
    }
}

/// Convenience alias for results returning a [`ProjError`].
pub type ProjResult<T> = Result<T, ProjError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn invalid_op_code() {
        assert_eq!(ProjError::InvalidOp.code(), 1024);
    }

    #[test]
    fn wrong_syntax_code() {
        assert_eq!(ProjError::WrongSyntax.code(), 1025);
    }

    #[test]
    fn missing_arg_code() {
        assert_eq!(ProjError::MissingArg.code(), 1026);
    }

    #[test]
    fn illegal_arg_value_code() {
        assert_eq!(ProjError::IllegalArgValue.code(), 1027);
    }

    #[test]
    fn mutually_exclusive_args_code() {
        assert_eq!(ProjError::MutuallyExclusiveArgs.code(), 1028);
    }

    #[test]
    fn file_not_found_code() {
        assert_eq!(ProjError::FileNotFound.code(), 1029);
    }

    #[test]
    fn not_ellipsoid_code() {
        assert_eq!(ProjError::NotEllipsoid.code(), 1027);
    }

    #[test]
    fn coord_transfm_code() {
        assert_eq!(ProjError::CoordTransfm.code(), 2048);
    }

    #[test]
    fn invalid_coord_code() {
        assert_eq!(ProjError::InvalidCoord.code(), 2049);
    }

    #[test]
    fn outside_projection_domain_code() {
        assert_eq!(ProjError::OutsideProjectionDomain.code(), 2050);
    }

    #[test]
    fn no_operation_code() {
        assert_eq!(ProjError::NoOperation.code(), 2051);
    }

    #[test]
    fn outside_grid_code() {
        assert_eq!(ProjError::OutsideGrid.code(), 2052);
    }

    #[test]
    fn grid_at_nodata_code() {
        assert_eq!(ProjError::GridAtNodata.code(), 2053);
    }

    #[test]
    fn no_convergence_code() {
        assert_eq!(ProjError::NoConvergence.code(), 2054);
    }

    #[test]
    fn missing_time_code() {
        assert_eq!(ProjError::MissingTime.code(), 2055);
    }

    #[test]
    fn other_code() {
        assert_eq!(ProjError::Other.code(), 4096);
    }

    #[test]
    fn api_misuse_code() {
        assert_eq!(ProjError::ApiMisuse.code(), 4097);
    }

    #[test]
    fn no_inverse_op_code() {
        assert_eq!(ProjError::NoInverseOp.code(), 4098);
    }

    #[test]
    fn network_error_code() {
        assert_eq!(ProjError::NetworkError.code(), 4099);
    }

    #[test]
    fn display_messages_non_empty() {
        assert!(!ProjError::InvalidCoord.to_string().is_empty());
        assert!(!ProjError::OutsideProjectionDomain.to_string().is_empty());
    }

    #[test]
    fn proj_result_alias_works() {
        let ok: ProjResult<i32> = Ok(7);
        let err: ProjResult<i32> = Err(ProjError::Other);
        assert_eq!(ok, Ok(7));
        assert_eq!(err, Err(ProjError::Other));
    }
}