feat: Take dependencies into account when sorting environment variables#1249
feat: Take dependencies into account when sorting environment variables#1249siegfriedweber wants to merge 11 commits into
Conversation
NickLarsenNZ
left a comment
There was a problem hiding this comment.
LGTM, but left a comment to check my understanding.
|
|
||
| /// Pattern for an escaped dollar sign (`$$`) | ||
| static ESCAPED_DOLLAR_SIGN_PATTERN: LazyLock<Regex> = | ||
| LazyLock::new(|| Regex::new(r"\$\$").expect("should be a valid regular expression")); |
There was a problem hiding this comment.
I would go as far as and say it must be a valid regex.
| LazyLock::new(|| Regex::new(r"\$\$").expect("should be a valid regular expression")); | |
| LazyLock::new(|| Regex::new(r"\$\$").expect("static string must be a valid regular expression")); |
There was a problem hiding this comment.
changed in 9a3111d
"Should" is often used in these cases. If I write a unit test that is EXPECTED to panic, I use the attribute #[should_panic] even if I know that it MUST panic as long as nobody changes the code.
| let dependency_resolver = | ||
| EnvVarDependencyResolver::new(&value, ENV_VAR_DEPENDENCY_RESOLVER_MAX_RECURSION_DEPTH); | ||
|
|
||
| let mut vec: Self = value.0.values().cloned().collect(); |
There was a problem hiding this comment.
There is no need to clone. We can consume value.
| let mut vec: Self = value.0.values().cloned().collect(); | |
| let mut vec: Self = value.0.into_values().collect(); |
There was a problem hiding this comment.
Oh we borrow value above and need to hold onto it after we collect the values into the Vec.
This indicates that the design of EnvVarDependencyResolver is sub optimal.
There was a problem hiding this comment.
EnvVarDependencyResolver resolves the dependencies and provides sort keys for a later sort. Which design would you prefer?
|
|
||
| /// Resolves dependencies between environment variables and provides sort keys which take these | ||
| /// dependencies into account | ||
| pub struct EnvVarDependencyResolver<'a> { |
There was a problem hiding this comment.
I feel like this whole struct should be build around owning the EnvVarSet. This would get rid of a whole bunch of clones all over the place. This additionally makes sense, because it is only used in a context in which the underlying EnvVarSet is consumed.
There was a problem hiding this comment.
I feel like this whole struct should be build around owning the EnvVarSet.
If EnvVarDependencyResolver would own the EnvVarSet, then it would not be available to the sorting algorithm anymore.
This additionally makes sense, because it is only used in a context in which the underlying
EnvVarSetis consumed.
An EnvVarSet should be usable for several containers or role-groups. So, consuming it, does not feel right for me.
This would get rid of a whole bunch of clones all over the place.
The code could be written completely without clones. But every avoided clone makes the code a bit harder to read. The EnvVarSets are small and every EnvVar usually contains a short name and a short value. So the clones are cheap.
However, I changed the code to use references (05660fa). The IntoIterator for EnvVarSet still clones the EnvVars, because this would be the alternative:
impl IntoIterator for EnvVarSet {
type IntoIter = vec::IntoIter<Self::Item>;
type Item = EnvVar;
fn into_iter(self) -> Self::IntoIter {
let sorted_indices: Vec<usize> = {
let dependency_resolver = EnvVarDependencyResolver::new(
&self,
ENV_VAR_DEPENDENCY_RESOLVER_MAX_RECURSION_DEPTH,
);
let mut indexed_env_vars: Vec<(usize, &EnvVar)> =
self.0.values().enumerate().collect();
indexed_env_vars
.sort_by_cached_key(|(_, env_var)| dependency_resolver.sort_key(env_var));
indexed_env_vars
.into_iter()
.map(|(index, _)| index)
.collect()
};
let mut env_vars: Vec<Option<EnvVar>> = self.0.into_values().map(Some).collect();
sorted_indices
.into_iter()
.map(|index| {
env_vars[index]
.take()
.expect("sorting must yield each index exactly once")
})
.collect::<Vec<_>>()
.into_iter()
}
}| /// assert_eq!(None, resolver.calculate_closure(&env_var7)); | ||
| /// assert_eq!(None, resolver.calculate_closure(&env_var8)); | ||
| /// ``` | ||
| pub fn calculate_closure(&self, env_var: &EnvVar) -> Option<BTreeSet<String>> { |
There was a problem hiding this comment.
What's the reason this needs to be public?
There was a problem hiding this comment.
Doc tests can only access public functions. I wanted to test each function in isolation because that made it easier to achieve high test coverage. I know this isn't idiomatic, but Rust doesn't offer an alternative I'd like better.
There was a problem hiding this comment.
What about pub(crate) (I assume that still doesn't give it enough visibility)?
There was a problem hiding this comment.
pub(crate) is not sufficient for doc tests.
If we would own EnvVar, then e.g. referenced_env_vars would be a public function there and it would be accepted that it is tested in isolation. We could wrap EnvVar, but that would be overkill.
Description
Take dependencies into account when sorting environment variables
For example, the environment variables
are sorted in this order when iterated or converted to a vector:
Part of stackabletech/issues#866
Definition of Done Checklist
Author
Reviewer
Acceptance