pavex/blueprint/config.rs
1use super::reflection::AnnotationCoordinates;
2use crate::blueprint::{CloningPolicy, conversions::cloning2cloning};
3use pavex_bp_schema::{Blueprint as BlueprintSchema, Component, ConfigType};
4
5/// The input type for [`Blueprint::config`].
6///
7/// Check out [`Blueprint::config`] for more information on how to manage configuration types
8/// in Pavex.
9///
10/// # Stability guarantees
11///
12/// Use the [`config`](macro@crate::config) attribute macro to create instances of `Config`.\
13/// `Config`'s fields are an implementation detail of Pavex's macros and should not be relied upon:
14/// newer versions of Pavex may add, remove or modify its fields.
15///
16/// [`Blueprint::config`]: crate::Blueprint::config
17pub struct Config {
18 #[doc(hidden)]
19 pub coordinates: AnnotationCoordinates,
20}
21
22/// A configuration type registered via [`Blueprint::config`].
23///
24/// # Example
25///
26/// You can use the methods exposed by [`RegisteredConfig`] to tune the behaviour
27/// of the registered configuration type.
28/// For example, instruct Pavex to use the `Default` implementation if the user configuration
29/// doesn't specify a value for `pool`:
30///
31/// ```rust
32/// use pavex::{config, Blueprint};
33///
34/// #[config(key = "pool")]
35/// #[derive(serde::Deserialize, Debug, Clone)]
36/// pub struct PoolConfig {
37/// pub max_n_connections: u32,
38/// pub min_n_connections: u32,
39/// }
40///
41/// impl Default for PoolConfig {
42/// fn default() -> Self {
43/// Self {
44/// max_n_connections: 10,
45/// min_n_connections: 2,
46/// }
47/// }
48/// }
49///
50/// let mut bp = Blueprint::new();
51/// // This is equivalent to `#[config(key = "pool", default_if_missing)]`
52/// bp.config(POOL_CONFIG).default_if_missing();
53/// ```
54///
55/// # Example: override the annotation
56///
57/// You can also override the behaviour specified via the [`config`](macro@crate::config) attribute.
58///
59/// ```rust
60/// use pavex::{config, Blueprint};
61///
62/// #[config(key = "pool", default_if_missing)]
63/// #[derive(serde::Deserialize, Debug, Clone)]
64/// pub struct PoolConfig {
65/// pub max_n_connections: u32,
66/// pub min_n_connections: u32,
67/// }
68///
69/// # impl Default for PoolConfig {
70/// # fn default() -> Self {
71/// # Self {
72/// # max_n_connections: 10,
73/// # min_n_connections: 2,
74/// # }
75/// # }
76/// # }
77/// #
78/// let mut bp = Blueprint::new();
79/// // Using `required`, we are overriding the `default_if_missing` flag
80/// // specified via the `config` attribute.
81/// // This is equivalent to `#[config(key = "pool")]`, thus restoring
82/// // the default behaviour.
83/// bp.config(POOL_CONFIG).required();
84/// ```
85///
86/// [`Blueprint::config`]: crate::Blueprint::config
87pub struct RegisteredConfig<'a> {
88 #[allow(unused)]
89 pub(crate) blueprint: &'a mut BlueprintSchema,
90 /// The index of the registered config type in the blueprint's `components` vector.
91 #[allow(unused)]
92 pub(crate) component_id: usize,
93}
94
95impl RegisteredConfig<'_> {
96 /// Set the cloning strategy for this configuration type.
97 ///
98 /// By default, Pavex will clone a configuration type if it's necessary to generate code
99 /// that satisfies Rust's borrow checker.
100 /// You can change the default behaviour by invoking [`never_clone`](Self::never_clone).
101 ///
102 /// Regardless of the chosen strategy, configuration types *must* implement `Clone`,
103 /// since the code-generated `ApplicationConfig` type will need to derive it.
104 pub fn cloning(mut self, strategy: CloningPolicy) -> Self {
105 self.config().cloning_policy = Some(cloning2cloning(strategy));
106 self
107 }
108
109 /// Set the cloning strategy to [`CloningPolicy::CloneIfNecessary`].
110 /// Check out [`cloning`](Self::cloning) method for more details.
111 pub fn clone_if_necessary(self) -> Self {
112 self.cloning(CloningPolicy::CloneIfNecessary)
113 }
114
115 /// Set the cloning strategy to [`CloningPolicy::NeverClone`].
116 /// Check out [`cloning`](Self::cloning) method for more details.
117 pub fn never_clone(self) -> Self {
118 self.cloning(CloningPolicy::NeverClone)
119 }
120
121 /// Use the default configuration values returned by [`Default::default`]
122 /// if the user has not specified its own configuration for this type.
123 ///
124 /// # Requirements
125 ///
126 /// The configuration type *must* implement the [`Default`] trait
127 /// to support this option.
128 ///
129 /// # Implementation notes
130 ///
131 /// `default_if_missing` adds a `#[serde(default)]` attribute on the corresponding
132 /// configuration key in the code-generated `ApplicationConfig` struct.
133 pub fn default_if_missing(mut self) -> Self {
134 self.config().default_if_missing = Some(true);
135 self
136 }
137
138 /// Force the user to specify a value for this configuration entry.
139 ///
140 /// It's the opposite of [`default_if_missing`](Self::default_if_missing).
141 pub fn required(mut self) -> Self {
142 self.config().default_if_missing = Some(false);
143 self
144 }
145
146 /// Include this configuration entry in the generated `ApplicationConfig` struct
147 /// even if the type is never used by the application.
148 pub fn include_if_unused(mut self) -> Self {
149 self.config().include_if_unused = Some(true);
150 self
151 }
152
153 fn config(&mut self) -> &mut ConfigType {
154 let component = &mut self.blueprint.components[self.component_id];
155 let Component::ConfigType(s) = component else {
156 unreachable!("The component should be a config type")
157 };
158 s
159 }
160}