Skip to content

Commit bd2bffa

Browse files
chore(config): leverage proc macros (#9111)
### Description This PR leverages [derive_setters](https://crates.io/crates/derive_setters) and [merge](https://docs.rs/merge/latest/merge/index.html) to reduce the amount of changes required when adding a field to the configuration options. The manual setup is error prone as there are 4 places that need to be updated and forgetting the final place will result in code compiling, but the field never getting layered properly. ### Testing Instructions Existing unit & integration tests, but also looking what the proc macros are generating (outputs shown are produced using `rust-analyzer`'s [Expand macro](https://rust-analyzer.github.io/manual.html#expand-macro-recursively) feature: Output of `#[derive(Setters)]` ``` impl TurborepoConfigBuilder { pub fn with_api_url(mut self, value: Option<String>) -> Self { self.override_config.api_url = value; self } ... ``` Which exactly matches what `create_builder` would produce: ``` pub fn with_api_url(mut self, value: Option<String>) -> Self { self.override_config.api_url = value; self } ``` `#derive(Merge)` produces ``` impl ::merge::Merge for ConfigurationOptions { fn merge(&mut self, other: Self) { ::merge::Merge::merge(&mut self.api_url, other.api_url); ... ``` and `Merge` is defined for `Option` as : ``` impl<T> Merge for Option<T> { fn merge(&mut self, mut other: Self) { if !self.is_some() { *self = other.take(); } } } ``` [source](https://docs.rs/merge/latest/src/merge/lib.rs.html#139-145) So `self.api_url` will only be set to `other.api_url` if there isn't a value present which is the behavior we want since we merge configs from highest to lowest precedence. --------- Co-authored-by: Nicholas Yang <[email protected]>
1 parent 2adeac9 commit bd2bffa

File tree

4 files changed

+50
-93
lines changed

4 files changed

+50
-93
lines changed

Cargo.lock

+36
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ console = "0.15.5"
9696
console-subscriber = "0.1.8"
9797
crossbeam-channel = "0.5.8"
9898
dashmap = "5.4.0"
99+
derive_setters = "0.1.6"
99100
dialoguer = "0.10.3"
100101
dunce = "1.0.3"
101102
either = "1.9.0"
@@ -108,6 +109,7 @@ indicatif = "0.17.3"
108109
indoc = "2.0.0"
109110
itertools = "0.10.5"
110111
lazy_static = "1.4.0"
112+
merge = "0.1.0"
111113
mime = "0.3.16"
112114
notify = "6.1.1"
113115
once_cell = "1.17.1"

crates/turborepo-lib/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const_format = "0.2.30"
5252
convert_case = "0.6.0"
5353
crossterm = "0.26"
5454
ctrlc = { version = "3.4.0", features = ["termination"] }
55+
derive_setters = { workspace = true }
5556
dialoguer = { workspace = true, features = ["fuzzy-select"] }
5657
dirs-next = "2.0.0"
5758
dunce = { workspace = true }
@@ -70,6 +71,7 @@ itertools = { workspace = true }
7071
jsonc-parser = { version = "0.21.0" }
7172
lazy_static = { workspace = true }
7273
libc = "0.2.140"
74+
merge = { workspace = true }
7375
miette = { workspace = true, features = ["fancy"] }
7476
nix = "0.26.2"
7577
notify = { workspace = true }

crates/turborepo-lib/src/config/mod.rs

+10-93
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ use std::{collections::HashMap, ffi::OsString, io};
66

77
use camino::{Utf8Path, Utf8PathBuf};
88
use convert_case::{Case, Casing};
9+
use derive_setters::Setters;
910
use env::{EnvVars, OverrideEnvVars};
1011
use file::{AuthFile, ConfigFile};
12+
use merge::Merge;
1113
use miette::{Diagnostic, NamedSource, SourceSpan};
1214
use serde::Deserialize;
1315
use struct_iterable::Iterable;
@@ -173,15 +175,6 @@ pub enum Error {
173175
},
174176
}
175177

176-
macro_rules! create_builder {
177-
($func_name:ident, $property_name:ident, $type:ty) => {
178-
pub fn $func_name(mut self, value: $type) -> Self {
179-
self.override_config.$property_name = value;
180-
self
181-
}
182-
};
183-
}
184-
185178
const DEFAULT_API_URL: &str = "https://vercel.com/api";
186179
const DEFAULT_LOGIN_URL: &str = "https://vercel.com";
187180
const DEFAULT_TIMEOUT: u64 = 30;
@@ -190,8 +183,13 @@ const DEFAULT_UPLOAD_TIMEOUT: u64 = 60;
190183
// We intentionally don't derive Serialize so that different parts
191184
// of the code that want to display the config can tune how they
192185
// want to display and what fields they want to include.
193-
#[derive(Deserialize, Default, Debug, PartialEq, Eq, Clone, Iterable)]
186+
#[derive(Deserialize, Default, Debug, PartialEq, Eq, Clone, Iterable, Merge, Setters)]
194187
#[serde(rename_all = "camelCase")]
188+
// Generate setters for the builder type that set these values on its override_config field
189+
#[setters(
190+
prefix = "with_",
191+
generate_delegates(ty = "TurborepoConfigBuilder", field = "override_config")
192+
)]
195193
pub struct ConfigurationOptions {
196194
#[serde(alias = "apiurl")]
197195
#[serde(alias = "ApiUrl")]
@@ -336,44 +334,6 @@ impl ConfigurationOptions {
336334
}
337335
}
338336

339-
macro_rules! create_set_if_empty {
340-
($func_name:ident, $property_name:ident, $type:ty) => {
341-
fn $func_name(&mut self, value: &mut Option<$type>) {
342-
if self.$property_name.is_none() {
343-
if let Some(value) = value.take() {
344-
self.$property_name = Some(value);
345-
}
346-
}
347-
}
348-
};
349-
}
350-
351-
// Private setters used only for construction
352-
impl ConfigurationOptions {
353-
create_set_if_empty!(set_api_url, api_url, String);
354-
create_set_if_empty!(set_login_url, login_url, String);
355-
create_set_if_empty!(set_team_slug, team_slug, String);
356-
create_set_if_empty!(set_team_id, team_id, String);
357-
create_set_if_empty!(set_token, token, String);
358-
create_set_if_empty!(set_signature, signature, bool);
359-
create_set_if_empty!(set_enabled, enabled, bool);
360-
create_set_if_empty!(set_preflight, preflight, bool);
361-
create_set_if_empty!(set_timeout, timeout, u64);
362-
create_set_if_empty!(set_ui, ui, UIMode);
363-
create_set_if_empty!(set_allow_no_package_manager, allow_no_package_manager, bool);
364-
create_set_if_empty!(set_daemon, daemon, bool);
365-
create_set_if_empty!(set_env_mode, env_mode, EnvMode);
366-
create_set_if_empty!(set_cache_dir, cache_dir, Utf8PathBuf);
367-
create_set_if_empty!(set_scm_base, scm_base, String);
368-
create_set_if_empty!(set_scm_head, scm_head, String);
369-
create_set_if_empty!(set_spaces_id, spaces_id, String);
370-
create_set_if_empty!(
371-
set_root_turbo_json_path,
372-
root_turbo_json_path,
373-
AbsoluteSystemPathBuf
374-
);
375-
}
376-
377337
// Maps Some("") to None to emulate how Go handles empty strings
378338
fn non_empty_str(s: Option<&str>) -> Option<&str> {
379339
s.filter(|s| !s.is_empty())
@@ -428,30 +388,6 @@ impl TurborepoConfigBuilder {
428388
.unwrap_or_else(get_lowercased_env_vars)
429389
}
430390

431-
create_builder!(with_api_url, api_url, Option<String>);
432-
create_builder!(with_login_url, login_url, Option<String>);
433-
create_builder!(with_team_slug, team_slug, Option<String>);
434-
create_builder!(with_team_id, team_id, Option<String>);
435-
create_builder!(with_token, token, Option<String>);
436-
create_builder!(with_signature, signature, Option<bool>);
437-
create_builder!(with_enabled, enabled, Option<bool>);
438-
create_builder!(with_preflight, preflight, Option<bool>);
439-
create_builder!(with_timeout, timeout, Option<u64>);
440-
create_builder!(with_ui, ui, Option<UIMode>);
441-
create_builder!(
442-
with_allow_no_package_manager,
443-
allow_no_package_manager,
444-
Option<bool>
445-
);
446-
create_builder!(with_daemon, daemon, Option<bool>);
447-
create_builder!(with_env_mode, env_mode, Option<EnvMode>);
448-
create_builder!(with_cache_dir, cache_dir, Option<Utf8PathBuf>);
449-
create_builder!(
450-
with_root_turbo_json_path,
451-
root_turbo_json_path,
452-
Option<AbsoluteSystemPathBuf>
453-
);
454-
455391
pub fn build(&self) -> Result<ConfigurationOptions, Error> {
456392
// Priority, from least significant to most significant:
457393
// - shared configuration (turbo.json)
@@ -483,27 +419,8 @@ impl TurborepoConfigBuilder {
483419
let config = sources.into_iter().try_fold(
484420
ConfigurationOptions::default(),
485421
|mut acc, current_source| {
486-
let mut current_source_config = current_source.get_configuration_options(&acc)?;
487-
acc.set_api_url(&mut current_source_config.api_url);
488-
acc.set_login_url(&mut current_source_config.login_url);
489-
acc.set_team_slug(&mut current_source_config.team_slug);
490-
acc.set_team_id(&mut current_source_config.team_id);
491-
acc.set_token(&mut current_source_config.token);
492-
acc.set_signature(&mut current_source_config.signature);
493-
acc.set_enabled(&mut current_source_config.enabled);
494-
acc.set_preflight(&mut current_source_config.preflight);
495-
acc.set_timeout(&mut current_source_config.timeout);
496-
acc.set_spaces_id(&mut current_source_config.spaces_id);
497-
acc.set_ui(&mut current_source_config.ui);
498-
acc.set_allow_no_package_manager(
499-
&mut current_source_config.allow_no_package_manager,
500-
);
501-
acc.set_daemon(&mut current_source_config.daemon);
502-
acc.set_env_mode(&mut current_source_config.env_mode);
503-
acc.set_scm_base(&mut current_source_config.scm_base);
504-
acc.set_scm_head(&mut current_source_config.scm_head);
505-
acc.set_cache_dir(&mut current_source_config.cache_dir);
506-
acc.set_root_turbo_json_path(&mut current_source_config.root_turbo_json_path);
422+
let current_source_config = current_source.get_configuration_options(&acc)?;
423+
acc.merge(current_source_config);
507424
Ok(acc)
508425
},
509426
);

0 commit comments

Comments
 (0)