mwtitle 0.2.0-alpha.1

MediaWiki title validation and formatting
Documentation
/*
Copyright (C) 2021 Kunal Mehta <legoktm@debian.org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use crate::NamespaceAlias;
use thiserror::Error as ThisError;

#[cfg(feature = "utils")]
#[cfg_attr(docs, doc(cfg(feature = "utils")))]
use std::{path::PathBuf, sync::Arc};

/// Title validation errors
#[non_exhaustive]
#[derive(ThisError, Debug, Clone)]
pub enum Error {
    /* Title issues */
    #[error("Title contains illegal UTF-8 sequences or forbidden Unicode characters: \"{0}\"")]
    IllegalUtf8(String),
    #[error(
        "Title is empty or contains only the name of a namespace: \"{0}\""
    )]
    Empty(String),
    #[error("Title refers to a talk page that cannot exist: \"{0}\"")]
    TalkNamespace(String),
    #[error("Title has relative path: \"{0}\"")]
    Relative(String),
    #[error("Title contains invalid magic tilde sequence (~~~): \"{0}\"")]
    MagicTildes(String),
    #[error("Title is too long")]
    TooLong(String),
    #[error("Title starts with a leading colon: \"{0}\"")]
    LeadingColon(String),
    #[error("Title has invalid characters: \"{0}\"")]
    Characters(String),
    #[error("Unknown namespace aliases found")]
    UnknownAliases(Vec<NamespaceAlias>),
    #[cfg(feature = "parsing")]
    #[cfg_attr(docs, doc(cfg(feature = "parsing")))]
    #[error("Failed to generate regular expression for illegal title characters: {0}")]
    IllegalTitleRegex(#[from] regex::Error),
    #[error("Missing key in HashMap: {0}")]
    NamespaceInfoMissingKey(&'static str),
    #[error("Invalid ID in HashMap: {0}")]
    NamespaceInfoInvalidId(String),
    #[error("Failed to parse JSON at {}", path.canonicalize().as_ref().unwrap_or(path).display())]
    #[cfg(feature = "utils")]
    #[cfg_attr(docs, doc(cfg(feature = "utils")))]
    JsonFile {
        path: PathBuf,
        source: Arc<serde_json::Error>,
    },
    #[error("Failed to parse JSON")]
    #[cfg(feature = "utils")]
    #[cfg_attr(docs, doc(cfg(feature = "utils")))]
    Json { source: Arc<serde_json::Error> },
    #[error("Failed to {action} at {}", path.canonicalize().as_ref().unwrap_or(path).display())]
    #[cfg(feature = "utils")]
    #[cfg_attr(docs, doc(cfg(feature = "utils")))]
    Io {
        action: &'static str,
        source: Arc<std::io::Error>,
        path: PathBuf,
    },
}

impl Error {
    pub fn mw_title_codec_error_code(&self) -> Option<&'static str> {
        use Error::*;
        Some(match self {
            IllegalUtf8(_) => "title-invalid-utf8",
            Empty(_) => "title-invalid-empty",
            TalkNamespace(_) => "title-invalid-talk-namespace",
            Relative(_) => "title-invalid-relative",
            MagicTildes(_) => "title-invalid-magic-tilde",
            TooLong(_) => "title-invalid-too-long",
            LeadingColon(_) => "title-invalid-leading-colon",
            Characters(_) => "title-invalid-characters",
            _ => return None,
        })
    }

    #[cfg(feature = "utils")]
    pub fn from_io<P: Into<PathBuf>>(
        action: &'static str,
        source: std::io::Error,
        path: P,
    ) -> Self {
        Error::Io {
            action,
            source: Arc::new(source),
            path: path.into(),
        }
    }
}