Replies: 5 comments 5 replies
-
Okay, I guess I need to study this one: https://github.com/contextgeneric/hypershell/blob/main/crates/hypershell-components/src/providers/string_arg.rs |
Beta Was this translation helpful? Give feedback.
-
Hi @rsfunc, the way you implement a trait like GetEntityTasks depends on what kind of implementations you have, and how you want to use it in your application. You could have multiple provider implementations with CGP, but if you only need one implementation for each concrete application context, then there is no need to use the extensible visitor pattern as highlighted in the blog post. For example, you could implement something like: #[cgp_new_provider]
impl<Context> GetEntityTasks<Context> for GetEntityTasksFromApi
where
Context: HasApiClient,
{ ... } And then wire it up in your application: #[cgp_context]
pub struct App {
pub api_client: ApiClient,
}
delegate_components! {
AppComponents {
GetEntityTasksComponent: GetEntityTasksFromApi,
}
} Alternatively, if you do need to support multiple ways of getting the task at runtime through an enum of providers, there is now a #[cgp_dispatch]
#[async_trait]
trait SupportsGetEntityTasks {
async fn get_entity_tasks(&self) -> Vec<EntityTask>;
}
impl SupportsGetEntityTasks for ApiClient { ... }
impl SupportsGetEntityTasks for InMemoryStore { ... }
#[derive(CgpVariant)]
pub enum EntityClient {
ApiClient(ApiClient),
InMemoryStore(InMemoryStore),
} In the above example, the trait |
Beta Was this translation helpful? Give feedback.
-
I tried to use
in the toml of my crate ... but unfortunately the additional macros do not popup. Anyway, if it would, so I would have something like
So, somewhere I have an App
How to access the enum 'and call' get_employer_tasks |
Beta Was this translation helpful? Give feedback.
-
Thanks for sharing more about your example code! I now have a better idea of what you are trying to do. It looks like we don't really need extensible visitor in this case, and the same thing can be achieved with a different CGP pattern that I have yet to document. At a high level, the implementation looks like follows: use cgp::prelude::*;
pub struct Employer;
pub struct AppTask;
// A generic employer task querier that can perform queries based on a given `TaskSpec`.
#[cgp_component {
provider: EmployerTaskQuerier,
derive_delegate: UseDelegate<TaskSpec>, // Implement the `UseDelegate` dispatcher based on `TaskSpec`
}]
#[async_trait]
pub trait CanQueryEmployerTasks<TaskSpec: Async>: Async {
async fn query_employer_tasks(
&self,
spec: &TaskSpec,
employer: &Employer,
) -> Result<Vec<AppTask>, anyhow::Error>;
}
// A task spec for checking incomplete billing info with a missing contact.
pub struct CheckForIncompleteBillingInfoMissingContact;
// A task spec for checking missing contribution payment info.
pub struct CheckMissingContributionPaymentInfo;
// A combined task spec that allows multiple kinds of employer checks.
pub enum EmployerChecks {
CheckForIncompleteBillingInfoMissingContact(CheckForIncompleteBillingInfoMissingContact),
CheckMissingContributionPaymentInfo(CheckMissingContributionPaymentInfo),
}
// A default provider that implements `EmployerTaskQuerier`.
// We can also define separate providers to implement `EmployerTaskQuerier` for each task spec,
// but we will share the same provider in this example for simplicity.
pub struct DefaultEmployerTaskQuerier;
#[cgp_provider]
impl<App> EmployerTaskQuerier<App, CheckForIncompleteBillingInfoMissingContact>
for DefaultEmployerTaskQuerier
where
App: Async,
{
async fn query_employer_tasks(
app: &App,
spec: &CheckForIncompleteBillingInfoMissingContact,
employer: &Employer,
) -> Result<Vec<AppTask>, anyhow::Error> {
todo!()
}
}
#[cgp_provider]
impl<App> EmployerTaskQuerier<App, CheckMissingContributionPaymentInfo>
for DefaultEmployerTaskQuerier
where
App: Async,
{
async fn query_employer_tasks(
app: &App,
spec: &CheckMissingContributionPaymentInfo,
employer: &Employer,
) -> Result<Vec<AppTask>, anyhow::Error> {
todo!()
}
}
// Implements `EmployerTaskQuerier` for `EmployerChecks` by delegating it to other
// queriers
#[cgp_provider]
impl<App> EmployerTaskQuerier<App, EmployerChecks> for DefaultEmployerTaskQuerier
where
// Use the context to look up for the implementation for sub tasks,
// so that alternative providers may be wired with it.
App: CanQueryEmployerTasks<CheckForIncompleteBillingInfoMissingContact>
+ CanQueryEmployerTasks<CheckMissingContributionPaymentInfo>,
{
async fn query_employer_tasks(
app: &App,
spec: &EmployerChecks,
employer: &Employer,
) -> Result<Vec<AppTask>, anyhow::Error> {
// We can later use the extensible visitor pattern to handle the dispatching.
// But it is better to start with an explicit match for clarity.
match spec {
EmployerChecks::CheckMissingContributionPaymentInfo(spec) => {
app.query_employer_tasks(spec, employer).await
}
EmployerChecks::CheckForIncompleteBillingInfoMissingContact(spec) => {
app.query_employer_tasks(spec, employer).await
}
}
}
}
#[cgp_context]
pub struct App;
delegate_components! {
AppComponents {
// Use the default provider to implement `EmployerTaskQuerierComponent`
EmployerTaskQuerierComponent: DefaultEmployerTaskQuerier,
}
} The main idea is that we would define a task querier component with a generic #[cgp_component(EmployerTaskQuerier)]
#[async_trait]
pub trait CanQueryEmployerTasks<TaskSpec: Async>: Async {
async fn query_employer_tasks(
&self,
spec: &TaskSpec,
employer: &Employer,
) -> Result<Vec<AppTask>, anyhow::Error>;
} The generic parameter is there, because we want the context to be able to handle different sub tasks, i.e. In the example, we define a default provider Although it is possible to use extensible visitor here to automate the dispatching, it is better to start with explicit In the example, I also use delegate_components! {
AppComponents {
EmployerTaskQuerierComponent:
UseDelegate<new EmployerTaskQuerierComponents {
EmployerChecks:
DefaultEmployerTaskQuerier, // todo: replace with a different provider
CheckMissingContributionPaymentInfo:
DefaultEmployerTaskQuerier, // todo: replace with a different provider
CheckForIncompleteBillingInfoMissingContact:
DefaultEmployerTaskQuerier, // todo: replace with a different provider
// todo: add new tasks here
}>,
}
} I hope that the explanation is clear enough. This is one of the more advanced CGP patterns that I still haven't written any documentation or blog posts. Feel free to ask me to clarify anything, if it is still confusing to you. |
Beta Was this translation helpful? Give feedback.
-
Btw, to use the [patch.crates-io]
cgp = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-core = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-extra = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-async = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-async-macro = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-component = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-macro = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-macro-lib = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-extra-macro = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-extra-macro-lib = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-type = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-field = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-error = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-error-extra = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-run = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-runtime = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-sync = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-inner = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-handler = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-monad = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-dispatch = { git = "https://github.com/contextgeneric/cgp.git" }
cgp-error-anyhow = { git = "https://github.com/contextgeneric/cgp.git" } The project uses lazy binding even for crate dependencies. It is currently a bit tedious due to breaking changes. But the idea is that once the project is fully stabilized, you will be able to override a specific sub-crate in CGP without having to fork the entire |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Context:
Let
then I would like to define multiple tasks provider for this, and somehow have super component that could call all implementations and return all tasks...
I have the feeling it should be similar as in https://contextgeneric.dev/blog/extensible-datatypes-part-2/
Could you give some pointers if it is already doable with the current code?
Beta Was this translation helpful? Give feedback.
All reactions