Enable consumers to skip failure to resolve dependencies
Closed this issue · 4 comments
Describe the Enhancement
I would like to find a better solution for callers of postal.Service.Resolve()
to identify that no dependencies were resolved without failing the build.
Currently this is possible, but it relies on inspecting the returned error text. That is brittle, and I'd like to find a better solution.
Motivation
The specific use-case I have in mind is splitting out precompiled dependencies from source dependencies (e.g. in the cpython buildpack) via different IDs/names and resolving them independently. In this scenario, we would like to be able to bypass the failure associated with resolving zero precompiled dependencies, while still failing if there are zero source dependencies resolved.
Possible Solutions
Below are multiple ways to achieve this, ordered by my most-preferred to my least-preferred solution.
-
Return newly-created specific error types to
postal
(e.g.ErrNoDependenciesResolved
,ErrMultipleDependenciesResolved
) which would allow consumers to introspect the error as follows:precompiledDependency, err := service.Resolve() if err != nil && !errors.Is(err, postal.ErrNoDependenciesResolved) { // fail }
Pros:
- Does not change the interface of the function (i.e. it is backwards-compatible)
- There is precedent for this in golang (e.g.
if err != nil && !errors.Is(err, fs.ErrExist){ ... }
)
Cons:
- This is the most complex option for consumers
- Adds the most code to the
postal
package - Introspecting on error types is an ugly way to manage control flow for non-failure cases
-
Add number of dependencies resolved as part of the result (e.g.
func Resolve() (Dependency, int, error))
)precompiledDependency, count, err := service.Resolve() if err != nil && count != 0 { // fail }
Pros:
- Fairly easy to read and understand
Cons:
- Not backwards-compatible
- Somewhat inelegant
-
Allow users to pass in some config to skip the failure entirely (e.g.
func Resolve([existing args] , skipNoDepsFail bool) (Dependency, error)
orfunc Resolve([existing args], config SomeNewConfigType) (Dependency, error))
)precompiledDependency, err := service.Resolve([existing args], true) if err != nil { // err is nil when zero dependencies are resolved // fail }
Pros:
- Simple for consumers; keeps consumer flow clean
- Fairly easy to read and understand
Cons:
- Not backwards-compatible
- Quite inelegant.
cc @paketo-buildpacks/tooling-maintainers - I am happy to create a PR for this but I'd like feedback on which direction we prefer.
I'd lean towards option 1 for a couple of reasons:
- It has precedent as a pattern in the Go ecosystem
- It is the only backwards-compatible option
I like option 1 because it allows consuming code to be very explicit about which errors to handle - reading the buildpack code will make it obvious that the buildpack won't fail if no dependencies are resolved. Also, +1 for backward compatibility.
Great, looks like we have a clear consensus that the way we'd like to see this feature implemented is via specific error types - the first option I outlined.
Thanks for the thoughts everyone. I'll submit a PR for this.