-
Notifications
You must be signed in to change notification settings - Fork 112
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 external types CI check + config #183
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #183 +/- ##
==========================================
+ Coverage 72.86% 73.40% +0.54%
==========================================
Files 7 7
Lines 1861 1854 -7
==========================================
+ Hits 1356 1361 +5
+ Misses 505 493 -12 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that these changes are semver-incompatible. I usually like to proactively bump the version number in cases like these (in this case, to 0.12.0) to make sure we don't release a semver-compatible release with semver-incompatible changes by accident.
I'm not sure whether it's useful to have separate RingKeyRejected
and RingUnspecified
error variants, I'd probably just merge them into a single Ring
variant.
405827f
to
46da70c
Compare
SGTM. I added a commit for that. I'll reconcile with #184 if that one lands first.
Sure, I don't feel strongly. I've done that in a separate commit in case it proves to be controversial. If it isn't, I'll squash the change to use a unified error with the two changes that fixed the type leaks. |
Probably a good idea to bring cargo-semver-check into CI as well. I'll look at doing that shortly. |
tacked on another commit for that. |
985cbe4
to
dda6554
Compare
Some points:
Ring's error variants are already unpleasingly limited (part of the greater problem of Ring's public API being overly restrictive; i understand that this is done for purposes of security but it's gone too far). In any case, I think it's useful to expose the full variety that Ring exposes. |
What about exposing only a So:
Does this sound good? |
If you want to preserve more of the inner stuff, I think we should just store a |
For applications I agree that enums are not good. For libraries, I have a different opinion: it's better to have all the possible cases listed rather than "some error, can be converted to a string maybe". The |
Actually, given that many error kind's don't carry data, we should maybe actually do it via the opaque wrapper type indeed... |
Because of the ability to downcast? I think yielding a |
The semver risk of Compared to enums, the semver-breaking nature is worse in two ways:
maybe this "breakage moved to runtime" thing is what makes this a non-breakage with some definitions of semver, but I'm not a fan of that. |
IMO using a |
I'm not sure I'm convinced there's an actual use-case for preserving the "inner stuff". Can you find a downstream user that unpacks the error variants that existed prior to this branch to do something other than display the string-ified err? None of the variants being obscured strike me as things you would want to try and programmatically recover from through distinguishing the variant. I'm generally hesitant about making things more complicated for theoretical uses when we could land something simpler and then adjust if someone arrived wanting to do a thing that wasn't possible. |
I don't really agree that the issue the PR tries to resolve needs a change in the API: IMO it's enough to document that if you rely on these types, you should pin your dependency. I think we all agree that error inspection is a niche thing at best, but I still think that it is a use case. Say you want to handle file not found errors specially and want to also look for files with a different extension. or you might want to display "seems there is no network" when the error was caused by dns.
|
Where would you propose documenting that? I think it will likely be overlooked and result in a mismatch between expectations and reality that will break downstream builds.
Right, but those aren't the error variants we're talking about here so I'm not sure it's relevant. I'm specifically interested in situations where you think a real user needs to match the specific error variants I'm proposing making more opaque. I would be in favour of the |
the type level docs of the
it only happens in the rare instances where people try to use these types (which you argue nobody wants to do anyways). And I don't want to repeat myself but a compile time breakage something easily discoverable and it's a very actionable thing, as you can easily pin the rcgen version even if it was some upstream crate which introduced the breakage.
that's a potential danger that the features are meant to resolve.
for example there is |
I've looked at some errors in the ecosystem. most of these don't have just a string-only way to access the error.
so it seems to me that the best practice is to not just expose glorified opaque Display objects, but enrich them with data that is maybe useful. Which also confirms what I thought earlier that the best practice was. |
Note that I wasn't proposing using |
FTR, we already don't expose any of Ring's internal types (except via the From conversion stuff, and I'm not sure if the proposed solution here is the one we want to take). |
That was how I understood your suggestion fwiw 👍
For the For the
The Since rcgen exposes interfaces for accepting DER a consumer that's interested in maximum programmatic specificity around PEM decoding could import |
fbe7cd9
to
ce54465
Compare
This branch has a +1 from djc, and I've restored the "lost" information for Ring errors.
I still believe this is the best path forward for an imagined consumer that needs maximum error specificity for PEM decoding. This puts full control into their hands, allowing any choice of PEM and Base64 library or configuration, while imposing no downstream costs to rcgen w.r.t managing its own dependency versions for the simple use case. @est31 Are you still unswayed by these arguments and putting a hard -1 on merging this? If so then it feels like this branch has reached an impasse and I'll probably close it. I'd prefer to reach an end state soon so that I don't have to keep rebasing and can direct that energy elsewhere. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay let's try it with a stringly typed Pem error now and if users complain we can always change it.
For the From removals, it's a bit sad that this leads to more verbose code, but I don't think there is much we can do about it. We can always improve it in the future also. Let's merge it.
Sounds good to me. Thank you. If there's any follow-up work related to this I'm happy to do it. |
Maybe instead of the map_err invocations there could be a trait like: trait ToRcgenErr: Sized {
fn _to_rcgen_err(self) -> Result<(), Error> { todo!() }
}
impl<T> ToRcgenErr for Result<T, ring::error::KeyRejected> {
fn _to_rcgen_err(self) -> Result<(), Error> { Error::RingKeyRejected(err.to_string()) }
}
// ... similar impl blocks for the other conversions to Error The advantage of that trait is that it is private and thus nothing leaks to the outside, and when you write/read code you don't have to immediately worry about how the error is converted. As it is an extension trait, we just need to make sure that the function name would never ever be added to std, which my suggestion fulfills. Shorter name suggestions welcome. |
I'll experiment with this idea and get back to you 👍 |
Previously this file had some places where members of a type were separated by whitespace, and some places where they weren't. In my experience it's more common to use whitespace here, and I think it reads better, so this commit standardizes on that (at least within key_pair.rs).
This trait offers a way to convert errors from dependencies into `rcgen::Error` instances without manually mapping the error in each instance, or leaking the error type into the public API with a `From<x> for Error` impl.
Having `From<ring::error::KeyRejected>` defined on the public `Error` type means that the `*ring*` type leaks into rcgen's public API, complicating semver incompatible updates. This commit takes the simple approach of using the crate-internal `ExternalError` trait to convert from the `*ring*` error type to our generic rcgen `Error::RingKeyRejected` type as needed. This allows the `From` impl on `Error` to be removed, fixing the type leak.
Having `From<ring::error::Unspecified>` defined on the public `Error` type means that the `*ring*` type leaks into rcgen's public API, complicating semver incompatible updates. This commit updates the sites that previously used this trait to instead use a crate internal `ExternalError` trait implementation to map to the generic rcgen `Error::RingUnspecified` err. This allows the `From` impl on `Error` to be removed, fixing the type leak.
Having `From<pem::PemError>` defined on the public `Error` type means that the `pem` type leaks into rcgen's public API, complicating semver incompatible updates. This commit updates the sites that previously used this trait to use the crate internal `ExternalError` extension trait to map the `PemError` err to the generic rcgen `Error::PemError` err. Additionally, the `rcgen::Error::PemError` variant is changed to hold a `String` with the `pem::PemError` error string instead of the type itself. This allows the `From` impl on `Error` to be removed, fixing the type leak.
This commit adds configuration and a CI task for checking that no types from dependencies are accidentally leaked through the Rcgen public API unintentionally. The previous commits in this branch fixed the `*ring*` type leaks, so our configuration only has two white-listed types as of this branch: 1. `time::offset_date_time::OffsetDateTime` It's unclear whether usage of that type should be adjusted, so for now we explicitly allow-list it in the cargo-check-external-types config. We can deal with this type (or not) in the future. 2. `zeroize::Zeroize` We could probably avoid leaking this type by implementing `Drop` and calling `zeroize` on fields directly from the `drop` impl. In the meantime we add this type to the allow list.
This commit bumps the rcgen version from `0.11.3` to `0.12.0` to reflect there are breaking changes in `main`.
This commit adds `cargo-semver-checks`[0] to CI. This tool helps detect when semver incompatible changes are being made without properly incrementing the `Cargo.toml` version. Note that this is necessary, but not sufficient, for ensuring semver compatibility. This tool is helpful, but not perfect, and can miss some breakages. [0]: https://github.com/obi1kenobi/cargo-semver-checks
ce54465
to
11f31a2
Compare
@est31 I implemented your suggestion in branch, but with the trait named |
Yes, perfect, thank you. |
Having types from dependent crates appear in rcgen's public API makes taking semver incompatible updates to those dependencies tricky: we need to treat it as a semver incompatible change to rcgen itself. To make sure whenever this type leak happens that it was done because of a deliberate choice this branch adds cargo-check-external-types to CI.
Three existing types that leaked into the API are fixed:
ring::error::KeyRejected
,ring::error::Unspecified
, andpem::PemError
. In each case it's simple to translate the specific error types from the dependency into our ownError
variants that don't expose the type.Two types are allow-listed:
time::offset_date_time::OffsetDateTime
andzeroize::Zeroize
.The first,
OffsetDateTime
is used throughout the public API. It isn't clear to me if we want to keep that as part of the public API and treattime
updates carefully, or if we should pursue using a more generic representation. I've left this out for now so it can be discussed.The second,
Zeroize
, could probably be avoided by implementingDrop
and callingzeroize
on the sensitive fields manually. That's the approach Rustls implemented (rustls/rustls#1492). I've left this out for in case there was a reason to prefer implementing the trait on the public types.