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/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 f8e3c8f..60fc140 100644 --- a/content/generic-accessor-providers.md +++ b/content/generic-accessor-providers.md @@ -99,6 +99,90 @@ 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. + +## Auto Accessor Traits + +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; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} + +impl HasApiBaseUrl for Context +where + Context: HasField, +{ + fn api_base_url(&self) -> &String { + self.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 + +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; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +# cgp_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_type!( AuthToken ); +# +#[cgp_auto_getter] +pub trait HasApiClientFields: HasAuthTokenType { + fn api_base_url(&self) -> &String; + + fn auth_token(&self) -> &Self::AuthToken; +} +``` + +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. + ## Using `HasField` in Accessor Providers With `HasField`, we can implement context-generic providers like `ApiUrlGetter`. Here's an example: @@ -140,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, @@ -169,11 +247,15 @@ where 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 +## The `UseFields` Pattern -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. +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: -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 +struct UseFields; +``` + +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; @@ -182,27 +264,200 @@ 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; +# } +# +# cgp_type!( AuthToken ); +# +# #[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) + } } -impl HasApiBaseUrl for Context +impl AuthTokenGetter for UseFields where - Context: HasField, + Context: HasAuthTokenType + HasField, { - fn api_base_url(&self) -> &String { - self.get_field(PhantomData) + fn auth_token(context: &Context) -> &Context::AuthToken { + 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. +## The `#[cgp_getter]` Macro -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 `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. -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. +With `#[cgp_getter]`, you can define accessor traits and seamlessly use `UseFields` directly in the component wiring, eliminating the need for manual implementations: -Overall, this approach may be an appealing option for developers who want a simpler experience with CGP without fully utilizing its advanced features. +```rust +# extern crate cgp; +# extern crate cgp_error_anyhow; +# extern crate reqwest; +# extern crate serde; +# +# 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; + +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]` 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 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. + +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; +# +# 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 @@ -262,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, @@ -293,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 { @@ -352,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 { @@ -408,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, @@ -450,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, @@ -473,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..b5c5b48 100644 --- a/content/use-field-pattern.md +++ b/content/use-field-pattern.md @@ -1,10 +1,10 @@ # 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. -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, # }] @@ -63,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 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`. + +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; @@ -88,21 +84,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, @@ -111,28 +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 { -# name: AuthTokenTypeComponent, -# provider: ProvideAuthTokenType, -# }] -# pub trait HasAuthTokenType { -# type AuthToken; -# } -# -# #[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)] @@ -178,42 +154,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, -# { -# 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, @@ -232,9 +172,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,