projects involving the gemini protocol
#![deny(
    missing_debug_implementations,
    missing_docs,
    missing_copy_implementations
)]
#![forbid(unsafe_code)]

//! A general purpose library of types for working with the Gemini protocol
//!
//! Represented are Gemini requests, responses, headers, and status codes.
//! Requests are currently just a small layer of functionality over top of a
//! `Url` from the aptly named `url` crate. All types are implemented with an
//! eye towards implementing the Gemini specification faithfully and making
//! illegal or invalid states unrepresentable.

pub mod gemtext;
pub mod header;
pub mod request;
pub mod response;
pub mod status;

pub use gemtext::{Builder, Doc, Level};
pub use header::{Header, MetaKind};
pub use request::{AnyRequest, GeminiRequest, InvalidRequest, Request, Url};
pub use response::Response;
pub use status::{Category, Code, InvalidStatusCode, Status};

/// Helper module with parsers for *COMPLETE* streams of bytes. If you want
/// streaming/resumable parsers, use the parser functions in each submodule
/// directly. Gemtext parsers only work with complete input.
#[cfg(feature = "parsers")]
pub mod parse {
    use nom::{error::Error, Finish};
    use paste::paste;

    pub use nom::Err;

    use crate::{gemtext, header, request, response, status};

    macro_rules! parsers {
        ($(
            $(#[$doc:meta])*
            $name:ident: $type:ident
        ),*) => {
            $(
                paste! {
                    $(#[$doc])*
                    pub fn [< parse_ $name >](input: impl AsRef<[u8]>) -> Result<$name::$type, Error<String>> {
                        let bytes = input.as_ref();
                        match $name::parse::$name(bytes).finish() {
                            Ok((_, res)) => Ok(res),
                            Err(Error { input, code }) => Err({
                                let bytes = input.to_owned();
                                let input = String::from_utf8_lossy(&bytes).to_string();
                                Error { input, code }
                            })
                        }
                    }
                }
            )*
        };
    }

    parsers!(
        /// Parse a complete `Header` from bytes.
        header: Header,
        /// Parse a complete `Request` from bytes.
        request: AnyRequest,
        /// Parse a complete `Response` from bytes.
        response: Response,
        /// Parse a complete `Status` from bytes.
        status: Status
    );

    /// Parse a gemtext document from utf-8 text.
    pub fn parse_gemtext(input: impl AsRef<str>) -> Result<gemtext::Builder, Error<String>> {
        let input = input.as_ref();
        match gemtext::parse::document(input).finish() {
            Ok((_, res)) => Ok(res),
            Err(Error { input, code }) => Err({
                let input = input.to_string();
                Error { input, code }
            }),
        }
    }
}