Fork me on GitHub
#clojure-spec
<
2019-05-03
>
Nolan01:05:32

just trying out alpha2 for the first time and finding (s/or ...) to be StackOverflowErroring on everything i try. seems like im missing something pretty big but havent been able to piece it together—do the regex macros need to be used with s/spec*?

Nolan01:05:53

ah, seems like its nrepl related…

Nolan01:05:58

seems to have resolved itself, what a baffling experience indeed.

Nolan02:05:52

is there a concise way of expressing something similar to s/or where data that conforms to multiple predicates returns all of the matches instead of just the first? something similar to:

(s/def ::example (s/... :e even? :s #(< % 42))
(s/conform ::example 2) ;; => ([:e 2] [:s 2])

alexmiller02:05:52

no, not really

Nolan02:05:04

good to know. thank you alex!

jumar12:05:58

I'm not sure how to approach this problem... I have multiple implementations of an "auth provider" (e.g. db, ldap, etc.) and they are represented as a namespaced map like this:

#:auth-provider{:active? true
                                     :type "ldap"
                                     :config {:port 636
                                              :host ""
                                              :bind-dn-format "uid={username},cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org"
                                              :search-base "dc=demo1,dc=freeipa,dc=org"
                                              :connection-timeout 10000
                                              :response-timeout 10000
                                              :ssl? true}}
The problem here is the :config key. It's widely different across different implementations and I'd like to have different specs for that. My problem is that AFAIK you can have only one spec for such a key - in my case that's fairly generic:
(s/def :auth-provider/config (s/map-of keyword? any?))
(s/def ::auth-provider-spec (s/keys :req [:auth-provider/type
                                          :auth-provider/active?]
                                    :opt [:auth-provider/id
                                          :auth-provider/default-role
                                          :auth-provider/config
                                          :auth-provider/priority-order
                                          :auth-provider/role-mapping]))
Now I want to "override" spec for the :auth-provider/config key for each implemantation - currently I use this hacky approach:
(s/def ::ldap-config (s/keys :req-un [::host ::port ::connection-timeout ::response-timeout]
                             :opt-un [::bind-dn-format ::search-base]))

(s/def ::ldap-provider-spec (s/and
                             ::auth-specs/auth-provider-spec
                             ;; this is less descriptive than using `s/keys` directly
                             ;; but we cannot use `s/keys` because `:auth-provider/config` default
                             ;; spec is already registered in auth-specs namespace
                             ;; and you cannot have two different specs for the same namespaced key (i.e. `:auth-provider/config`)
                             #(s/valid? ::ldap-config (:auth-provider/config %))))
But that's not only awkward it's also problematic from the "error reporting" point of view - in case something inside the :config key is invalid I get pretty useless error message that the spec failed but I don't know why:
... Reason: Invalid LDAP configuration: -- Spec failed ---------
... Relevant specs ------- :auth.providers.ldap-provider/ldap-provider-spec: (clojure.spec.alpha/merge :auth.specs/auth-provider-spec (clojure.core/fn [%] (clojure.spec.alpha/valid? :auth.providers.ldap-provider/ldap-config (:auth-provider/config %)))) ------------------------- Detected 1 error

jumar12:05:28

I tried merge and multi-spec (might not know how it should be used properly) but failed basically for the same reason - cannot define different specs for the key :auth-provider/config

manutter5113:05:03

Wonder if you could do something with the namespaces on the key? Like, instead of :auth-provider/config for all the variants, have :auth-provider-ldap/config, :auth-provider-db/config, etc.

jumar13:05:29

That's not an option I'm afraid.

alexmiller13:05:36

You’re in the ballpark of s/multi-spec - have you tried that?

jumar13:05:17

As commented in the reply, I got stuck basically for the same reason (not able to define different specs for the same namespaced-key) - I might be doing it wrong, though.

jumar13:05:43

roughly something like this:

(defmulti auth-specs/provider-type :auth-provider/type)

(defmethod auth-specs/provider-type "ldap" [_]
  (s/keys :req [:auth-provider/config]))

jumar13:05:33

Now I don't know how to define different versions of :auth-provider/config...

alexmiller13:05:25

one way would be to do this one level up - the map containing :auth-provider/config

alexmiller13:05:16

or I find it's always helpful to come back to the truth of the matter - what values can a key take on? here :auth-provider/config has multiple sets of things

alexmiller13:05:32

so spec it as s/or of different s/keys specs

alexmiller13:05:56

(some of which might reuse the same attributes)

jumar13:05:20

idea with s/or is interestig - I'll try that. Thanks!

denik18:05:29

it looks like unform is not aware of nonconforming or am I doing sth wrong?

(s/def ::bar 
  (s/or :baz integer?))

(s/def ::foo
  (s/nonconforming   ; <========
   (s/keys :req-un [::bar])))

(s/def ::tez 
  (s/tuple any? ::foo))

(s/conform ::tez [123 {:bar 4}])
; [123 {:bar 4}]
(s/unform ::tez [123 {:bar 4}])
; Error: nth not supported on this type function Number() { [native code] }
;    at Function.

denik18:05:22

smaller example

(s/def ::bar 
  (s/or :baz integer?))

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

(s/conform ::foo {:bar 4})
(s/unform ::foo {:bar 4})

alexmiller22:05:48

those both roundtrip fine for me w/o error

alexmiller22:05:02

are you on cljs? or clj?

denik17:05:50

just tested and can't reproduce in CLJ.