Fork me on GitHub
Ricardo Cabral08:12:34

Hello all, I hoe you are doing good and safe. I am a newbie in Clojure and I am trying to learn about spec. I am using spec-alpha2 and I am facing an issue trying to generate a contact as you can see in the code below. The error is Couldn’t satisfy such-that predicate after 100 tries. when I try to generate a sample of an email using regex. Is it possible to do like this?

(def email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$")
(s/def ::firstname string?)
(s/def ::lastname string?)

(s/def ::email  (s/and string? #(re-matches email-regex %)))

(s/def ::contact (s/schema [::firstname, ::lastname, ::email]))
(def contact-test  {:firstname "Ricardo" :lastname "Cabral" :email "" :extra 1})

(s/valid? ::contact contact-test)
(gen/generate (s/gen ::contact)). ;;error here
(gen/sample (s/gen ::contact)) ;; error here as well


I'm no spec expert, but my understanding is that s/and uses the first spec (in this case string?) to generate values and then checks that the value matches the rest of the conditions (in this case the regex). Because string? generates random strings, it is unlikely that it matches the very specific email regex. After generating several random strings, spec gives up as it cannot create a random string which satisfies the email regex.


possible solutions: a) use a constant set of sample email addresses and use s/with-gen to pick one of the samples, b) read about test.check and the tools it provides to build custom generators and try to build an email address generator by hand, c) check out libraries like lambdaisland/regal, which provide facilities to create generators for regex

Ricardo Cabral09:12:01

Thank you @UC506CB5W for your quick answer. I will check that

Alex Miller (Clojure team)14:12:36

You may have some trouble with this as spec2 is a work in progress and with-gen in particular may have some issues. I would recommend using spec 1 for now

Ricardo Cabral17:12:45

Thank you @U064X3EF3 so far I am not facing any big issue. I am just trying some small experiments to understand how spec2 works and also learn about the idea behind it, and so far it is working as expected

Ricardo Cabral17:12:51

(def email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$")

(s/def ::email-type (s/and string? #(re-matches email-regex %) ))

(defn- email-generator
   "Generates sample emails" []
	 (gen/fmap #(str % "@" % ".com") (gen/string-alphanumeric))

		(s/def ::sample-email (s/with-gen ::email-type email-generator))
     (defn generate-contact-sample
		"Generate sample contact to test"
		  {:firstname (gen/generate (gen/string))
		   :lastname (gen/generate (gen/string))
			:email (gen/generate (email-generator))}


(s/def ::firstname string?)
(s/def ::lastname string?)
(s/def ::email ::email-type )

(s/def ::contact (s/schema [::firstname ::lastname ::email]))

(s/valid? ::contact (generate-contact-sample))