Fork me on GitHub
#clojure-spec
<
2018-09-11
>
slipset14:09:52

Sorry if this is a faq, but I’d like to generate specs at runtime to use for validation eg:

slipset14:09:03

Say I receive something like:

slipset14:09:24

{:id 1, :model-id 4711, :foo 'bar}

slipset14:09:56

In order to know how to validate this thing, I’d need to look up what the model is in a database, and then construct a spec based on that model.

1
ghadi14:09:40

eval

👀 1
lilactown14:09:01

unfortunately, clojure.spec is very unwieldy for this type of use case

slipset14:09:24

What I’d be looking for is something like:

slipset14:09:04

(s/valid? (model->spec (find-model (:model-id entity))) entity)

ghadi14:09:23

@slipset (-> model-id (grab-from-database db) read-string eval)

ghadi14:09:42

that'll give you a spec, if the form is saved in the db

slipset14:09:53

And with my understnading of spec, this is not possible, but I’d like to flag it as a use case, just in case.

ghadi14:09:56

and all of what it refers to is already loaded and available

ghadi14:09:16

it's totally possible, you just have to dynamically load the spec

lilactown14:09:56

I know that there seems to be some disinclination to adding more support this. it’s my most-desired feature

ghadi14:09:27

it's not a feature you add to spec, you can already do it

slipset14:09:48

@ghadi but my specs are not “known” or stored anywhere, and there might be a bunch of models (a bunch being in the thousands).

slipset14:09:27

While I’m typing this, I guess I think that if specs were data, this would be feasible, right?

lilactown14:09:40

the ability to have “anonymous” specs / specs-as-data would have a lot more utility as a general purpose way of describing data. but I think that it rubs against the desire of the core team to encourage people to use namespaced keys

ghadi14:09:09

regardless of the mechanism, your specs have to be known somehow if you want to dynamically load them

slipset14:09:24

@ghadi: in my db I would typically have someting like this (for a model)

slipset14:09:20

{:id 4711, :foo "integer", :bar "list-of-strings", :baz "string"}

slipset14:09:46

and, while this might be fortunate, it’s how my world is.

guy14:09:13

Do you have lots of models?

slipset14:09:31

and they’re user defined

slipset14:09:02

so my model->spec would return, for the model above, something like:

slipset14:09:36

(s/keys :req-un [::foo ::bar ::baz])

slipset14:09:47

and there I see where the eval comes in 🙂

ghadi14:09:52

any validation system that stores stuff as data needs to convert list-of-strings into some predicate

slipset14:09:49

Yeah, I see that, list-of-strings which is one of a few predefined types, would be mapped to (s/coll-of string?)

slipset14:09:27

The main problem here, I guess is ::foo, ::bar, and ::baz needs to be defined.

lilactown14:09:56

right. everything in clojure.spec must be named

guy14:09:12

Are the types of your data always quite static?

ghadi14:09:37

named only if you refer to the spec by name

ghadi14:09:46

(keys or def / fdef)

ghadi14:09:12

you can call (valid? (coll-of string?) ["a" "b"]) without naming the spec

ghadi15:09:21

of course if you're checking a map, you'll want to handle that yourself

ghadi15:09:44

i.e. just because s/keys is available, you don't have to use it

slipset15:09:32

The types are static

slipset15:09:05

but the field names are not.

lilactown15:09:13

:thinking_face: maybe using map-of instead of keys?

lilactown15:09:40

there’s also https://github.com/metosin/spec-tools which might be a more ergonomic solution

ikitommi15:09:12

with spec-tools, without macros, on top of the non-documented parts of clojure.spec:

(require '[spec-tools.data-spec :as ds])
(require '[clojure.spec.alpha :as s])

(let [model {:foo integer?
             :bar [string?]
             :baz string?}
      spec (ds/spec
             {:spec model
              :name ::spec})]
  (s/valid? spec {:foo 1
                  :bar ["kikka" "kukka"]
                  :baz "abba"}))
; true

borkdude16:09:47

@ikitommi that’s cool. kind of like Schema notation for clojure.spec?

borkdude16:09:07

fair reminder, go on 🙂

ikitommi16:09:35

@borkdude yes, it is. Quite nice for ad-hoc stuff like web apps, where one would use Schema

ikitommi16:09:10

I’m actually looking forward to seeing things break.

ikitommi16:09:58

any plans on doing that @alexmiller?

borkdude16:09:12

even the public parts of spec itself may break if it’s for the better, it’s alpha 😉

alexmiller16:09:29

Rich has been getting back to spec and right now I think it’s likely that we’ll try to get 1.10 out the door, then start working on spec in earnest

🍾 1
borkdude16:09:55

what are the main features of 1.10? - I saw you were working on better error messages, very nice work

alexmiller16:09:38

updated JVM/JDK requirements and compatibility, prepl (kind of alpha), error messages

seancorfield16:09:35

Does that mean we'll get some new tooling from Cognitect based around the prepl?

alexmiller16:09:51

unlikely for 1.10

borkdude16:09:07

prepl.alpha… what are we going to do with the prepl?

alexmiller16:09:18

whatever you like :0

alexmiller16:09:46

there’s a bunch of things in work. rather than wait for them all to reach completion, I think we’ll just release 1.10 and keep working on them

borkdude16:09:02

anything to read on prepl?

seancorfield16:09:12

The source code? 🙂

seancorfield16:09:39

I had a quick play with it when it dropped... I can imagine some interesting tooling options based on it...

alexmiller16:09:58

there’s a small chance it might even be removed before 1.10 releases

borkdude16:09:59

I have this vague notion it’s something similar to unrepl, but that’s all I know

alexmiller16:09:10

it shares many goals with unrepl

alexmiller16:09:27

both are data-oriented stream repls

seancorfield16:09:18

We're thinking a bit about building some internal tooling on top of prepl. Automating some of the stuff we currently do today (via a REPL manually).

seancorfield16:09:22

(we run a Socket Server REPL inside one of our production processes and currently use unrepl to talk to it over an ssh tunnel for certain things)

borkdude16:09:52

we run nREPL in production, but I only talk to it myself 😉

seancorfield16:09:46

We used to -- but I didn't like the stack of dependencies that nREPL brought in. Running just a Socket REPL is nicer. And of course we can start any process up with a Socket REPL without needing any code inside the process.

borkdude16:09:23

I did that once for a Scala project where I added clojure as a dependency 😉

✔️ 2
borkdude16:09:44

it wasn’t a serious project though, but it worked 😉

seancorfield16:09:23

We have a REPL running inside our legacy CFML app so I won't judge 🙂

slipset17:09:23

@ikitommi nice! I’ll have a look at the data specs.

arohner19:09:03

s/keys supports and in :req, but not :opt, right? Is there any other way to express “:foo is optional, but if present, :bar must also be present”?

misha19:09:22

s/multispec, s/or

misha19:09:46

btw, the exact behavior of or and and inside s/keys is still a mystery to me. is there something official to read about it, @alexmiller? or is it all unintended side effect, which should not be relied upon?

seancorfield19:09:13

@arohner That's usually when I reach for s/and wrapped around s/keys and a predicate for the present/not-present relationships...

arohner19:09:56

@seancorfield thanks. The combinations here get a little gnarly to multi-spec, so I guess I’ll punt

arohner19:09:06

@misha it’s documented in s/keys docstring:

The :req key vector supports 'and' and 'or' for key groups: