Fork me on GitHub
#clojure-spec
<
2020-08-29
>
Lennart Buit19:08:42

How would you go about spec’ing a (http) patch like api using nil as a sentinel for retraction. Let’s say you have an entity that has an optional attribute, I’d hate to say that this attribute is s/nilable in its global definition just because there is a patch endpoint using nil to signal retraction

Lennart Buit19:08:41

Like — if I send you this attribute, it will either not be there, or it will have a non-nil value.

Lennart Buit19:08:58

I don’t know, it feels asymmetrical: I’ll always promise you either no value at all, or some value satisfying a spec, but you can provide me no value, a value satisfying a spec or nil, but only in this specific case. I guess it doesn’t help me that I can have only one definition for a namespaced keyword (here).

seancorfield19:08:06

Isn't this just a non-required key in a spec?

seancorfield19:08:24

It's either present (and conforms) or it is not present.

Lennart Buit20:08:57

Yes, outgoing I would agree: I’ll (= server) never send you a nil for a key I have no value for and instead omit it. The problem is in how you (= client) tell me that you don’t want this value anymore: If you omit it in your (partial) update request, do you mean to retract it, or to retain it? So some rest endpoints allow you to patch an entity, but with nil as value, meaning ‘let’s get rid of this value for that attribute’. So now we have a bit of asymmetry: I’ll promise you to never send a nil value, but you can send me a nil to indicate retraction on this patch endpoint.

Lennart Buit20:08:59

I don’t know how to express, in spec, that I as a server make a stronger guarantee than you as a client have to when patch ’ing entities. If that makes any sense 🙂.

Lennart Buit20:08:15

(Or more specifically, I don’t know how to do that when my keys are namespace qualified. If I were to use s/keys with :req-un I could maintain two specs.)

seancorfield20:08:56

That's just two different specs (perhaps with reuse on the common stuff). A spec for the result of a call (where the key is optional but spec'd to be non-nil). A spec for the input value (where the key is nilable).

seancorfield20:08:53

If it's an API spec, it's going to be for unqualified keys, surely? Since it will be a wire spec, e.g., JSON.

Lennart Buit20:08:23

Right, we may have extended this nilability (or the spec, for that matter) too far into our system. We have a JSON handler that accepts this nil and is spec checked, that is unqualified, but then we have an update method shared between this JSON endpoint and other places that is also checked, but has qualified keys. Thats where friction occurs: the keys are qualified, but their semantics differ between what you supply and what you get.

seancorfield20:08:08

Well, if you have an internal (qualified) name for that attribute, either it should be optional and non-`nil`, or it should be nilable -- in all cases. And if that's not possible, then the two semantics should have different names.

Lennart Buit21:08:50

Sorry for the kinda fuzzy description… Right so in the latter of your options you say to extend the notion of valid values for this attribute to be a superset of all values it can take in all contexts. That means that consumers I could have promised no nils (because they consume the ‘outgoing’ format), have to consider nils because thats what I could promises them in my spec, right? I find that kinda sad.

seancorfield21:08:00

I'm saying use different specs as needed and transform the data to match as you cross boundaries.

seancorfield21:08:48

Another option is to chose a specific, unique, representation for a retraction (and, again, map from the inbound retraction to that representation).

seancorfield21:08:14

(we had exactly this situation and we tried to blur the lines with nilable and optionality and it was a mess so we mapped it to a different representation altogether)

Lennart Buit21:08:38

Right that makes sense

Lennart Buit21:08:36

Yeah, I think we are going wrong in a similar way: have a single namespace qualified keyword for an attribute in all its contexts, but using it in different ways. I’ll put this in the hammock, thank you for the input, as always 🙂!