gd 0.0.0

Geometry Dash API wrapper written in Rust.
Documentation
use std::{borrow::Cow, convert::Infallible};

use base64::engine::{general_purpose::URL_SAFE, Engine};
use miette::Diagnostic;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{crypto::utf8, thunk};

#[derive(Debug, Error, Diagnostic)]
#[error("failed to decode base64")]
#[diagnostic(code(gd::crypto::base64::decode), help("make sure input is valid"))]
pub struct DecodeError(#[from] pub base64::DecodeError);

pub fn encode<D: AsRef<[u8]>>(data: D) -> String {
    fn encode_inner(data: &[u8]) -> String {
        URL_SAFE.encode(data)
    }

    encode_inner(data.as_ref())
}

pub fn decode<D: AsRef<[u8]>>(data: D) -> Result<Vec<u8>, DecodeError> {
    fn decode_inner(data: &[u8]) -> Result<Vec<u8>, DecodeError> {
        URL_SAFE.decode(data).map_err(DecodeError)
    }

    decode_inner(data.as_ref())
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Processor;

impl thunk::Processor for Processor {
    type ProcessError = DecodeError;
    type UnprocessError = Infallible;

    type Output<'a> = Cow<'a, [u8]>;

    fn process(unprocessed: Cow<'_, str>) -> Result<Self::Output<'_>, Self::ProcessError> {
        decode(unprocessed.as_ref()).map(Cow::Owned)
    }

    fn unprocess<'p>(
        processed: &'p Self::Output<'_>,
    ) -> Result<Cow<'p, str>, Self::UnprocessError> {
        Ok(Cow::Owned(encode(processed.as_ref())))
    }
}

#[derive(Debug, Error, Diagnostic)]
#[error(transparent)]
#[diagnostic(transparent)]
pub enum ErrorSource {
    Decode(#[from] DecodeError),
    Utf8(#[from] utf8::Error),
}

#[derive(Debug, Error, Diagnostic)]
#[error("failed to decode base64 into string")]
#[diagnostic(
    code(gd::crypto::base64::decode_string),
    help("see the report for more information")
)]
pub struct Error {
    #[source]
    #[diagnostic_source]
    pub source: ErrorSource,
}

impl Error {
    pub fn new(source: ErrorSource) -> Self {
        Self { source }
    }

    pub fn decode(error: DecodeError) -> Self {
        Self::new(error.into())
    }

    pub fn utf8(error: utf8::Error) -> Self {
        Self::new(error.into())
    }
}

pub fn decode_string<D: AsRef<[u8]>>(data: D) -> Result<String, Error> {
    let decoded = decode(data).map_err(Error::decode)?;

    utf8::convert(decoded).map_err(Error::utf8)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct StringProcessor;

impl thunk::Processor for StringProcessor {
    type ProcessError = Error;
    type UnprocessError = Infallible;

    type Output<'a> = Cow<'a, str>;

    fn process(unprocessed: Cow<'_, str>) -> Result<Self::Output<'_>, Self::ProcessError> {
        decode_string(unprocessed.as_ref()).map(Cow::Owned)
    }

    fn unprocess<'p>(
        processed: &'p Self::Output<'_>,
    ) -> Result<Cow<'p, str>, Self::UnprocessError> {
        Ok(Cow::Owned(encode(processed.as_ref())))
    }
}