utpm 0.3.0

UTPM is a package manager for local and remote Typst packages. Quickly create and manage projects and templates on your system, and publish them directly to Typst Universe.
Documentation
use serde::Serialize;
use thiserror::Error as TError;

/// A specialized `Result` type for UTPM operations.
pub type Result<T> = anyhow::Result<T, UtpmError>;

use serde::ser::{SerializeStruct, Serializer};
use typst_syntax::package::PackageVersion;

/// The error type for UTPM operations.
///
/// This enum consolidates all possible errors that can occur within the application,
/// providing a single, consistent error handling mechanism.
#[derive(Debug, TError)]
pub enum UtpmError {
    /// A git-related error.

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

    /// A git-related error.

    #[error("We didn't find git on your path. Try to add it.")]
    GitNotFound,

    /// An error from the `inquire` crate, used for interactive prompts.

    #[error("Inquire error: {0}")]
    Questions(#[from] inquire::InquireError),

    /// An I/O error.
    #[error("IO error: {0}")]
    IO(#[from] std::io::Error),

    /// An error during JSON serialization or deserialization.

    #[cfg(feature = "output_json")]
    #[error("Can't parse to json: {0}")]
    JsonParse(#[from] serde_json::Error),

    /// An error during Hjson serialization or deserialization.

    #[error("Can't parse to hjson: {0}")]
    #[cfg(feature = "output_hjson")]
    HJsonParse(#[from] serde_hjson::Error),

    /// An error during YAML serialization or deserialization.

    #[cfg(feature = "output_yaml")]
    #[error("Can't parse to yaml: {0}")]
    YamlParse(#[from] serde_yaml::Error),

    /// An error during TOML serialization.
    #[error("TOML serialization error: {0:?}")]
    Serialize(#[from] toml::ser::Error),

    /// An error during TOML deserialization.
    #[error("TOML deserialization error: {0}")]
    Deserialize(#[from] toml::de::Error),

    /// An error during TOML deserialization.
    #[error("TOML deserialization error: {0}")]
    DeserializeMut(#[from] toml_edit::TomlError),

    /// An error from the `ignore` crate.

    #[error("Ignore crate error: {0}")]
    Ignore(#[from] ignore::Error),

    /// An error from the `octocrab` crate for GitHub API interactions.

    #[error("Octocrab error: {0}")]
    OctoCrab(#[from] octocrab::Error),

    /// An unknown or unexpected error.
    #[error("Unknown error: {0}")]
    Unknown(String),

    /// An error when the current directory cannot be determined.
    #[error("Couldn't find the current directory.")]
    CurrentDir,

    /// An error when the home directory cannot be determined.
    #[error("Could not determine home directory.")]
    HomeDir,

    /// An error for a missing `typst.toml` manifest.
    #[error(
        "Missing typst.toml manifest file in the current directory.\nRun 'utpm prj init' to create one."
    )]
    Manifest,

    /// An error for an invalid package string.
    #[error(
        "Invalid package format.\n\nAccepted formats:\n  - Full: @namespace/package:1.0.0\n  - Without version: @namespace/package\n  - Namespace only: @namespace\n\nExample: @preview/example:1.0.0"
    )]
    PackageNotValid,

    /// An error for an invalid package string.
    #[error(
        "Package format error: the name or version is incorrect.\nExpected format: @namespace/package:version\nExample: @preview/example:1.0.0"
    )]
    PackageFormatError,

    #[error("There is no files in the new package. You should change your ignored files.")]
    NoFiles,

    #[error("Can't find `typst.toml` file in {0}. Did you omit it in your ignored files?")]
    OmitedTypstFile(String),

    #[error("Can't find {0} file in {1}. Did you omit it in your ignored files?")]
    OmitedEntryfile(String, String),

    /// An error when a specified package does not exist.
    #[error(
        "Package not found.\n\nPossible causes:\n  - Package doesn't exist in your local storage\n  - Package name or version is incorrect\n  - Package is not published yet\n\nCheck available packages:\n  - Local: utpm pkg list\n  - Remote: https://typst.app/universe"
    )]
    PackageNotExist,

    /// An error when content is found in a directory that should be empty.
    #[error("We founded content. Cancelled the operation.")]
    ContentFound,

    /// An error when a package to be linked already exists.
    #[error("{2} Package {0} with version {1} already exist.")]
    AlreadyExist(String, PackageVersion, String),

    #[error("Reqwest Error: {0}")]
    ReqwestError(#[from] reqwest::Error),

    #[error("FromUTF8 Error: {0}")]
    FromUTF8Error(#[from] std::string::FromUtf8Error),

    /// A wrapper for any other error.
    #[error(transparent)]
    Other(#[from] anyhow::Error),
}

// Custom `Serialize` implementation for `UtpmError`.
// This allows errors to be serialized into structured formats like JSON.
impl Serialize for UtpmError {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut st = serializer.serialize_struct("UtpmError", 2)?;
        st.serialize_field("type", self.variant_name())?;
        st.serialize_field("message", &self.to_string())?;
        st.end()
    }
}

impl UtpmError {
    /// Returns the string representation of the error variant.
    fn variant_name(&self) -> &'static str {
        use UtpmError::*;
        match self {
            Git(_) => "Git",

            Questions(_) => "Questions",
            IO(_) => "IO",
            Serialize(_) => "Serialize",
            Deserialize(_) => "Deserialize",
            DeserializeMut(_) => "DeserializeMut",

            Ignore(_) => "Ignore",

            OctoCrab(_) => "OctoCrab",
            Unknown(_) => "Unknown",
            CurrentDir => "CurrentDir",
            HomeDir => "HomeDir",
            Manifest => "Manifest",
            PackageNotValid => "PackageNotValid",
            PackageNotExist => "PackageNotExist",
            ContentFound => "ContentFound",
            AlreadyExist(_, _, _) => "AlreadyExist",
            Other(_) => "Other",
            #[cfg(feature = "output_json")]
            JsonParse(_) => "JSONParse",

            #[cfg(feature = "output_hjson")]
            HJsonParse(_) => "HJSONParse",

            #[cfg(feature = "output_yaml")]
            YamlParse(_) => "YamlParse",
            ReqwestError(_) => "ReqwestError",
            FromUTF8Error(_) => "FromUTF8Error",
            GitNotFound => "GitNotFound",
            PackageFormatError => "PackageFormatError",
            NoFiles => "NoFiles",
            OmitedTypstFile(_) => "OmitedTypstFile",
            OmitedEntryfile(_, _) => "OmitedEntryfile",
        }
    }
}