From 9e2b12f63d7b9c0ad4b3018d89746078fa47a3a9 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jan 2025 13:38:21 +0000 Subject: [PATCH 1/8] Add section for cgp_auto_getter --- Cargo.lock | 37 ++++++++-------- Cargo.toml | 36 +++++++-------- content/generic-accessor-providers.md | 64 +++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c656fa3..0f4de70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,7 +101,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cgp" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-async", "cgp-core", @@ -111,7 +111,7 @@ dependencies = [ [[package]] name = "cgp-async" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-async-macro", "cgp-sync", @@ -120,7 +120,7 @@ dependencies = [ [[package]] name = "cgp-async-macro" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "proc-macro2", "quote", @@ -130,20 +130,21 @@ dependencies = [ [[package]] name = "cgp-component" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" [[package]] name = "cgp-component-macro" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-component-macro-lib", + "syn", ] [[package]] name = "cgp-component-macro-lib" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "itertools", "prettyplease", @@ -155,7 +156,7 @@ dependencies = [ [[package]] name = "cgp-core" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-async", "cgp-component", @@ -169,7 +170,7 @@ dependencies = [ [[package]] name = "cgp-error" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-async", "cgp-component", @@ -180,7 +181,7 @@ dependencies = [ [[package]] name = "cgp-error-anyhow" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "anyhow", "cgp-core", @@ -189,7 +190,7 @@ dependencies = [ [[package]] name = "cgp-error-extra" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-error", ] @@ -197,7 +198,7 @@ dependencies = [ [[package]] name = "cgp-extra" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-error-extra", "cgp-inner", @@ -208,7 +209,7 @@ dependencies = [ [[package]] name = "cgp-field" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-component", "cgp-type", @@ -217,7 +218,7 @@ dependencies = [ [[package]] name = "cgp-field-macro" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-field-macro-lib", "proc-macro2", @@ -226,7 +227,7 @@ dependencies = [ [[package]] name = "cgp-field-macro-lib" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "prettyplease", "proc-macro2", @@ -237,7 +238,7 @@ dependencies = [ [[package]] name = "cgp-inner" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-component", "cgp-component-macro", @@ -261,7 +262,7 @@ dependencies = [ [[package]] name = "cgp-run" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-async", "cgp-component", @@ -272,7 +273,7 @@ dependencies = [ [[package]] name = "cgp-runtime" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-core", ] @@ -289,7 +290,7 @@ dependencies = [ [[package]] name = "cgp-type" version = "0.2.0" -source = "git+https://github.com/contextgeneric/cgp.git#8fe487c4c17f6857d4a30c451516e8a3876e838f" +source = "git+https://github.com/contextgeneric/cgp.git?branch=getter-component#bda46a93e4e9ee550a3f191191cfabefe0f463fd" dependencies = [ "cgp-component", "cgp-component-macro", diff --git a/Cargo.toml b/Cargo.toml index 8358d79..3949bd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,21 +16,21 @@ reqwest = { version = "0.12.12", features = [ "blocking", "json" ] } [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-component-macro = { git = "https://github.com/contextgeneric/cgp.git" } -cgp-component-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-field-macro = { git = "https://github.com/contextgeneric/cgp.git" } -cgp-field-macro-lib = { 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-error-anyhow = { 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-inner = { git = "https://github.com/contextgeneric/cgp.git" } +cgp = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-core = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-extra = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-async = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-async-macro = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-component = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-component-macro = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-component-macro-lib = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-type = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-field = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-field-macro = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-field-macro-lib = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-error = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-error-extra = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-error-anyhow = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-run = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-runtime = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } +cgp-inner = { git = "https://github.com/contextgeneric/cgp.git", branch = "getter-component" } diff --git a/content/generic-accessor-providers.md b/content/generic-accessor-providers.md index f8e3c8f..3c30e7c 100644 --- a/content/generic-accessor-providers.md +++ b/content/generic-accessor-providers.md @@ -99,6 +99,8 @@ While this type may seem complex, it has a compact representation from the persp It’s important to note that the current representation of symbols is a temporary workaround. Once Rust supports using strings in const generics, we can simplify the desugaring process and adjust our implementation accordingly. +If the explanation here still feels unclear, think of symbols as strings being used as _types_ rather than values. In later sections, we’ll explore how `cgp` provides additional abstractions that abstract away the use of `symbol!` and `HasField`. These abstractions simplify the process, so you won’t need to worry about these details in simple cases. + ## Using `HasField` in Accessor Providers With `HasField`, we can implement context-generic providers like `ApiUrlGetter`. Here's an example: @@ -204,6 +206,68 @@ The main drawback of this approach is that the context cannot easily override th Overall, this approach may be an appealing option for developers who want a simpler experience with CGP without fully utilizing its advanced features. +## The `#[cgp_auto_getter]` Macro + +To simplify the definition of auto accessor traits, the `cgp` crate provides the `#[cgp_auto_getter]` macro, to derive the blanket implementation of an accessor trait. So the same example can be +rewritten as follows: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +#[cgp_auto_getter] +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} + +#[cgp_auto_getter] +pub trait HasAuthToken: HasAuthTokenType { + fn auth_token(&self) -> &Self::AuthToken; +} +``` + +Notice that since `#[cgp_auto_getter]` generates a blanket implementation that uses `HasField` +directly, there is no corresponding provider trait being derived here. + +We can also use `#[cgp_auto_getter]` inside accessor traits that contain multiple getter methods. +So we can for example combine the two accessor traits as follows: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +#[cgp_auto_getter] +pub trait HasApiClientFields: HasAuthTokenType { + fn api_base_url(&self) -> &String; + + fn auth_token(&self) -> &Self::AuthToken; +} +``` + +Using `#[cgp_auto_getter]`, the accessor traits will be automatically be implemented by contexts that use `#[derive(HasField)]`, and has fields that match the name and return type of the accessor methods. In this way, the use of `HasField` and `symbol!` are completely hidden away, and we get to define well-typed and idiomatic interfaces to access the fields. + ## Static Accessors One advantage of defining minimal accessor traits is that it allows the implementation of custom accessor providers that do not necessarily read field values from the context. For instance, we can create _static accessor_ providers that always return a global constant value. From 730eea00d625403a2414dc4a2f6b57e54408bc27 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jan 2025 13:45:54 +0000 Subject: [PATCH 2/8] AI-revise section --- content/generic-accessor-providers.md | 44 ++++++++++++++++++++++----- src/lib.rs | 1 + 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/content/generic-accessor-providers.md b/content/generic-accessor-providers.md index 3c30e7c..e99b329 100644 --- a/content/generic-accessor-providers.md +++ b/content/generic-accessor-providers.md @@ -208,8 +208,7 @@ Overall, this approach may be an appealing option for developers who want a simp ## The `#[cgp_auto_getter]` Macro -To simplify the definition of auto accessor traits, the `cgp` crate provides the `#[cgp_auto_getter]` macro, to derive the blanket implementation of an accessor trait. So the same example can be -rewritten as follows: +To streamline the creation of auto accessor traits, the `cgp` crate provides the `#[cgp_auto_getter]` macro, which derives blanket implementations for accessor traits. For instance, the earlier example can be rewritten as follows: ```rust # extern crate cgp; @@ -237,11 +236,9 @@ pub trait HasAuthToken: HasAuthTokenType { } ``` -Notice that since `#[cgp_auto_getter]` generates a blanket implementation that uses `HasField` -directly, there is no corresponding provider trait being derived here. +Since `#[cgp_auto_getter]` generates a blanket implementation leveraging `HasField` directly, there is no corresponding provider trait being derived in this case. -We can also use `#[cgp_auto_getter]` inside accessor traits that contain multiple getter methods. -So we can for example combine the two accessor traits as follows: +The `#[cgp_auto_getter]` attribute can also be applied to accessor traits that define multiple getter methods. For instance, we can combine two accessor traits into one, as shown below: ```rust # extern crate cgp; @@ -266,7 +263,40 @@ pub trait HasApiClientFields: HasAuthTokenType { } ``` -Using `#[cgp_auto_getter]`, the accessor traits will be automatically be implemented by contexts that use `#[derive(HasField)]`, and has fields that match the name and return type of the accessor methods. In this way, the use of `HasField` and `symbol!` are completely hidden away, and we get to define well-typed and idiomatic interfaces to access the fields. +By using `#[cgp_auto_getter]`, accessor traits are automatically implemented for contexts that use `#[derive(HasField)]` and include fields matching the names and return types of the accessor methods. This approach encapsulates the use of `HasField` and `symbol!`, providing well-typed and idiomatic interfaces for field access while abstracting the underlying mechanics. + +## The `#[cgp_getter]` Macro + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +#[cgp_getter { + provider: ApiBaseUrlGetter, +}] +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} + +#[cgp_getter { + provider: AutoTokenGetter, +}] +pub trait HasAuthToken: HasAuthTokenType { + fn auth_token(&self) -> &Self::AuthToken; +} +``` + ## Static Accessors diff --git a/src/lib.rs b/src/lib.rs index e69de29..8b13789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1 @@ + From fd5c8aba305734a01676cdb620f913906064b791 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jan 2025 15:44:57 +0000 Subject: [PATCH 3/8] Add sections for UseFields cgp_getter --- content/generic-accessor-providers.md | 308 ++++++++++++++++++++------ src/lib.rs | 118 ++++++++++ 2 files changed, 357 insertions(+), 69 deletions(-) diff --git a/content/generic-accessor-providers.md b/content/generic-accessor-providers.md index e99b329..88c8207 100644 --- a/content/generic-accessor-providers.md +++ b/content/generic-accessor-providers.md @@ -101,9 +101,11 @@ It’s important to note that the current representation of symbols is a tempora If the explanation here still feels unclear, think of symbols as strings being used as _types_ rather than values. In later sections, we’ll explore how `cgp` provides additional abstractions that abstract away the use of `symbol!` and `HasField`. These abstractions simplify the process, so you won’t need to worry about these details in simple cases. -## Using `HasField` in Accessor Providers +## Auto Accessor Traits -With `HasField`, we can implement context-generic providers like `ApiUrlGetter`. Here's an example: +The process of defining and wiring many CGP components can be overwhelming for developers who are new to CGP. In the early stages of a project, there is typically not much need for customizing how fields are accessed. As a result, some developers may find the full use of field accessors introduced in this chapter unnecessarily complex. + +To simplify the use of accessor traits, one approach is to define them not as CGP components, but as regular Rust traits with blanket implementations that leverage `HasField`. For example, we can redefine the `HasApiBaseUrl` trait as follows: ```rust # extern crate cgp; @@ -112,28 +114,31 @@ With `HasField`, we can implement context-generic providers like `ApiUrlGetter`. # # use cgp::prelude::*; # -# #[cgp_component { -# provider: ApiBaseUrlGetter, -# }] -# pub trait HasApiBaseUrl { -# fn api_base_url(&self) -> &String; -# } -# -pub struct GetApiUrl; +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} -impl ApiBaseUrlGetter for GetApiUrl +impl HasApiBaseUrl for Context where - Context: HasField, + Context: HasField, { - fn api_base_url(context: &Context) -> &String { - context.get_field(PhantomData) + fn api_base_url(&self) -> &String { + self.get_field(PhantomData) } } ``` -In this implementation, `GetApiUrl` is defined for any `Context` type that implements `HasField`. This means that as long as the context uses `#[derive(HasField)]`, and has a field named `api_url` of type `String`, the `GetApiUrl` provider can be used with it. +With this approach, the `HasApiBaseUrl` trait will be automatically implemented for any context that derives `HasField` and contains the relevant field. There is no longer need for explicit wiring of the `ApiBaseUrlGetterComponent` within the context components. -Similarly, we can implement a context-generic provider for `AuthTokenGetter` as follows: +This approach allows providers, such as `ReadMessageFromApi`, to still use accessor traits like `HasApiBaseUrl` to simplify field access. Meanwhile, context implementers can simply use `#[derive(HasField)]` without having to worry about manual wiring. + +The main drawback of this approach is that the context cannot easily override the implementation of `HasApiBaseUrl`, unless it opts not to implement `HasField`. However, it would be straightforward to refactor the trait in the future to convert it into a full CGP component. + +Overall, this approach may be an appealing option for developers who want a simpler experience with CGP without fully utilizing its advanced features. + +## The `#[cgp_auto_getter]` Macro + +To streamline the creation of auto accessor traits, the `cgp` crate provides the `#[cgp_auto_getter]` macro, which derives blanket implementations for accessor traits. For instance, the earlier example can be rewritten as follows: ```rust # extern crate cgp; @@ -150,32 +155,49 @@ Similarly, we can implement a context-generic provider for `AuthTokenGetter` as # type AuthToken; # } # +#[cgp_auto_getter] +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} + +#[cgp_auto_getter] +pub trait HasAuthToken: HasAuthTokenType { + fn auth_token(&self) -> &Self::AuthToken; +} +``` + +Since `#[cgp_auto_getter]` generates a blanket implementation leveraging `HasField` directly, there is no corresponding provider trait being derived in this case. + +The `#[cgp_auto_getter]` attribute can also be applied to accessor traits that define multiple getter methods. For instance, we can combine two accessor traits into one, as shown below: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# # #[cgp_component { -# provider: AuthTokenGetter, +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, # }] -# pub trait HasAuthToken: HasAuthTokenType { -# fn auth_token(&self) -> &Self::AuthToken; +# pub trait HasAuthTokenType { +# type AuthToken; # } # -pub struct GetAuthToken; +#[cgp_auto_getter] +pub trait HasApiClientFields: HasAuthTokenType { + fn api_base_url(&self) -> &String; -impl AuthTokenGetter for GetAuthToken -where - Context: HasAuthTokenType + HasField, -{ - fn auth_token(context: &Context) -> &Context::AuthToken { - context.get_field(PhantomData) - } + fn auth_token(&self) -> &Self::AuthToken; } ``` -The `GetAuthToken` provider is slightly more complex since the `auth_token` method returns an abstract `Context::AuthToken` type. To handle this, we require the `Context` to implement `HasAuthTokenType` and for the `Value` associated type to match `Context::AuthToken`. This ensures that `GetAuthToken` can be used with any context that has an `auth_token` field of the same type as the `AuthToken` defined in `HasAuthTokenType`. - -## Auto Accessor Traits +By using `#[cgp_auto_getter]`, accessor traits are automatically implemented for contexts that use `#[derive(HasField)]` and include fields matching the names and return types of the accessor methods. This approach encapsulates the use of `HasField` and `symbol!`, providing well-typed and idiomatic interfaces for field access while abstracting the underlying mechanics. -The process of defining and wiring many CGP components can be overwhelming for developers who are new to CGP. In the early stages of a project, there is typically not much need for customizing how fields are accessed. As a result, some developers may find the full use of field accessors introduced in this chapter unnecessarily complex. +## Using `HasField` in Accessor Providers -To simplify the use of accessor traits, one approach is to define them not as CGP components, but as regular Rust traits with blanket implementations that leverage `HasField`. For example, we can redefine the `HasApiBaseUrl` trait as follows: +With `HasField`, we can implement context-generic providers like `ApiUrlGetter`. Here's an example: ```rust # extern crate cgp; @@ -184,31 +206,28 @@ To simplify the use of accessor traits, one approach is to define them not as CG # # use cgp::prelude::*; # -pub trait HasApiBaseUrl { - fn api_base_url(&self) -> &String; -} +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +pub struct GetApiUrl; -impl HasApiBaseUrl for Context +impl ApiBaseUrlGetter for GetApiUrl where - Context: HasField, + Context: HasField, { - fn api_base_url(&self) -> &String { - self.get_field(PhantomData) + fn api_base_url(context: &Context) -> &String { + context.get_field(PhantomData) } } ``` -With this approach, the `HasApiBaseUrl` trait will be automatically implemented for any context that derives `HasField` and contains the relevant field. There is no longer need for explicit wiring of the `ApiBaseUrlGetterComponent` within the context components. - -This approach allows providers, such as `ReadMessageFromApi`, to still use accessor traits like `HasApiBaseUrl` to simplify field access. Meanwhile, context implementers can simply use `#[derive(HasField)]` without having to worry about manual wiring. - -The main drawback of this approach is that the context cannot easily override the implementation of `HasApiBaseUrl`, unless it opts not to implement `HasField`. However, it would be straightforward to refactor the trait in the future to convert it into a full CGP component. - -Overall, this approach may be an appealing option for developers who want a simpler experience with CGP without fully utilizing its advanced features. - -## The `#[cgp_auto_getter]` Macro +In this implementation, `GetApiUrl` is defined for any `Context` type that implements `HasField`. This means that as long as the context uses `#[derive(HasField)]`, and has a field named `api_url` of type `String`, the `GetApiUrl` provider can be used with it. -To streamline the creation of auto accessor traits, the `cgp` crate provides the `#[cgp_auto_getter]` macro, which derives blanket implementations for accessor traits. For instance, the earlier example can be rewritten as follows: +Similarly, we can implement a context-generic provider for `AuthTokenGetter` as follows: ```rust # extern crate cgp; @@ -225,20 +244,36 @@ To streamline the creation of auto accessor traits, the `cgp` crate provides the # type AuthToken; # } # -#[cgp_auto_getter] -pub trait HasApiBaseUrl { - fn api_base_url(&self) -> &String; -} +# #[cgp_component { +# provider: AuthTokenGetter, +# }] +# pub trait HasAuthToken: HasAuthTokenType { +# fn auth_token(&self) -> &Self::AuthToken; +# } +# +pub struct GetAuthToken; -#[cgp_auto_getter] -pub trait HasAuthToken: HasAuthTokenType { - fn auth_token(&self) -> &Self::AuthToken; +impl AuthTokenGetter for GetAuthToken +where + Context: HasAuthTokenType + HasField, +{ + fn auth_token(context: &Context) -> &Context::AuthToken { + context.get_field(PhantomData) + } } ``` -Since `#[cgp_auto_getter]` generates a blanket implementation leveraging `HasField` directly, there is no corresponding provider trait being derived in this case. +The `GetAuthToken` provider is slightly more complex since the `auth_token` method returns an abstract `Context::AuthToken` type. To handle this, we require the `Context` to implement `HasAuthTokenType` and for the `Value` associated type to match `Context::AuthToken`. This ensures that `GetAuthToken` can be used with any context that has an `auth_token` field of the same type as the `AuthToken` defined in `HasAuthTokenType`. -The `#[cgp_auto_getter]` attribute can also be applied to accessor traits that define multiple getter methods. For instance, we can combine two accessor traits into one, as shown below: +## The `UseFields` Pattern + +The providers `GetAuthToken` and `GetApiUrl` share one thing in common: they all implement accessor traits for any context type by making use of `HasField`, with the field name being the same as the accessor method name. To make the usage pattern clear, the `cgp` offers the `UseFields` marker struct that can be used for implementing such providers: + +```rust +struct UseFields; +``` + +Using `UseFields`, we can skip defining custom provider structs and implement directly on `UseFields` as follows: ```rust # extern crate cgp; @@ -248,6 +283,13 @@ The `#[cgp_auto_getter]` attribute can also be applied to accessor traits that d # use cgp::prelude::*; # # #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +# #[cgp_component { # name: AuthTokenTypeComponent, # provider: ProvideAuthTokenType, # }] @@ -255,31 +297,114 @@ The `#[cgp_auto_getter]` attribute can also be applied to accessor traits that d # type AuthToken; # } # -#[cgp_auto_getter] -pub trait HasApiClientFields: HasAuthTokenType { - fn api_base_url(&self) -> &String; +# #[cgp_component { +# provider: AuthTokenGetter, +# }] +# pub trait HasAuthToken: HasAuthTokenType { +# fn auth_token(&self) -> &Self::AuthToken; +# } +# +impl ApiBaseUrlGetter for UseFields +where + Context: HasField, +{ + fn api_base_url(context: &Context) -> &String { + context.get_field(PhantomData) + } +} - fn auth_token(&self) -> &Self::AuthToken; +impl AuthTokenGetter for UseFields +where + Context: HasAuthTokenType + HasField, +{ + fn auth_token(context: &Context) -> &Context::AuthToken { + context.get_field(PhantomData) + } } ``` -By using `#[cgp_auto_getter]`, accessor traits are automatically implemented for contexts that use `#[derive(HasField)]` and include fields matching the names and return types of the accessor methods. This approach encapsulates the use of `HasField` and `symbol!`, providing well-typed and idiomatic interfaces for field access while abstracting the underlying mechanics. ## The `#[cgp_getter]` Macro +The `cgp` crate provides the `#[cgp_getter]` macro, which auto derive implementations such as `UseFields`. +Other than that, it provides the same interface as `#[cgp_component]`, and derives the same component +traits and blanket implementations. + +Using `#[cgp_getter]`, we can just define the accessor traits and then make use of `UseFields` directly +inside the component wiring without additional implementation: + ```rust # extern crate cgp; +# extern crate cgp_error_anyhow; +# extern crate reqwest; +# extern crate serde; # -# use core::marker::PhantomData; +# use core::fmt::Display; # +# use cgp::core::component::UseDelegate; +# use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; +# use cgp::core::field::UseField; +# use cgp::extra::error::RaiseFrom; # use cgp::prelude::*; +# use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; +# use reqwest::blocking::Client; +# use reqwest::StatusCode; +# use serde::Deserialize; +# +# cgp_type!(Message); +# cgp_type!(MessageId); +# cgp_type!(AuthToken); # # #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, +# provider: MessageQuerier, # }] -# pub trait HasAuthTokenType { -# type AuthToken; +# pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { +# fn query_message(&self, message_id: &Self::MessageId) -> Result; +# } +# +# pub struct ReadMessageFromApi; +# +# #[derive(Debug)] +# pub struct ErrStatusCode { +# pub status_code: StatusCode, +# } +# +# #[derive(Deserialize)] +# pub struct ApiMessageResponse { +# pub message: String, +# } +# +# impl MessageQuerier for ReadMessageFromApi +# where +# Context: HasMessageIdType +# + HasMessageType +# + HasApiBaseUrl +# + HasAuthToken +# + CanRaiseError +# + CanRaiseError, +# Context::AuthToken: Display, +# { +# fn query_message(context: &Context, message_id: &u64) -> Result { +# let client = Client::new(); +# +# let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); +# +# let response = client +# .get(url) +# .bearer_auth(context.auth_token()) +# .send() +# .map_err(Context::raise_error)?; +# +# let status_code = response.status(); +# +# if !status_code.is_success() { +# return Err(Context::raise_error(ErrStatusCode { status_code })); +# } +# +# let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; +# +# Ok(message_response.message) +# } # } # #[cgp_getter { @@ -290,13 +415,58 @@ pub trait HasApiBaseUrl { } #[cgp_getter { - provider: AutoTokenGetter, + provider: AuthTokenGetter, }] pub trait HasAuthToken: HasAuthTokenType { fn auth_token(&self) -> &Self::AuthToken; } + +#[derive(HasField)] +pub struct ApiClient { + pub api_base_url: String, + pub auth_token: String, +} + +pub struct ApiClientComponents; + +impl HasComponents for ApiClient { + type Components = ApiClientComponents; +} + +delegate_components! { + ApiClientComponents { + ErrorTypeComponent: UseAnyhowError, + ErrorRaiserComponent: UseDelegate, + MessageTypeComponent: UseType, + MessageIdTypeComponent: UseType, + AuthTokenTypeComponent: UseType, + [ + ApiBaseUrlGetterComponent, + AuthTokenGetterComponent, + ]: UseFields, + MessageQuerierComponent: ReadMessageFromApi, + } +} +# +# pub struct RaiseApiErrors; +# +# delegate_components! { +# RaiseApiErrors { +# reqwest::Error: RaiseFrom, +# ErrStatusCode: DebugAnyhowError, +# } +# } +# +# pub trait CanUseApiClient: CanQueryMessage {} +# +# impl CanUseApiClient for ApiClient {} ``` +Compared to `#[cgp_auto_getter]`, `#[cgp_getter]` requires the same wiring step as other CGP components. +But to get the same result as using `#[cgp_auto_getter]`, the only additional step that is required +is to delegate the getter component to `UseFields` inside `delegate_components`. + +The main flexibility that we get from using `#[cgp_getter]` is that it is now possible to define custom accessor providers that have different ways to read the fields from the context, as we will see in the next section. ## Static Accessors diff --git a/src/lib.rs b/src/lib.rs index 8b13789..d22be78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,119 @@ +use core::fmt::Display; +use cgp::core::component::UseDelegate; +use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; +use cgp::core::field::UseField; +use cgp::extra::error::RaiseFrom; +use cgp::prelude::*; +use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; +use reqwest::blocking::Client; +use reqwest::StatusCode; +use serde::Deserialize; + +cgp_type!(Message); +cgp_type!(MessageId); +cgp_type!(AuthToken); + +#[cgp_component { + provider: MessageQuerier, +}] +pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { + fn query_message(&self, message_id: &Self::MessageId) -> Result; +} + +pub struct ReadMessageFromApi; + +#[derive(Debug)] +pub struct ErrStatusCode { + pub status_code: StatusCode, +} + +#[derive(Deserialize)] +pub struct ApiMessageResponse { + pub message: String, +} + +impl MessageQuerier for ReadMessageFromApi +where + Context: HasMessageIdType + + HasMessageType + + HasApiBaseUrl + + HasAuthToken + + CanRaiseError + + CanRaiseError, + Context::AuthToken: Display, +{ + fn query_message(context: &Context, message_id: &u64) -> Result { + let client = Client::new(); + + let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); + + let response = client + .get(url) + .bearer_auth(context.auth_token()) + .send() + .map_err(Context::raise_error)?; + + let status_code = response.status(); + + if !status_code.is_success() { + return Err(Context::raise_error(ErrStatusCode { status_code })); + } + + let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; + + Ok(message_response.message) + } +} + +#[cgp_getter { + provider: ApiBaseUrlGetter, +}] +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} + +#[cgp_getter { + provider: AuthTokenGetter, +}] +pub trait HasAuthToken: HasAuthTokenType { + fn auth_token(&self) -> &Self::AuthToken; +} + +#[derive(HasField)] +pub struct ApiClient { + pub api_base_url: String, + pub auth_token: String, +} + +pub struct ApiClientComponents; + +pub struct RaiseApiErrors; + +impl HasComponents for ApiClient { + type Components = ApiClientComponents; +} + +delegate_components! { + ApiClientComponents { + ErrorTypeComponent: UseAnyhowError, + ErrorRaiserComponent: UseDelegate, + MessageIdTypeComponent: UseType, + MessageTypeComponent: UseType, + AuthTokenTypeComponent: UseType, + ApiBaseUrlGetterComponent: UseFields, + AuthTokenGetterComponent: UseFields, + MessageQuerierComponent: ReadMessageFromApi, + } +} + +delegate_components! { + RaiseApiErrors { + reqwest::Error: RaiseFrom, + ErrStatusCode: DebugAnyhowError, + } +} + +pub trait CanUseApiClient: CanQueryMessage {} + +impl CanUseApiClient for ApiClient {} From 529cc83f71f17956d996d97cbb30146cb3f804b7 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jan 2025 15:50:21 +0000 Subject: [PATCH 4/8] Add example use of multiple accessor methods --- content/generic-accessor-providers.md | 29 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/content/generic-accessor-providers.md b/content/generic-accessor-providers.md index 88c8207..18efc45 100644 --- a/content/generic-accessor-providers.md +++ b/content/generic-accessor-providers.md @@ -177,13 +177,7 @@ The `#[cgp_auto_getter]` attribute can also be applied to accessor traits that d # # use cgp::prelude::*; # -# #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } +# cgp_type!( AuthToken ); # #[cgp_auto_getter] pub trait HasApiClientFields: HasAuthTokenType { @@ -468,6 +462,27 @@ is to delegate the getter component to `UseFields` inside `delegate_components`. The main flexibility that we get from using `#[cgp_getter]` is that it is now possible to define custom accessor providers that have different ways to read the fields from the context, as we will see in the next section. +Similar to `#[cgp_auto_getter]`, we can also use `#[cgp_getter]` with accessor traits that contain multiple accessor methods. So in case if we need to use custom accessor providers at a later time, it is straightforward to upgrade a trait like `HasApiClientFields` to use `#[cgp_getter]`: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +# cgp_type!( AuthToken ); +# +#[cgp_getter { + provider: ApiClientFieldsGetter, +}] +pub trait HasApiClientFields: HasAuthTokenType { + fn api_base_url(&self) -> &String; + + fn auth_token(&self) -> &Self::AuthToken; +} +``` + ## Static Accessors One advantage of defining minimal accessor traits is that it allows the implementation of custom accessor providers that do not necessarily read field values from the context. For instance, we can create _static accessor_ providers that always return a global constant value. From 74a1f93b91cf50ebd4f6f4841011413b21f2be89 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jan 2025 16:06:40 +0000 Subject: [PATCH 5/8] AI-revise sections --- content/generic-accessor-providers.md | 28 ++++++++------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/content/generic-accessor-providers.md b/content/generic-accessor-providers.md index 18efc45..e37da56 100644 --- a/content/generic-accessor-providers.md +++ b/content/generic-accessor-providers.md @@ -261,13 +261,13 @@ The `GetAuthToken` provider is slightly more complex since the `auth_token` meth ## The `UseFields` Pattern -The providers `GetAuthToken` and `GetApiUrl` share one thing in common: they all implement accessor traits for any context type by making use of `HasField`, with the field name being the same as the accessor method name. To make the usage pattern clear, the `cgp` offers the `UseFields` marker struct that can be used for implementing such providers: +The providers `GetAuthToken` and `GetApiUrl` share a common characteristic: they implement accessor traits for any context type by utilizing `HasField`, with the field name corresponding to the accessor method name. To streamline this pattern, `cgp` provides the `UseFields` marker struct, which simplifies the implementation of such providers: ```rust struct UseFields; ``` -Using `UseFields`, we can skip defining custom provider structs and implement directly on `UseFields` as follows: +With `UseFields`, we can bypass the need to define custom provider structs and implement the logic directly on `UseFields`, as shown below: ```rust # extern crate cgp; @@ -283,13 +283,7 @@ Using `UseFields`, we can skip defining custom provider structs and implement di # fn api_base_url(&self) -> &String; # } # -# #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } +# cgp_type!( AuthToken ); # # #[cgp_component { # provider: AuthTokenGetter, @@ -317,15 +311,11 @@ where } ``` - ## The `#[cgp_getter]` Macro -The `cgp` crate provides the `#[cgp_getter]` macro, which auto derive implementations such as `UseFields`. -Other than that, it provides the same interface as `#[cgp_component]`, and derives the same component -traits and blanket implementations. +The `cgp` crate offers the `#[cgp_getter]` macro, which automatically derives implementations like `UseFields`. As an extension of `#[cgp_component]`, it provides the same interface and generates the same CGP component traits and blanket implementations. -Using `#[cgp_getter]`, we can just define the accessor traits and then make use of `UseFields` directly -inside the component wiring without additional implementation: +With `#[cgp_getter]`, you can define accessor traits and seamlessly use `UseFields` directly in the component wiring, eliminating the need for manual implementations: ```rust # extern crate cgp; @@ -456,13 +446,11 @@ delegate_components! { # impl CanUseApiClient for ApiClient {} ``` -Compared to `#[cgp_auto_getter]`, `#[cgp_getter]` requires the same wiring step as other CGP components. -But to get the same result as using `#[cgp_auto_getter]`, the only additional step that is required -is to delegate the getter component to `UseFields` inside `delegate_components`. +Compared to `#[cgp_auto_getter]`, `#[cgp_getter]` follows the same wiring process as other CGP components. To achieve the same outcome as `#[cgp_auto_getter]`, the only additional step required is delegating the getter component to UseFields within `delegate_components!`. -The main flexibility that we get from using `#[cgp_getter]` is that it is now possible to define custom accessor providers that have different ways to read the fields from the context, as we will see in the next section. +The primary advantage of using `#[cgp_getter]` is the ability to define custom accessor providers that can retrieve fields from the context in various ways, as we will explore in the next section. -Similar to `#[cgp_auto_getter]`, we can also use `#[cgp_getter]` with accessor traits that contain multiple accessor methods. So in case if we need to use custom accessor providers at a later time, it is straightforward to upgrade a trait like `HasApiClientFields` to use `#[cgp_getter]`: +Like `#[cgp_auto_getter]`, `#[cgp_getter]` can also be used with accessor traits containing multiple methods. This makes it easy to upgrade a trait, such as `HasApiClientFields`, to use `#[cgp_getter]` if custom accessor providers are needed in the future: ```rust # extern crate cgp; From 74022c89803156086223333137913dcf0527ab0a Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jan 2025 16:18:57 +0000 Subject: [PATCH 6/8] Simplify code with new cgp APIs --- content/field-accessors.md | 197 +++----------------------- content/generic-accessor-providers.md | 92 ++---------- content/use-field-pattern.md | 61 ++------ 3 files changed, 37 insertions(+), 313 deletions(-) diff --git a/content/field-accessors.md b/content/field-accessors.md index 9e9550b..217e890 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -11,21 +11,8 @@ Suppose our application needs to make API calls to an external service to read m # use cgp::prelude::*; -#[cgp_component { - name: MessageIdTypeComponent, - provider: ProvideMessageIdType, -}] -pub trait HasMessageIdType { - type MessageId; -} - -#[cgp_component { - name: MessageTypeComponent, - provider: ProvideMessageType, -}] -pub trait HasMessageType { - type Message; -} +cgp_type!( Message ); +cgp_type!( MessageId ); #[cgp_component { provider: MessageQuerier, @@ -48,22 +35,9 @@ With the interfaces defined, we now implement a simple API client provider that use reqwest::blocking::Client; use reqwest::StatusCode; use serde::Deserialize; - -# #[cgp_component { -# name: MessageIdTypeComponent, -# provider: ProvideMessageIdType, -# }] -# pub trait HasMessageIdType { -# type MessageId; -# } # -# #[cgp_component { -# name: MessageTypeComponent, -# provider: ProvideMessageType, -# }] -# pub trait HasMessageType { -# type Message; -# } +# cgp_type!( Message ); +# cgp_type!( MessageId ); # # #[cgp_component { # provider: MessageQuerier, @@ -153,21 +127,8 @@ Next, we can include the `HasApiBaseUrl` trait within `ReadMessageFromApi`, allo # use reqwest::StatusCode; # use serde::Deserialize; # -# #[cgp_component { -# name: MessageIdTypeComponent, -# provider: ProvideMessageIdType, -# }] -# pub trait HasMessageIdType { -# type MessageId; -# } -# -# #[cgp_component { -# name: MessageTypeComponent, -# provider: ProvideMessageType, -# }] -# pub trait HasMessageType { -# type Message; -# } +# cgp_type!( Message ); +# cgp_type!( MessageId ); # # #[cgp_component { # provider: MessageQuerier, @@ -234,13 +195,7 @@ Just as we did with `HasApiBaseUrl`, we can define a `HasAuthToken` trait to ret # # use cgp::prelude::*; # -#[cgp_component { - name: AuthTokenTypeComponent, - provider: ProvideAuthTokenType, -}] -pub trait HasAuthTokenType { - type AuthToken; -} +cgp_type!( AuthToken ); #[cgp_component { provider: AuthTokenGetter, @@ -266,21 +221,9 @@ Next, we define a getter trait, `HasAuthToken`, which provides access to an abst # use reqwest::StatusCode; # use serde::Deserialize; # -# #[cgp_component { -# name: MessageIdTypeComponent, -# provider: ProvideMessageIdType, -# }] -# pub trait HasMessageIdType { -# type MessageId; -# } -# -# #[cgp_component { -# name: MessageTypeComponent, -# provider: ProvideMessageType, -# }] -# pub trait HasMessageType { -# type Message; -# } +# cgp_type!( Message ); +# cgp_type!( MessageId ); +# cgp_type!( AuthToken ); # # #[cgp_component { # provider: MessageQuerier, @@ -297,14 +240,6 @@ Next, we define a getter trait, `HasAuthToken`, which provides access to an abst # } # # #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } -# -# #[cgp_component { # provider: AuthTokenGetter, # }] # pub trait HasAuthToken: HasAuthTokenType { @@ -368,13 +303,7 @@ When creating providers like `ReadMessageFromApi`, which often need to use both # # use cgp::prelude::*; # -# #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } +# cgp_type!( AuthToken ); # #[cgp_component { provider: ApiClientFieldsGetter, @@ -428,59 +357,7 @@ For the purposes of this book, we will continue to use minimal traits, as this e Now that we have implemented the provider, we would look at how to implement a concrete context that uses `ReadMessageFromApi` and implement the accessors. - -First of all, we would implement the type traits by implementing type providers -that fit the constraints of `ReadMessageFromApi`: - -```rust -# extern crate cgp; -# -# use cgp::prelude::*; -# -# #[cgp_component { -# name: MessageIdTypeComponent, -# provider: ProvideMessageIdType, -# }] -# pub trait HasMessageIdType { -# type MessageId; -# } -# -# #[cgp_component { -# name: MessageTypeComponent, -# provider: ProvideMessageType, -# }] -# pub trait HasMessageType { -# type Message; -# } -# -# #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } -# -pub struct UseU64MessageId; - -impl ProvideMessageIdType for UseU64MessageId { - type MessageId = u64; -} - -pub struct UseStringMessage; - -impl ProvideMessageType for UseStringMessage { - type Message = String; -} - -pub struct UseStringAuthToken; - -impl ProvideAuthTokenType for UseStringAuthToken { - type AuthToken = String; -} -``` - -We can then implement an `ApiClient` context that makes use of all providers +We can implement an `ApiClient` context that makes use of all providers as follows: ```rust @@ -500,21 +377,9 @@ as follows: # use reqwest::StatusCode; # use serde::Deserialize; # -# #[cgp_component { -# name: MessageIdTypeComponent, -# provider: ProvideMessageIdType, -# }] -# pub trait HasMessageIdType { -# type MessageId; -# } -# -# #[cgp_component { -# name: MessageTypeComponent, -# provider: ProvideMessageType, -# }] -# pub trait HasMessageType { -# type Message; -# } +# cgp_type!( Message ); +# cgp_type!( MessageId ); +# cgp_type!( AuthToken ); # # #[cgp_component { # provider: MessageQuerier, @@ -531,14 +396,6 @@ as follows: # } # # #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } -# -# #[cgp_component { # provider: AuthTokenGetter, # }] # pub trait HasAuthToken: HasAuthTokenType { @@ -590,24 +447,6 @@ as follows: # } # } # -# pub struct UseStringAuthToken; -# -# impl ProvideAuthTokenType for UseStringAuthToken { -# type AuthToken = String; -# } -# -# pub struct UseU64MessageId; -# -# impl ProvideMessageIdType for UseU64MessageId { -# type MessageId = u64; -# } -# -# pub struct UseStringMessage; -# -# impl ProvideMessageType for UseStringMessage { -# type Message = String; -# } -# pub struct ApiClient { pub api_base_url: String, pub auth_token: String, @@ -625,9 +464,9 @@ delegate_components! { ApiClientComponents { ErrorTypeComponent: UseAnyhowError, ErrorRaiserComponent: UseDelegate, - MessageIdTypeComponent: UseU64MessageId, - MessageTypeComponent: UseStringMessage, - AuthTokenTypeComponent: UseStringAuthToken, + MessageIdTypeComponent: UseType, + MessageTypeComponent: UseType, + AuthTokenTypeComponent: UseType, MessageQuerierComponent: ReadMessageFromApi, } } diff --git a/content/generic-accessor-providers.md b/content/generic-accessor-providers.md index e37da56..60fc140 100644 --- a/content/generic-accessor-providers.md +++ b/content/generic-accessor-providers.md @@ -147,13 +147,7 @@ To streamline the creation of auto accessor traits, the `cgp` crate provides the # # use cgp::prelude::*; # -# #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } +# cgp_type!( AuthToken ); # #[cgp_auto_getter] pub trait HasApiBaseUrl { @@ -230,13 +224,7 @@ Similarly, we can implement a context-generic provider for `AuthTokenGetter` as # # use cgp::prelude::*; # -# #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } +# cgp_type!( AuthToken ); # # #[cgp_component { # provider: AuthTokenGetter, @@ -529,21 +517,9 @@ With `UseProductionApiUrl`, we can now define a production `ApiClient` context, # use reqwest::StatusCode; # use serde::Deserialize; # -# #[cgp_component { -# name: MessageIdTypeComponent, -# provider: ProvideMessageIdType, -# }] -# pub trait HasMessageIdType { -# type MessageId; -# } -# -# #[cgp_component { -# name: MessageTypeComponent, -# provider: ProvideMessageType, -# }] -# pub trait HasMessageType { -# type Message; -# } +# cgp_type!( Message ); +# cgp_type!( MessageId ); +# cgp_type!( AuthToken ); # # #[cgp_component { # provider: MessageQuerier, @@ -560,14 +536,6 @@ With `UseProductionApiUrl`, we can now define a production `ApiClient` context, # } # # #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } -# -# #[cgp_component { # provider: AuthTokenGetter, # }] # pub trait HasAuthToken: HasAuthTokenType { @@ -619,24 +587,6 @@ With `UseProductionApiUrl`, we can now define a production `ApiClient` context, # } # } # -# pub struct UseStringAuthToken; -# -# impl ProvideAuthTokenType for UseStringAuthToken { -# type AuthToken = String; -# } -# -# pub struct UseU64MessageId; -# -# impl ProvideMessageIdType for UseU64MessageId { -# type MessageId = u64; -# } -# -# pub struct UseStringMessage; -# -# impl ProvideMessageType for UseStringMessage { -# type Message = String; -# } -# # pub struct UseProductionApiUrl; # # impl ApiBaseUrlGetter for UseProductionApiUrl { @@ -675,9 +625,9 @@ delegate_components! { ApiClientComponents { ErrorTypeComponent: UseAnyhowError, ErrorRaiserComponent: UseDelegate, - MessageIdTypeComponent: UseU64MessageId, - MessageTypeComponent: UseStringMessage, - AuthTokenTypeComponent: UseStringAuthToken, + MessageIdTypeComponent: UseType, + MessageTypeComponent: UseType, + AuthTokenTypeComponent: UseType, ApiBaseUrlGetterComponent: UseProductionApiUrl, AuthTokenGetterComponent: GetAuthToken, MessageQuerierComponent: ReadMessageFromApi, @@ -717,21 +667,9 @@ Since the `HasField` trait can be automatically derived by contexts, some develo # use reqwest::StatusCode; # use serde::Deserialize; # -# #[cgp_component { -# name: MessageIdTypeComponent, -# provider: ProvideMessageIdType, -# }] -# pub trait HasMessageIdType { -# type MessageId; -# } -# -# #[cgp_component { -# name: MessageTypeComponent, -# provider: ProvideMessageType, -# }] -# pub trait HasMessageType { -# type Message; -# } +# cgp_type!( Message ); +# cgp_type!( MessageId ); +# cgp_type!( AuthToken ); # # #[cgp_component { # provider: MessageQuerier, @@ -740,14 +678,6 @@ Since the `HasField` trait can be automatically derived by contexts, some develo # fn query_message(&self, message_id: &Self::MessageId) -> Result; # } # -# #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } -# # pub struct ReadMessageFromApi; # # #[derive(Debug)] diff --git a/content/use-field-pattern.md b/content/use-field-pattern.md index 70c2f51..fb96f4d 100644 --- a/content/use-field-pattern.md +++ b/content/use-field-pattern.md @@ -4,7 +4,7 @@ In the previous section, we were able to implement context-generic accessor prov There are various reasons why a context might want to use different names for the field values. For instance, two independent accessor providers might choose the same field name for different types, or a context might have multiple similar fields with slightly different names. In these cases, it would be beneficial to allow the context to customize the field names instead of having the providers pick fixed field names. -To address this, the `cgp` crate provides the `UseField` type, which we can leverage to implement flexible accessor providers: +To address this, the `cgp` crate provides the `UseField` marker type (note the lack of `s`, making it different from `UseFields`), which we can leverage to implement flexible accessor providers: ```rust # use core::marker::PhantomData; @@ -29,14 +29,7 @@ Similar to the [`UseDelegate` pattern](./delegated-error-raiser.md), the `UseFie # fn api_base_url(&self) -> &String; # } # -# #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } -# +# cgp_type!( AuthToken ); # #[cgp_component { # provider: AuthTokenGetter, # }] @@ -88,21 +81,9 @@ By using `UseField`, we can simplify the implementation of `ApiClient` and wire # use reqwest::StatusCode; # use serde::Deserialize; # -# #[cgp_component { -# name: MessageIdTypeComponent, -# provider: ProvideMessageIdType, -# }] -# pub trait HasMessageIdType { -# type MessageId; -# } -# -# #[cgp_component { -# name: MessageTypeComponent, -# provider: ProvideMessageType, -# }] -# pub trait HasMessageType { -# type Message; -# } +# cgp_type!( Message ); +# cgp_type!( MessageId ); +# cgp_type!( AuthToken ); # # #[cgp_component { # provider: MessageQuerier, @@ -119,14 +100,6 @@ By using `UseField`, we can simplify the implementation of `ApiClient` and wire # } # # #[cgp_component { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } -# -# #[cgp_component { # provider: AuthTokenGetter, # }] # pub trait HasAuthToken: HasAuthTokenType { @@ -178,24 +151,6 @@ By using `UseField`, we can simplify the implementation of `ApiClient` and wire # } # } # -# pub struct UseStringAuthToken; -# -# impl ProvideAuthTokenType for UseStringAuthToken { -# type AuthToken = String; -# } -# -# pub struct UseU64MessageId; -# -# impl ProvideMessageIdType for UseU64MessageId { -# type MessageId = u64; -# } -# -# pub struct UseStringMessage; -# -# impl ProvideMessageType for UseStringMessage { -# type Message = String; -# } -# # impl ApiBaseUrlGetter for UseField # where # Context: HasField, @@ -232,9 +187,9 @@ delegate_components! { ApiClientComponents { ErrorTypeComponent: UseAnyhowError, ErrorRaiserComponent: UseDelegate, - MessageIdTypeComponent: UseU64MessageId, - MessageTypeComponent: UseStringMessage, - AuthTokenTypeComponent: UseStringAuthToken, + MessageIdTypeComponent: UseType, + MessageTypeComponent: UseType, + AuthTokenTypeComponent: UseType, ApiBaseUrlGetterComponent: UseField, AuthTokenGetterComponent: UseField, MessageQuerierComponent: ReadMessageFromApi, From cc89347a2ac85f5a22d24f616e6542ea1c769dd7 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jan 2025 16:45:09 +0000 Subject: [PATCH 7/8] Add section for deriving UseField with cgp_getter --- content/use-field-pattern.md | 57 +++++++++++++----------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/content/use-field-pattern.md b/content/use-field-pattern.md index fb96f4d..07dbd8a 100644 --- a/content/use-field-pattern.md +++ b/content/use-field-pattern.md @@ -1,6 +1,6 @@ # The `UseField` Pattern -In the previous section, we were able to implement context-generic accessor providers like `GetApiUrl` and `GetAuthToken` without directly referencing the concrete context. However, the field names, such as `api_url` and `auth_token`, were hardcoded into the provider implementation. This means that a concrete context cannot choose different _field names_ for these specific fields unless it manually re-implements the accessors. +In the previous chapter, we were able to implement context-generic accessor providers like `GetApiUrl` and `UseFields` without directly referencing the concrete context. However, the field names, such as `api_url` and `auth_token`, were hardcoded into the provider implementation. This means that a concrete context cannot choose different _field names_ for these specific fields unless it manually re-implements the accessors. There are various reasons why a context might want to use different names for the field values. For instance, two independent accessor providers might choose the same field name for different types, or a context might have multiple similar fields with slightly different names. In these cases, it would be beneficial to allow the context to customize the field names instead of having the providers pick fixed field names. @@ -56,11 +56,14 @@ where } ``` -In contrast to the explicit providers `GetApiUrl` and `GetAuthToken`, we now implement the `ApiBaseUrlGetter` and `AuthTokenGetter` traits directly on the `UseField` type provided by the `cgp` crate. The implementation is parameterized by an additional `Tag` type, which represents the field name we want to access. - +Compared to `UseFields`, the implementation of `UseField` is parameterized by an additional `Tag` type, which represents the field name we want to access. The structure of the implementation is almost the same as before, but instead of using `symbol!` to directly reference the field names, we rely on the `Tag` type to abstract the field names. -By using `UseField`, we can simplify the implementation of `ApiClient` and wire up the accessor components directly within `delegate_components!`: +## Deriving `UseField` from `#[cgp_getter]` + +The implementation of `UseField` on accessor traits can be automatically derived, when we define the trait with `#[cgp_getter]`. However, the implementation would only be derived if the accessor trait contains exactly one accessor method. This is because otherwise, there is no way to determine which accessor method should use the `Tag` type specified in `UseField`. + +Using both `#[cgp_getter]` and `UseField`, we can simplify the implementation of `ApiClient` and wire up the accessor components directly within `delegate_components!`: ```rust # extern crate cgp; @@ -92,20 +95,20 @@ By using `UseField`, we can simplify the implementation of `ApiClient` and wire # fn query_message(&self, message_id: &Self::MessageId) -> Result; # } # -# #[cgp_component { -# provider: ApiBaseUrlGetter, -# }] -# pub trait HasApiBaseUrl { -# fn api_base_url(&self) -> &String; -# } -# -# #[cgp_component { -# provider: AuthTokenGetter, -# }] -# pub trait HasAuthToken: HasAuthTokenType { -# fn auth_token(&self) -> &Self::AuthToken; -# } -# +#[cgp_getter { + provider: ApiBaseUrlGetter, +}] +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} + +#[cgp_getter { + provider: AuthTokenGetter, +}] +pub trait HasAuthToken: HasAuthTokenType { + fn auth_token(&self) -> &Self::AuthToken; +} + # pub struct ReadMessageFromApi; # # #[derive(Debug)] @@ -151,24 +154,6 @@ By using `UseField`, we can simplify the implementation of `ApiClient` and wire # } # } # -# impl ApiBaseUrlGetter for UseField -# where -# Context: HasField, -# { -# fn api_base_url(context: &Context) -> &String { -# context.get_field(PhantomData) -# } -# } -# -# impl AuthTokenGetter for UseField -# where -# Context: HasAuthTokenType + HasField, -# { -# fn auth_token(context: &Context) -> &Context::AuthToken { -# context.get_field(PhantomData) -# } -# } -# # #[derive(HasField)] # pub struct ApiClient { # pub api_base_url: String, From 44ddd84cfe592ebdd35f4a285c605af375536bc0 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 8 Jan 2025 16:47:03 +0000 Subject: [PATCH 8/8] AI-revise section --- content/use-field-pattern.md | 4 +- src/lib.rs | 119 ----------------------------------- 2 files changed, 2 insertions(+), 121 deletions(-) diff --git a/content/use-field-pattern.md b/content/use-field-pattern.md index 07dbd8a..b5c5b48 100644 --- a/content/use-field-pattern.md +++ b/content/use-field-pattern.md @@ -61,9 +61,9 @@ The structure of the implementation is almost the same as before, but instead of ## Deriving `UseField` from `#[cgp_getter]` -The implementation of `UseField` on accessor traits can be automatically derived, when we define the trait with `#[cgp_getter]`. However, the implementation would only be derived if the accessor trait contains exactly one accessor method. This is because otherwise, there is no way to determine which accessor method should use the `Tag` type specified in `UseField`. +The implementation of `UseField` on accessor traits can be automatically derived when the trait is defined with `#[cgp_getter]`. However, the derivation will only occur if the accessor trait contains exactly one accessor method. This is because, in cases with multiple methods, there is no clear way to determine which accessor method should utilize the `Tag` type specified in `UseField`. -Using both `#[cgp_getter]` and `UseField`, we can simplify the implementation of `ApiClient` and wire up the accessor components directly within `delegate_components!`: +By combining `#[cgp_getter]` with `UseField`, we can streamline the implementation of `ApiClient` and directly wire the accessor components within `delegate_components!`: ```rust # extern crate cgp; diff --git a/src/lib.rs b/src/lib.rs index d22be78..e69de29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,119 +0,0 @@ -use core::fmt::Display; - -use cgp::core::component::UseDelegate; -use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; -use cgp::core::field::UseField; -use cgp::extra::error::RaiseFrom; -use cgp::prelude::*; -use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; -use reqwest::blocking::Client; -use reqwest::StatusCode; -use serde::Deserialize; - -cgp_type!(Message); -cgp_type!(MessageId); -cgp_type!(AuthToken); - -#[cgp_component { - provider: MessageQuerier, -}] -pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { - fn query_message(&self, message_id: &Self::MessageId) -> Result; -} - -pub struct ReadMessageFromApi; - -#[derive(Debug)] -pub struct ErrStatusCode { - pub status_code: StatusCode, -} - -#[derive(Deserialize)] -pub struct ApiMessageResponse { - pub message: String, -} - -impl MessageQuerier for ReadMessageFromApi -where - Context: HasMessageIdType - + HasMessageType - + HasApiBaseUrl - + HasAuthToken - + CanRaiseError - + CanRaiseError, - Context::AuthToken: Display, -{ - fn query_message(context: &Context, message_id: &u64) -> Result { - let client = Client::new(); - - let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); - - let response = client - .get(url) - .bearer_auth(context.auth_token()) - .send() - .map_err(Context::raise_error)?; - - let status_code = response.status(); - - if !status_code.is_success() { - return Err(Context::raise_error(ErrStatusCode { status_code })); - } - - let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; - - Ok(message_response.message) - } -} - -#[cgp_getter { - provider: ApiBaseUrlGetter, -}] -pub trait HasApiBaseUrl { - fn api_base_url(&self) -> &String; -} - -#[cgp_getter { - provider: AuthTokenGetter, -}] -pub trait HasAuthToken: HasAuthTokenType { - fn auth_token(&self) -> &Self::AuthToken; -} - -#[derive(HasField)] -pub struct ApiClient { - pub api_base_url: String, - pub auth_token: String, -} - -pub struct ApiClientComponents; - -pub struct RaiseApiErrors; - -impl HasComponents for ApiClient { - type Components = ApiClientComponents; -} - -delegate_components! { - ApiClientComponents { - ErrorTypeComponent: UseAnyhowError, - ErrorRaiserComponent: UseDelegate, - MessageIdTypeComponent: UseType, - MessageTypeComponent: UseType, - AuthTokenTypeComponent: UseType, - ApiBaseUrlGetterComponent: UseFields, - AuthTokenGetterComponent: UseFields, - MessageQuerierComponent: ReadMessageFromApi, - } -} - -delegate_components! { - RaiseApiErrors { - reqwest::Error: RaiseFrom, - ErrStatusCode: DebugAnyhowError, - } -} - -pub trait CanUseApiClient: CanQueryMessage {} - -impl CanUseApiClient for ApiClient {}