Created
December 25, 2024 20:17
-
-
Save soareschen/fcde9d72f01509b4cee9c8d4bedbedf2 to your computer and use it in GitHub Desktop.
Scala Cake in CGP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is an example of how the Scala cake pattern can be used in CGP, | |
// following the example at https://www.baeldung.com/scala/cake-pattern | |
pub mod traits { | |
use std::collections::BTreeMap; | |
use anyhow::Error; | |
use cgp::prelude::*; | |
// For simplicity of the example, we use `dyn` trait to represent test cases | |
pub trait TestCase { | |
fn execute_test(&self, test_environment: &BTreeMap<String, String>) -> Result<(), Error>; | |
} | |
// This follows the TestEnvironmentComponent Scala trait | |
#[cgp_component { | |
name: TestEnvironmentComponent, | |
provider: TestEnvironmentGetter, | |
context: Context, | |
}] | |
pub trait HasTestEnvironment { | |
fn test_environment(&self) -> &BTreeMap<String, String>; | |
} | |
// This follows the TestExecutorComponent Scala trait | |
#[cgp_component { | |
name: TestExecutorComponent, | |
provider: TestExecutor, | |
context: Context, | |
}] | |
pub trait CanExecuteTests { | |
fn execute_tests(&self, tests: &[Box<dyn TestCase>]) -> Result<(), Error>; | |
} | |
// This follows the LoggingComponent Scala trait | |
#[cgp_component { | |
name: LoggerComponent, | |
provider: Logger, | |
context: Context, | |
}] | |
pub trait CanLog { | |
fn log(&self, level: &str, message: &str); | |
} | |
} | |
pub mod impls { | |
use core::marker::PhantomData; | |
use anyhow::Error; | |
use super::traits::*; | |
// We show an example test executor that runs all tests in serial. | |
// The name implies that we can also define a different test executor that runs all tests in parallel. | |
pub struct ExecuteAllTestsInSerial; | |
impl<Context> TestExecutor<Context> for ExecuteAllTestsInSerial | |
where | |
// We use dependency injection to require `Context` to implement `HasTestEnvironment` | |
Context: HasTestEnvironment, | |
{ | |
fn execute_tests(context: &Context, tests: &[Box<dyn TestCase>]) -> Result<(), Error> { | |
let test_environment = context.test_environment(); | |
for test in tests { | |
test.execute_test(test_environment)?; | |
} | |
Ok(()) | |
} | |
} | |
// We have a middleware test executor that would log the test environment using the logger, | |
// and then use `InExecutor` to execute the tests | |
pub struct LogAndExecuteTests<InExecutor>(pub PhantomData<InExecutor>); | |
impl<Context, InHandler> TestExecutor<Context> for LogAndExecuteTests<InHandler> | |
where | |
// We use dependency injection to require `Context` to implement both `CanLog` and `HasTestEnvironment` | |
Context: CanLog + HasTestEnvironment, | |
InHandler: TestExecutor<Context>, | |
{ | |
fn execute_tests(context: &Context, tests: &[Box<dyn TestCase>]) -> Result<(), Error> { | |
context.log( | |
"INFO", | |
&format!( | |
"Executing tests with environments: {:?}", | |
context.test_environment() | |
), | |
); | |
InHandler::execute_tests(context, tests) | |
} | |
} | |
// A simple logger implementation that logs using `prinln!`. | |
// We could later replace this with more sophisticated loggers, such as `TracingLogger`. | |
pub struct PrintLoggger; | |
impl<Context> Logger<Context> for PrintLoggger { | |
fn log(_context: &Context, level: &str, message: &str) { | |
println!("[{level}] {message}"); | |
} | |
} | |
} | |
pub mod contexts { | |
use std::collections::BTreeMap; | |
use cgp::prelude::*; | |
use super::impls::*; | |
use super::traits::*; | |
pub struct MyTestContext { | |
pub test_environment: BTreeMap<String, String>, | |
} | |
pub struct MyTestContextComponents; | |
impl HasComponents for MyTestContext { | |
type Components = MyTestContextComponents; | |
} | |
// We can easily customize how we want to execute the test and display logs, | |
// by choosing the appropriate providers. | |
delegate_components! { | |
MyTestContextComponents { | |
TestExecutorComponent: LogAndExecuteTests<ExecuteAllTestsInSerial>, | |
LoggerComponent: PrintLoggger, | |
} | |
} | |
// Define a custom `TestEnvironmentGetter` implementation for `MyTestContext`. | |
// It is also possible to have more general getter implementation, but we'd | |
// skip that inside this example. | |
impl TestEnvironmentGetter<MyTestContext> for MyTestContextComponents { | |
fn test_environment(context: &MyTestContext) -> &BTreeMap<String, String> { | |
&context.test_environment | |
} | |
} | |
} | |
#[cfg(test)] | |
pub mod tests { | |
use std::collections::BTreeMap; | |
use anyhow::Error; | |
use super::contexts::MyTestContext; | |
use super::traits::*; | |
pub struct DummyTest; | |
impl TestCase for DummyTest { | |
fn execute_test( | |
&self, | |
_test_environment: &std::collections::BTreeMap<String, String>, | |
) -> Result<(), anyhow::Error> { | |
Ok(()) | |
} | |
} | |
#[test] | |
fn run_dummy_test() -> Result<(), Error> { | |
let test_context = MyTestContext { | |
test_environment: BTreeMap::from([("foo".into(), "bar".into())]), | |
}; | |
test_context.execute_tests(&[Box::new(DummyTest)])?; | |
Ok(()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment