Search code examples
clojureclojure.spec

How do I make custom clojure.spec generator?


I'm trying to spec the following data structure called Connection:

{:id "some string" :channel "instance of org.httpkit.server.AsyncChannel" }

Here is my spec:

(defn make-channel []
  (proxy [AsyncChannel] [nil nil]
    (toString [] "mock AsyncChannel")))

(defn channel-gen
  []
  (->> (s/gen (s/int-in 0 1))
       (gen/fmap (fn [_] (make-channel)))))

(s/def ::channel (s/spec (::channel-type)
                         :gen channel-gen))

(s/def ::id string?)

(s/def ::connection (s/keys :req-un [::channel ::id]))

(s/fdef make-connection
        :args ::channel
        :ret ::connection)

I'm getting the following error and I have no clue what is wrong here:

clojure.lang.ExceptionInfo: Unable to construct gen at: [] for: gameserve.ws$make_connection@788ffa19
clojure.lang.Compiler$CompilerException: clojure.lang.ExceptionInfo: Unable to construct gen at: [] for: gameserve.ws$make_connection@788ffa19 #:clojure.spec.alpha{:path [], :form #object[gameserve.ws$make_connection 0x788ffa19 "gameserve.ws$make_connection@788ffa19"], :failure :no-gen}

Solution

  • I can't reproduce your error, but wanted to point out a couple of things that might help you get this working.

    Your gen/fmap ignoring its parameter thing is already a thing: gen/return.

    Here you're calling a keyword with no arguments, this will throw an IllegalArgumentException. Just remove the parens around ::channel-type.

    (s/def ::channel (s/spec (::channel-type)
                             :gen channel-gen))
    

    And here you're making an args spec that talks about a single thing. :args is always a sequence of arguments, if the function takes only one argument, it's a sequence of length one. You'd normally use s/cat.

    (s/fdef make-connection
      :args ::channel
      :ret ::connection)
    

    The following works for me. (It assumes that your channel stuff is correct.)

    (ns foo.core
      (:require
       [clojure.spec.gen.alpha :as gen]
       [clojure.spec.alpha :as s]))
    
    (defn make-channel []
      :mock-channel)
    
    (defn channel-gen
      []
      (gen/return (make-channel)))
    
    (s/def ::channel-type any?)
    
    (s/def ::channel (s/spec ::channel-type
                             :gen channel-gen))
    
    (s/def ::id string?)
    
    (s/def ::connection (s/keys :req-un [::channel ::id]))
    
    (defn make-connection [c])
    
    (s/fdef make-connection
      :args (s/cat :c ::channel)
      :ret ::connection)
    
    (comment
    
      (s/exercise ::connection)
      ;;=> ([{:channel :mock-channel, :id ""} {:channel :mock-channel, :id ""}]
      ;;    [{:channel :mock-channel, :id "k"} {:channel :mock-channel, :id "k"}] ,,,)
    
      (s/exercise-fn `make-connection)
      ;;=> ([(:mock-channel) nil] [(:mock-channel) nil] ,,,)
    
      )