iac-forge 0.1.6

Platform-independent infrastructure-as-code generator from OpenAPI specs
Documentation
use thiserror::Error;

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum IacForgeError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("TOML parse error: {0}")]
    Toml(#[from] toml::de::Error),

    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("OpenAPI error: {0}")]
    OpenApi(#[from] openapi_forge::ForgeError),

    #[error("missing CRUD endpoint: {resource} needs {endpoint}")]
    MissingEndpoint { resource: String, endpoint: String },

    #[error("schema not found in spec: {0}")]
    SchemaNotFound(String),

    #[error("unknown type override: {value} on field {field}")]
    UnknownTypeOverride { field: String, value: String },

    #[error("validation error: {0}")]
    ValidationError(String),

    #[error("backend error: {0}")]
    BackendError(String),
}

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

    #[test]
    fn display_io_error() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
        let err = IacForgeError::Io(io_err);
        let msg = err.to_string();
        assert!(msg.contains("IO error"), "got: {msg}");
        assert!(msg.contains("file missing"), "got: {msg}");
    }

    #[test]
    fn from_io_error() {
        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
        let err: IacForgeError = io_err.into();
        assert!(matches!(err, IacForgeError::Io(_)));
    }

    #[test]
    fn display_toml_error() {
        let toml_err = toml::from_str::<toml::Value>("{{invalid").unwrap_err();
        let err = IacForgeError::Toml(toml_err);
        let msg = err.to_string();
        assert!(msg.contains("TOML parse error"), "got: {msg}");
    }

    #[test]
    fn from_toml_error() {
        let toml_err = toml::from_str::<toml::Value>("{{").unwrap_err();
        let err: IacForgeError = toml_err.into();
        assert!(matches!(err, IacForgeError::Toml(_)));
    }

    #[test]
    fn display_json_error() {
        let json_err = serde_json::from_str::<serde_json::Value>("{bad}").unwrap_err();
        let err = IacForgeError::Json(json_err);
        let msg = err.to_string();
        assert!(msg.contains("JSON error"), "got: {msg}");
    }

    #[test]
    fn from_json_error() {
        let json_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
        let err: IacForgeError = json_err.into();
        assert!(matches!(err, IacForgeError::Json(_)));
    }

    #[test]
    fn display_missing_endpoint() {
        let err = IacForgeError::MissingEndpoint {
            resource: "my_secret".to_string(),
            endpoint: "/create-secret".to_string(),
        };
        let msg = err.to_string();
        assert!(msg.contains("my_secret"), "got: {msg}");
        assert!(msg.contains("/create-secret"), "got: {msg}");
        assert!(msg.contains("missing CRUD endpoint"), "got: {msg}");
    }

    #[test]
    fn display_schema_not_found() {
        let err = IacForgeError::SchemaNotFound("CreateFoo".to_string());
        let msg = err.to_string();
        assert!(msg.contains("CreateFoo"), "got: {msg}");
        assert!(msg.contains("schema not found"), "got: {msg}");
    }

    #[test]
    fn display_unknown_type_override() {
        let err = IacForgeError::UnknownTypeOverride {
            field: "status".to_string(),
            value: "BADTYPE".to_string(),
        };
        let msg = err.to_string();
        assert!(msg.contains("unknown type override"), "got: {msg}");
        assert!(msg.contains("status"), "got: {msg}");
        assert!(msg.contains("BADTYPE"), "got: {msg}");
    }

    #[test]
    fn error_source_unknown_type_override_is_none() {
        use std::error::Error;
        let err = IacForgeError::UnknownTypeOverride {
            field: "f".to_string(),
            value: "v".to_string(),
        };
        assert!(err.source().is_none());
    }

    #[test]
    fn display_validation_error() {
        let err = IacForgeError::ValidationError("field X is invalid".to_string());
        let msg = err.to_string();
        assert!(msg.contains("validation error"), "got: {msg}");
        assert!(msg.contains("field X is invalid"), "got: {msg}");
    }

    #[test]
    fn display_backend_error() {
        let err = IacForgeError::BackendError("template rendering failed".to_string());
        let msg = err.to_string();
        assert!(msg.contains("backend error"), "got: {msg}");
        assert!(msg.contains("template rendering failed"), "got: {msg}");
    }

    #[test]
    fn error_is_send_and_sync() {
        fn assert_send_sync<T: Send + Sync>() {}
        assert_send_sync::<IacForgeError>();
    }

    #[test]
    fn error_source_io() {
        use std::error::Error;
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
        let err = IacForgeError::Io(io_err);
        assert!(err.source().is_some());
    }

    #[test]
    fn error_source_json() {
        use std::error::Error;
        let json_err = serde_json::from_str::<serde_json::Value>("bad").unwrap_err();
        let err = IacForgeError::Json(json_err);
        assert!(err.source().is_some());
    }

    #[test]
    fn error_source_toml() {
        use std::error::Error;
        let toml_err = toml::from_str::<toml::Value>("{{").unwrap_err();
        let err = IacForgeError::Toml(toml_err);
        assert!(err.source().is_some());
    }

    #[test]
    fn error_source_backend_is_none() {
        use std::error::Error;
        let err = IacForgeError::BackendError("fail".to_string());
        assert!(err.source().is_none());
    }

    #[test]
    fn error_source_validation_is_none() {
        use std::error::Error;
        let err = IacForgeError::ValidationError("invalid".to_string());
        assert!(err.source().is_none());
    }

    #[test]
    fn error_source_schema_not_found_is_none() {
        use std::error::Error;
        let err = IacForgeError::SchemaNotFound("Schema".to_string());
        assert!(err.source().is_none());
    }

    #[test]
    fn error_source_missing_endpoint_is_none() {
        use std::error::Error;
        let err = IacForgeError::MissingEndpoint {
            resource: "r".to_string(),
            endpoint: "/ep".to_string(),
        };
        assert!(err.source().is_none());
    }

    #[test]
    fn debug_format_includes_variant() {
        let err = IacForgeError::SchemaNotFound("TestSchema".to_string());
        let debug = format!("{err:?}");
        assert!(debug.contains("SchemaNotFound"), "got: {debug}");
    }

    #[test]
    fn display_missing_endpoint_with_empty_strings() {
        let err = IacForgeError::MissingEndpoint {
            resource: String::new(),
            endpoint: String::new(),
        };
        let msg = err.to_string();
        assert!(msg.contains("missing CRUD endpoint"), "got: {msg}");
    }
}