cgp-tests 0.6.1

Context-generic programming meta crate
Documentation
use core::convert::Infallible;
use core::fmt::{Debug, Display};
use core::marker::PhantomData;

use cgp::core::error::ErrorTypeProviderComponent;
use cgp::core::field::impls::{CanDowncast, CanDowncastFields, CanUpcast};
use cgp::extra::dispatch::{
    DowncastAndHandle, ExtractFieldAndHandle, HandleFieldValue, MatchWithFieldHandlers,
    MatchWithHandlers, MatchWithValueHandlersRef,
};
use cgp::extra::handler::{
    Computer, ComputerComponent, ComputerRef, ComputerRefComponent, PromoteAsync,
};
use cgp::prelude::*;
use futures::executor::block_on;

#[derive(Debug, Eq, PartialEq, CgpData)]
pub enum FooBarBaz {
    Foo(u64),
    Bar(String),
    Baz(bool),
}

#[derive(Debug, Eq, PartialEq, CgpData)]
pub enum FooBar {
    Foo(u64),
    Bar(String),
}

#[derive(Debug, Eq, PartialEq, CgpData)]
pub enum Baz {
    Baz(bool),
}

#[derive(Debug, Eq, PartialEq, CgpData)]
pub enum BazBarFoo {
    Baz(bool),
    Bar(String),
    Foo(u64),
}

fn context_to_string(context: FooBarBaz) -> String {
    match context
        .extractor_ref()
        .extract_field(PhantomData::<Symbol!("Foo")>)
    {
        Ok(value) => value.to_string(),
        Err(remainder) => match remainder.extract_field(PhantomData::<Symbol!("Bar")>) {
            Ok(value) => value.to_string(),
            Err(remainder) => match remainder.extract_field(PhantomData::<Symbol!("Baz")>) {
                Ok(value) => value.to_string(),
                Err(remainder) => remainder.finalize_extract(),
            },
        },
    }
}

#[test]
fn test_basic_extractor() {
    assert_eq!(context_to_string(FooBarBaz::Foo(1)), "1");
    assert_eq!(
        context_to_string(FooBarBaz::Bar("hello".to_owned())),
        "hello"
    );
    assert_eq!(context_to_string(FooBarBaz::Baz(true)), "true");
}

#[test]
fn test_upcast() {
    assert_eq!(
        FooBar::Foo(1).upcast(PhantomData::<FooBarBaz>),
        FooBarBaz::Foo(1)
    );

    assert_eq!(
        FooBar::Bar("hello".to_owned()).upcast(PhantomData::<FooBarBaz>),
        FooBarBaz::Bar("hello".to_owned())
    );
}

#[test]
fn test_downcast() {
    assert_eq!(
        FooBarBaz::Foo(1).downcast(PhantomData::<FooBar>).ok(),
        Some(FooBar::Foo(1))
    );

    assert_eq!(
        FooBarBaz::Bar("hello".to_owned())
            .downcast(PhantomData::<FooBar>)
            .ok(),
        Some(FooBar::Bar("hello".to_owned()))
    );

    assert_eq!(
        FooBarBaz::Baz(true).downcast(PhantomData::<FooBar>).ok(),
        None
    );

    {
        let remainder = FooBarBaz::Baz(true)
            .downcast(PhantomData::<FooBar>)
            .unwrap_err();
        assert_eq!(
            remainder.downcast_fields(PhantomData::<Baz>).ok(),
            Some(Baz::Baz(true))
        );
    }

    assert_eq!(
        FooBarBaz::Foo(1).downcast(PhantomData::<BazBarFoo>).ok(),
        Some(BazBarFoo::Foo(1))
    );

    assert_eq!(
        FooBarBaz::Bar("hello".to_owned())
            .downcast(PhantomData::<BazBarFoo>)
            .ok(),
        Some(BazBarFoo::Bar("hello".to_owned()))
    );

    assert_eq!(
        FooBarBaz::Baz(true).downcast(PhantomData::<BazBarFoo>).ok(),
        Some(BazBarFoo::Baz(true))
    );
}

#[cgp_context]
pub struct App;

delegate_components! {
    AppComponents {
        ErrorTypeProviderComponent: UseType<Infallible>,
    }
}

#[cgp_computer]
pub fn field_to_string<Tag, Value>(Field { value, .. }: Field<Tag, Value>) -> String
where
    Value: Display,
{
    value.to_string()
}

#[cgp_computer]
pub fn value_to_string_ref<Value>(value: &Value) -> String
where
    Value: Display,
{
    value.to_string()
}

#[test]
fn test_dispatch_fields() {
    let context = App;
    let code = PhantomData::<()>;

    assert_eq!(
        MatchWithFieldHandlers::<FieldToString>::compute(&context, code, FooBarBaz::Foo(1)),
        "1"
    );

    assert_eq!(
        MatchWithFieldHandlers::<FieldToString>::compute(
            &context,
            code,
            FooBarBaz::Bar("hello".to_owned())
        ),
        "hello"
    );

    assert_eq!(
        MatchWithFieldHandlers::<FieldToString>::compute(&context, code, FooBarBaz::Baz(true)),
        "true"
    );
}

#[test]
fn test_dispatch_values_ref() {
    let context = App;
    let code = PhantomData::<()>;

    assert_eq!(
        MatchWithValueHandlersRef::<ValueToStringRef>::compute(&context, code, &FooBarBaz::Foo(1)),
        "1"
    );

    assert_eq!(
        MatchWithValueHandlersRef::<ValueToStringRef>::compute_ref(
            &context,
            code,
            &FooBarBaz::Foo(1)
        ),
        "1"
    );

    assert_eq!(
        MatchWithValueHandlersRef::<ValueToStringRef>::compute(
            &context,
            code,
            &FooBarBaz::Bar("hello".to_owned())
        ),
        "hello"
    );

    assert_eq!(
        MatchWithValueHandlersRef::<ValueToStringRef>::compute(
            &context,
            code,
            &FooBarBaz::Baz(true)
        ),
        "true"
    );
}

#[cgp_new_provider]
impl<Context, Code, Value> Computer<Context, Code, &Value> for ValueToString
where
    Value: Display,
{
    type Output = String;

    fn compute(_context: &Context, _code: PhantomData<Code>, input: &Value) -> Self::Output {
        input.to_string()
    }
}

#[test]
fn test_dispatch_fields_ref() {
    let context = App;
    let code = PhantomData::<()>;

    assert_eq!(
        MatchWithValueHandlersRef::<ValueToString>::compute(&context, code, &FooBarBaz::Foo(1)),
        "1"
    );

    assert_eq!(
        MatchWithValueHandlersRef::<ValueToString>::compute(
            &context,
            code,
            &FooBarBaz::Bar("hello".to_owned())
        ),
        "hello"
    );

    assert_eq!(
        MatchWithValueHandlersRef::<ValueToString>::compute(&context, code, &FooBarBaz::Baz(true)),
        "true"
    );
}

#[test]
fn test_async_dispatch_fields() {
    let context = App;
    let code = PhantomData::<()>;

    assert_eq!(
        block_on(MatchWithFieldHandlers::<FieldToString>::compute_async(
            &context,
            code,
            FooBarBaz::Foo(1)
        )),
        "1"
    );

    assert_eq!(
        block_on(MatchWithFieldHandlers::<FieldToString>::compute_async(
            &context,
            code,
            FooBarBaz::Bar("hello".to_owned())
        )),
        "hello"
    );

    assert_eq!(
        block_on(MatchWithFieldHandlers::<FieldToString>::compute_async(
            &context,
            code,
            FooBarBaz::Baz(true)
        )),
        "true"
    );
}

#[cgp_computer]
pub fn show_foo_bar(input: FooBar) -> String {
    format!("FooBar::{input:?}")
}

#[cgp_computer]
pub fn show_baz(input: bool) -> String {
    format!("Baz({input:?})")
}

type Computers = Product![
    ExtractFieldAndHandle<Symbol!("Baz"), HandleFieldValue<ShowBaz>>,
    DowncastAndHandle<FooBar, ShowFooBar>,
];

type Handlers = Product![
    PromoteAsync<ExtractFieldAndHandle<Symbol!("Baz"), HandleFieldValue<ShowBaz>>>,
    PromoteAsync<DowncastAndHandle<FooBar, ShowFooBar>>,
];

#[test]
fn test_dispatch_computers() {
    let context = App;
    let code = PhantomData::<()>;

    assert_eq!(
        MatchWithHandlers::<Computers>::compute(&context, code, FooBarBaz::Foo(1)),
        "FooBar::Foo(1)"
    );

    assert_eq!(
        MatchWithHandlers::<Computers>::compute(&context, code, FooBarBaz::Bar("hello".to_owned())),
        "FooBar::Bar(\"hello\")"
    );

    assert_eq!(
        MatchWithHandlers::<Computers>::compute(&context, code, FooBarBaz::Baz(true)),
        "Baz(true)"
    );
}

#[test]
fn test_dispatch_handlers() {
    let context = App;
    let code = PhantomData::<()>;

    assert_eq!(
        block_on(MatchWithHandlers::<Handlers>::compute_async(
            &context,
            code,
            FooBarBaz::Foo(1)
        )),
        "FooBar::Foo(1)"
    );

    assert_eq!(
        block_on(MatchWithHandlers::<Handlers>::compute_async(
            &context,
            code,
            FooBarBaz::Bar("hello".to_owned())
        )),
        "FooBar::Bar(\"hello\")"
    );

    assert_eq!(
        block_on(MatchWithHandlers::<Handlers>::compute_async(
            &context,
            code,
            FooBarBaz::Baz(true)
        )),
        "Baz(true)"
    );
}