toolforge 5.8.0

Small library for common tasks on Wikimedia Toolforge
Documentation
/*
Small library for common tasks on Wikimedia Toolforge
Copyright (C) 2013, 2017, 2020, 2022 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/>.
 */
//! The `toolforge` crate provides helper functions for various tasks on
//! [Toolforge](https://toolforge.org/).
//!
//! ## Constructing a User-Agent
//! The `user_agent!` macro generates a tool-specific `User-Agent` header in
//! compliance with [Wikimedia's User-Agent policy](https://meta.wikimedia.org/wiki/User-Agent_policy).
//! The first and only required parameter is the tool's name. Optional second
//! and third parameters can be overrides to the url and email, respectively.
//!
//! ### Example
//! ```rust
//! const USER_AGENT: &str = toolforge::user_agent!("mycooltool");
//!
//! assert_eq!(
//!     USER_AGENT,
//!     "https://mycooltool.toolforge.org/ tools.mycooltool@toolforge.org"
//! );
//! ```
//!
//! ## WikiReplica connection helpers
//! The `connection_info!` macro builds the URL string to connect to [Wiki Replicas](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database)
//! for use with  the [`mysql_async`](https://docs.rs/mysql_async) crate. It
//! should also work with the [`mysql`](https://docs.rs/mysql/) crate.
//! Specifically, it follows the [Toolforge connection handling policy](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database#Connection_handling_policy).
//!
//! The first argument is the database name, with or without a `_p` suffix.
//! The second (optional) argument is which cluster to connect to, either
//! `WEB` (default) or `ANALYTICS`.
//!
//! ### Example
//! ```rust
//! # #[cfg(feature = "mysql")]
//! # {
//! use mysql_async::Pool;
//! # async fn demo() -> toolforge::Result<()> {
//! let pool = Pool::new(
//!     toolforge::connection_info!("enwiki", WEB)
//!         .expect("unable to load db config")
//!         .to_string()
//!         .as_str(),
//! );
//! let mut conn = pool.get_conn().await?;
//! # Ok(())
//! # }
//! # }
//! ```
//!
//! ### Local development
//! Copy your tool's `replica.my.cnf` to your local machine, saving it to
//! `~/replica.my.cnf` and add a `local='true'` flag. For example:
//! ```text
//! [client]
//! user='u####'
//! password='...'
//! local='true'
//! ```
//!
//! Then open a [SSH tunnel](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database#SSH_tunneling_for_local_testing_which_makes_use_of_Wiki_Replica_databases)
//! on port 3306 to the specific wiki replicas database you want to access.
//! The `toolforge-tunnel` helper described below can simplify this.
//!
//! ## `WikiPool` feature
//! `WikiPool` is a wrapper around `mysql_async::Pool` that given a database
//! name, will connect to the appropriate backend server. It is smart enough to
//! know which databases are on the same server so connections can be optimally
//! reused for better performance. This is behind the `wikipool` feature.
//!
//! ## `toolforge-tunnel` helper
//! The `toolforge-tunnel` tool simplifies the process of opening SSH tunnels
//! to wiki replicas. Example usage: `toolforge-tunnel enwiki`.
//!
//! It can be installed via cargo: `cargo install toolforge --features=cli`.
//! Pre-built binaries can be downloaded from [GitLab](https://gitlab.wikimedia.org/repos/mwbot-rs/toolforge/-/releases).
//!
//! ## Contributing
//! `toolforge` is a part of the [`mwbot-rs` project](https://www.mediawiki.org/wiki/Mwbot-rs).
//! We're always looking for new contributors, please [reach out](https://www.mediawiki.org/wiki/Mwbot-rs#Contributing)
//! if you're interested!

#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(clippy::all)]
#![deny(rustdoc::all)]

#[cfg(feature = "mysql")]
#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
pub mod db;
#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
#[cfg(feature = "mysql")]
mod error;
#[cfg_attr(docsrs, doc(cfg(feature = "wikipool")))]
#[cfg(feature = "wikipool")]
pub mod pool;

#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
#[cfg(feature = "mysql")]
pub use error::Error;
#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
#[cfg(feature = "mysql")]
pub type Result<A> = std::result::Result<A, Error>;

/// Generates a Tool-specific `User-Agent` header in compliance with
/// [Wikimedia's User-Agent policy](https://meta.wikimedia.org/wiki/User-Agent_policy).
/// The first and only required parameter is the tool's name. Optional second
/// and third parameters can be overrides to the url and email, respectively.
///
/// # Example
/// ```rust
/// const USER_AGENT: &str = toolforge::user_agent!("mycooltool");
///
/// assert_eq!(
///     USER_AGENT,
///     "https://mycooltool.toolforge.org/ tools.mycooltool@toolforge.org"
/// );
/// ```
#[macro_export]
macro_rules! user_agent {
    ( $tool:expr ) => {
        concat!(
            "https://",
            $tool,
            ".toolforge.org/ tools.",
            $tool,
            "@toolforge.org"
        )
    };
    ( $tool:expr, $url:expr ) => {
        concat!($url, " tools.", $tool, "@toolforge.org")
    };
    ( $tool:expr, $url:expr, $email:expr ) => {
        concat!($url, " ", $email)
    };
}

/// Get connection information for the [Toolforge databases](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database).
///
/// The first argument is the database name, with or without a `_p` suffix.
/// The second (optional) argument is which cluster to connect to, either
/// `WEB` (default) or `ANALYTICS`.
///
/// You can also connect to the the [`meta_p`](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database#Metadata_database)
/// database this way.
///
/// ```rust
/// # fn demo() -> toolforge::Result<()> {
/// assert_eq!(
///     "mysql://user:pass@enwiki.web.db.svc.eqiad.wmflabs:3306/enwiki_p".to_string(),
///     toolforge::connection_info!("enwiki")?.to_string()
/// );
/// assert_eq!(
///     "mysql://user:pass@enwiki.analytics.db.svc.eqiad.wmflabs:3306/enwiki_p".to_string(),
///     toolforge::connection_info!(
///         "enwiki",
///         ANALYTICS
///     )?.to_string()
/// );
/// # Ok(())
/// # }
/// ```
///
/// ## Local development
/// It is possible to set up a SSH tunnel to the wiki replicas to allow for
/// local testing of MySQL connections. Copy your `replica.my.cnf` to your
/// machine, and add `local='true'` to it. With that set, this library
/// will automatically connect to port 3306 on your local machine.
#[cfg(feature = "mysql")]
#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
#[macro_export]
macro_rules! connection_info {
    ( $dbname:expr ) => {
        $crate::db::get_db_connection_info(
            $dbname,
            $crate::db::Cluster::WEB,
            None,
        )
    };
    ( $dbname:expr, $cluster:ident ) => {
        $crate::db::get_db_connection_info(
            $dbname,
            $crate::db::Cluster::$cluster,
            None,
        )
    };
}

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

    const USER_AGENT: &str = user_agent!("foo");

    #[test]
    fn test_const_user_agent() {
        assert_eq!(
            USER_AGENT,
            "https://foo.toolforge.org/ tools.foo@toolforge.org"
        );
    }

    #[test]
    fn test_user_agent() {
        assert_eq!(
            user_agent!("foo"),
            "https://foo.toolforge.org/ tools.foo@toolforge.org"
        );
        assert_eq!(
            user_agent!("foo", "https://example.org/"),
            "https://example.org/ tools.foo@toolforge.org"
        );
        assert_eq!(
            user_agent!("foo", "https://example.org/", "tool@example.org"),
            "https://example.org/ tool@example.org"
        );
    }
}