-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add macros to construct libcnb-data
newtypes from literal strings
#172
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Malax
force-pushed
the
malax/newtype-literals-macro
branch
3 times, most recently
from
November 19, 2021 15:39
9bb7556
to
3a06ec3
Compare
Malax
force-pushed
the
malax/newtype-literals-macro
branch
from
November 19, 2021 15:41
3a06ec3
to
666f14a
Compare
edmorley
approved these changes
Nov 19, 2021
Thank you for doing this! I much prefer the idea of compile time validation :-) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem Statement
With the use of newtypes to enforce correct values for CNB defined types also comes a drawback: we no longer cannot use string literals in places where the a literal value is needed. This forces the user to put
.unwrap
calls in these cases:Currently, this is not too bad as we only have this case with
ProcessType
really, usually once per buildpack if at all. But as soon as we implement #101, we will have this for every layer. I still think having newtypes for these scenarios is great but we need a solution for literal values and this is where this PR comes in.Solution
This PR extends the
libcnb_newtype!
macro introduced in #162 so that it generates another macro for every newtype that allows us to create literal newtype values that are checked at compile time! The previous example becomes:Should the string not be a valid
ProcessType
, the buildpack will not compile:Since these are regular compile errors, the error nicely propagates into IDEs out of the box:
Implementation Notes
Regex
The implementation is very generic since we can validate all our newtype values via regular expressions. This allows us to compile the regular expression at compile time and use it against the literal string passed to the macro. As soon as we need to use arbitrary Rust code to validate these, the current approach with automatically generated macros is no longer feasible, unless we write a Rust interpreter or use a scripting language for the validation logic.
To extend the cases where regular expressions can be used, I replaced the
regex
crate with fancy_regex to allow for look-around expression which cannot be implemented in a NFA regex implementation. This means our regular expressions are no longer guaranteed to run in linear time. I think this is a fine trade-off for us, since we're only using it with very simple expressions that are entirely controlled by us and only used in a very limited use-case.See: Catastrophic Backtracking
Removal of extra predicates
See above, we cannot use arbitrary Rust code to do the validation at compile time since we would have no way to execute it. We can implement allow- and blocklists via regex just fine and don't need extra predicates at this point.
New crate:
libcnb-proc-macros
This feature can only be implemented via procedural macros. By design, procedural macros cannot be used in the crate they're defined it and need a special crate type. Therefore, we have a new crate for the proc macro required by this PR and potential future ones.
Testing Strategy
Testing code that, by design, should not compile is hard. Rust currently provides no way to test non-compiling code in unit tests. This limits the tests to only test the positive cases. I would like to test the negative code path as well. There is compiletest-rs which seems overkill in our-case.
To keep things simpler, I'd fine to not test the negative path for now. Note that the string will still be validated at runtime as well, the worst-case scenario would be that code with an illegal newtype value compiles, but fails at runtime as if these macros would not exist. I'm happy to be convinced otherwise. :)
GUS-W-10203923.