fix: update all ports to support success + error union response #468 #475
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.
TL;DR: there shouldn't be any breaking changes in this PR, besides removing the unused
error
fields on some Cache port methods. Everything else should be backwards compatible. Not a lot of code changes (most of the diff is the duplicated utility), but need to explain my reasoning.Previously, adapters would send back resolved promises for successful responses and rejected promises for error responses. With #468, that paradigm has shifted, such that an unhandled exceptions returns as a rejected promise and a handled exception should returned as a resolved promise.
Zod schemas wrap each adapter method, that validate the return type of resolved responses. Since errors were previously not resolved responses, these were not validated. Now they run through the zod schema validation flow, which is what we want, but introduces some necessary changes:
status
andmsg
msg
was present on some methods on some ports, but not all of them.status
was less present. Firstly, because hyper convention ismsg
andstatus
only come back for error responses, and the zod schema is not used to validate rejected promises, these fields on the schema were superfluous. Now with the new error convention, they are not superfluous, but a new issue arises: **With object schemas, Zod will parse a value against a schema, then STRIP extraneous fields. This meant thatstatus
andmsg
had to be added explicitly to each return type schema on each port method.all optional fields on return types
Because the resolve/reject promise boundary separated the success and error responses before, this wasn't an issue. Now both success and error responses can come back as a resolved Promise, and thereby are parsed by zod. If we were to try and encapsulate this into a single Zod object schema, say for
queryDocuments
, effectively all fields on the return schema, besidesok
, would have to beoptional()
:This isn't particularly useful, for validation. Ideally we would like some fields to be required if
ok === true
and other fields to be required ifok === false
.The solution: Zod Discriminated Union Response
Zod introduced a new feature in
3.12.0
called discriminated unions which is precisely what we need. However, due to colinhacks/zod#965 we cannot use that version just yet. For now,z.union
will suffice.We use
zod.union
to represent the possible return types of each adapter method. I've created a utility calledhyperResSchema
which enforces the union between a "success" response and a "hyper error" response. This allows each port to validate each type of response, without needing to make each field optional. I've duped this util across ports. perhaps eventually, we move it intohyper-utils
hyper responses everywhere
Eventually, we want each port method to return a "hyper response", that is
{ ok: boolean, .... }
. There are some apis that do not follow this convention. But we would like ways to adapters to incrementally move to this convention without breaking validation. For this, I updated the following methods such that the schema supports the current return type, and a "hyper response" return type:ListObjects
: can be currentz.string().any()
or hyper response with{ objects: z.string().any() }
Index
: can be currentz.string().any()
or hyper response with{ queues: z.string().any() }
get
: can be currentCrawlerDoc
or hyper response with{ doc: CrawlerDoc }
getDoc
: can be currentany()
or hyper response with{ doc: any() }
update, delete, create
: removed optionalerror
field from response (it should return a hyper error)index
: can be currentz.string().any()
or hyper response with{ caches: z.string().any() }
other things I did
putObject
:stream
must now be defined (notnil
) ifuseSignedUrl
isfalsey
using suggested approach from z.object().passthrough() not working with objects like Buffer colinhacks/zod#925