clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
2020-08-29T19:06:42.004500Z

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

2020-08-29T19:08:41.006900Z

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

2020-08-29T19:19:58.012500Z

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).

seancorfield 2020-08-29T19:56:06.012700Z

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

seancorfield 2020-08-29T19:56:24.012900Z

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

2020-08-29T20:19:57.013100Z

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.

2020-08-29T20:20:59.013300Z

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 🙂.

2020-08-29T20:34:15.013600Z

(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.)

seancorfield 2020-08-29T20:41:56.013800Z

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).

seancorfield 2020-08-29T20:43:53.014Z

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.

2020-08-29T20:49:23.014200Z

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.

seancorfield 2020-08-29T20:55:08.014500Z

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.

2020-08-29T21:04:50.014700Z

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.

seancorfield 2020-08-29T21:07:00.014900Z

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

seancorfield 2020-08-29T21:13:48.015100Z

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

seancorfield 2020-08-29T21:15:14.015300Z

(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)

2020-08-29T21:15:38.015500Z

Right that makes sense

2020-08-29T21:19:36.015700Z

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 🙂!