geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
mod function;
mod schema;

use self::function::{
    UriProvider, codeunit_slice, parse_query, percent_decode, percent_encode, pop_codeunit,
};
use self::schema::{CodeunitPair, PercentDecodeResult, QueryConstructions, QueryResult};
use super::GleamStdlibHostProfile;
use crate::{HostProviderModule, HostRegistrationError};
use ecow::EcoString;
use num_bigint::BigInt;

pub(super) fn host_provider<Profile>() -> Result<HostProviderModule<Profile>, HostRegistrationError>
where
    Profile: GleamStdlibHostProfile,
{
    HostProviderModule::new("gleam_stdlib", "gleam/uri")
        .and_then(|provider| {
            provider.with_scoped_function::<UriProvider<Profile>, (EcoString,), CodeunitPair, _>(
                "pop_codeunit",
                pop_codeunit::<Profile>,
            )
        })
        .and_then(|provider| {
            provider.with_fallible_function::<(EcoString, BigInt, BigInt), EcoString, _>(
                "codeunit_slice",
                codeunit_slice,
            )
        })
        .and_then(|provider| {
            provider.with_scoped_function_and_constructions::<
                UriProvider<Profile>,
                (EcoString,),
                QueryResult,
                QueryConstructions,
                _,
            >("parse_query", parse_query::<Profile>)
        })
        .and_then(|provider| provider.with_function("percent_encode", percent_encode))
        .and_then(|provider| {
            provider
                .with_scoped_function::<UriProvider<Profile>, (EcoString,), PercentDecodeResult, _>(
                    "percent_decode",
                    percent_decode::<Profile>,
                )
        })
}

#[cfg(test)]
mod tests {
    use super::host_provider;
    use crate::ValueType;
    use crate::gleam_stdlib::GleamStdlibProfile;
    use crate::plan::{CustomType, CustomTypeName, FunctionType};

    #[test]
    fn registers_only_the_bodyless_official_uri_externals() {
        let provider =
            host_provider::<GleamStdlibProfile>().expect("official URI provider should register");

        assert_eq!(provider.package(), "gleam_stdlib");
        assert_eq!(provider.module(), "gleam/uri");
        assert_eq!(provider.external_types().count(), 0);
        let result = |success| {
            ValueType::Custom(CustomType::new(
                CustomTypeName::new("".into(), "gleam".into(), "Result".into()),
                vec![success, ValueType::Nil],
            ))
        };
        let expected = [
            (
                "pop_codeunit",
                FunctionType::new(
                    vec![ValueType::String],
                    ValueType::Tuple(vec![ValueType::Int, ValueType::String]),
                ),
            ),
            (
                "codeunit_slice",
                FunctionType::new(
                    vec![ValueType::String, ValueType::Int, ValueType::Int],
                    ValueType::String,
                ),
            ),
            (
                "parse_query",
                FunctionType::new(
                    vec![ValueType::String],
                    result(ValueType::List(Box::new(ValueType::Tuple(vec![
                        ValueType::String,
                        ValueType::String,
                    ])))),
                ),
            ),
            (
                "percent_encode",
                FunctionType::new(vec![ValueType::String], ValueType::String),
            ),
            (
                "percent_decode",
                FunctionType::new(vec![ValueType::String], result(ValueType::String)),
            ),
        ];
        let functions = provider.functions().collect::<Vec<_>>();

        assert_eq!(functions.len(), expected.len());
        for (function, (name, type_)) in functions.into_iter().zip(expected) {
            assert_eq!(function.name(), name);
            assert!(function.scheme().is_monomorphic());
            assert_eq!(function.type_(), &type_);
        }
    }
}