Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
compat: Improve TryCompat trait type safety and fix consistency issue
It is possible to implement try_compat() in an inconsistent way compared to other implementations. Instead, implement the whole compatibility logic in the default try_compat() implementation, and create tailored method to be implemented by objects: try_compat_inner() and try_compat_children(). The new CompatResult enum contains all the required informations to implement a consistent try_compat() for all trait implementations: the full, partial or not-supported state, the associated compatible value, and the potential associated error. This way, try_compat() has everything to take the decision to either return a Some(Self) or a CompatError according to the CompatLevel. The try_compat() default implementation now always update the self's CompatState, and then return a result according to its CompatLevel thanks to clean and exhaustive CompatResult matches. We now only use the Compatibility struct for Ruleset and RulesetCreated, but we pass separately ABI, CompatLevel and &mut CompatState to try_compat(). This enables to differentiate mutability and store CompatState in all Compatible types. ABI and CompatLevel should only be inherited from Ruleset and RulesetCreated. try_compat_inner() needs to be implemented by all TryCompat types. This method only gets a non-mutable ABI, and returns a new CompatResult or a CompatError. Delegating nested TryCompat objects management (e.g. allowed_access by PathBeneath) can lead to inconsistencies between different objects. To avoid this issue as much as possible, create a new try_compat_children() method to specifically call try_compat() on all the nested objects, if any. For instance, PathBeneath objects contain an AccessFs object and they both implement Compatible, which lets them have an independent CompatLevel thanks to set_compatible(). It is not possible to call try_compat() from a try_compat_inner() implementation because they don't return the same result type. Instead, the new try_compat_children() returns the same type as try_compat() and can then call it for all the children. try_compat_children() is only called by try_compat(), which then makes all the compatibility process consistent across different object implementations. Test this fixed behavior with a more complete too_much_access_rights_for_a_file(), initially introduced with a previous commit for the purpose of this fix. As a side effect, the PathBeneath implementation now first checks its allowed_access generic compatibility (with try_compat_children), and then the compatibility with the file type (with try_compat_inner). To better fit with the Compatible contract, replace CompatLevel with Option<CompatLevel>. None is set by default until users change it. This enables to inherit the parent compatibility level if the current one is not set. Add the TailoredCompatLevel trait with the tailored_compat_level() method to get the current compatibility level accordingly. Automatically implements TailoredCompatLevel for each Compatible implementation (only PathBeneath for now), which helps avoid missing an implementation for a new Compatible types. Signed-off-by: Mickaël Salaün <[email protected]>
- Loading branch information