metosin / compojure-api

Sweet web apis with Compojure & Swagger
http://metosin.github.io/compojure-api/doc/
Eclipse Public License 1.0
1.11k stars 149 forks source link

Spec coercion fails with `Assert failed: missing spec predicate` #398

Closed metametadata closed 5 years ago

metametadata commented 5 years ago

Library Version(s)

Started appearing in 2.0.0-alpha27. Presumably it's a defect in spec-tools dep.

Problem

Library produces AssertionError if it stumbles upon the spec similar to foo-result-map in :return or :body (it's the minimal example I could find):

(s/def ::foo string?)
(s/def ::foo-result ::foo)
(s/def ::foo-result-map (s/keys :req-un [::foo-result]))

...

:return ::foo-result-map

...

:body [item ::foo-result-map]

Steps:

1) git clone https://github.com/metametadata/compojure-api-2-alpha27-bug 2) lein ring server 3) In Swagger UI try out foo-result-map or process-foo-result-map endpoint.

Actual: java.lang.AssertionError

Expected: no errors.

Stacktrace example:

ERROR Assert failed: missing spec predicate
spec
java.lang.AssertionError: Assert failed: missing spec predicate
spec
    at spec_tools.core$create_spec.invokeStatic(core.cljc:423)
    at spec_tools.core$create_spec.invoke(core.cljc:407)
    at spec_tools.core$into_spec.invokeStatic(core.cljc:476)
    at spec_tools.core$into_spec.invoke(core.cljc:472)
    at spec_tools.core$coerce.invokeStatic(core.cljc:177)
    at spec_tools.core$coerce.invoke(core.cljc:171)
    at spec_tools.core.Spec$fn__8862.invoke(core.cljc:300)
    at spec_tools.core$eval8827$fn__8829$fn__8831.invoke(core.cljc:254)
    at clojure.core$fn__8072$fn__8074.invoke(core.clj:6760)
    at clojure.core.protocols$iter_reduce.invokeStatic(protocols.clj:49)
    at clojure.core.protocols$fn__7839.invokeStatic(protocols.clj:75)
    at clojure.core.protocols$fn__7839.invoke(protocols.clj:75)
    at clojure.core.protocols$fn__7781$G__7776__7794.invoke(protocols.clj:13)
    at clojure.core$reduce.invokeStatic(core.clj:6748)
    at clojure.core$fn__8072.invokeStatic(core.clj:6750)
    at clojure.core$fn__8072.invoke(core.clj:6750)
    at clojure.core.protocols$fn__7860$G__7855__7869.invoke(protocols.clj:175)
    at clojure.core$reduce_kv.invokeStatic(core.clj:6776)
    at clojure.core$reduce_kv.invoke(core.clj:6767)
    at spec_tools.core$eval8827$fn__8829.invoke(core.cljc:251)
    at clojure.lang.MultiFn.invoke(MultiFn.java:243)
    at spec_tools.core.Spec._coerce(core.cljc:300)
    at spec_tools.core$coerce.invokeStatic(core.cljc:177)
    at spec_tools.core$coerce.invoke(core.cljc:171)
    at spec_tools.core$coerce.invokeStatic(core.cljc:175)
    at spec_tools.core$coerce.invoke(core.cljc:171)
    at compojure.api.coercion.spec.SpecCoercion.coerce_request(spec.clj:124)
    at compojure.api.coercion.spec.SpecCoercion.coerce_response(spec.clj:140)
    at compojure.api.coercion$coerce_response_BANG_.invokeStatic(coercion.clj:69)
    at compojure.api.coercion$coerce_response_BANG_.invoke(coercion.clj:59)
    at compojure.api.coercion$wrap_coerce_response$fn__9974.invoke(coercion.clj:95)
    at compojure.core$pre_init$fn__3971$fn__3974.invoke(core.clj:335)
    at compojure.core$wrap_route_middleware$fn__3854.invoke(core.clj:127)
    at compojure.core$wrap_route_info$fn__3859.invoke(core.clj:137)
    at compojure.core$wrap_route_matches$fn__3863.invoke(core.clj:146)
    at compojure.core$wrap_routes$fn__3981.invoke(core.clj:348)
    at compojure.api.routes.Route.invoke(routes.clj:90)
    at compojure.core$routing$fn__3878.invoke(core.clj:185)
    at clojure.core$some.invokeStatic(core.clj:2693)
    at clojure.core$some.invoke(core.clj:2684)
    at compojure.core$routing.invokeStatic(core.clj:185)
    at compojure.core$routing.doInvoke(core.clj:182)
    at clojure.lang.RestFn.applyTo(RestFn.java:139)
    at clojure.core$apply.invokeStatic(core.clj:659)
    at clojure.core$apply.invoke(core.clj:652)
    at compojure.core$routes$fn__3882.invoke(core.clj:192)
    at compojure.api.middleware$wrap_coercion$fn__12207.invoke(middleware.clj:107)
    at compojure.api.compojure_compat$make_context$handler__14143.invoke(compojure_compat.clj:26)
    at compojure.api.compojure_compat$make_context$fn__14145.invoke(compojure_compat.clj:34)
    at compojure.api.routes.Route.invoke(routes.clj:90)
    at compojure.core$routing$fn__3878.invoke(core.clj:185)
    at clojure.core$some.invokeStatic(core.clj:2693)
    at clojure.core$some.invoke(core.clj:2684)
    at compojure.core$routing.invokeStatic(core.clj:185)
    at compojure.core$routing.doInvoke(core.clj:182)
    at clojure.lang.RestFn.applyTo(RestFn.java:139)
    at clojure.core$apply.invokeStatic(core.clj:659)
    at clojure.core$apply.invoke(core.clj:652)
    at compojure.core$routes$fn__3882.invoke(core.clj:192)
    at compojure.api.routes.Route.invoke(routes.clj:90)
    at compojure.api.middleware$wrap_swagger_data$fn__12226.invoke(middleware.clj:190)
    at compojure.api.middleware$wrap_inject_data$fn__12204.invoke(middleware.clj:96)
    at muuntaja.middleware$wrap_params$fn__11202.invoke(middleware.clj:52)
    at compojure.api.middleware$wrap_exceptions$fn__12193.invoke(middleware.clj:62)
    at muuntaja.middleware$wrap_format_request$fn__11214.invoke(middleware.clj:114)
    at compojure.api.middleware$wrap_exceptions$fn__12193.invoke(middleware.clj:62)
    at muuntaja.middleware$wrap_format_response$fn__11218.invoke(middleware.clj:132)
    at muuntaja.middleware$wrap_format_negotiate$fn__11211.invoke(middleware.clj:96)
    at ring.middleware.keyword_params$wrap_keyword_params$fn__10016.invoke(keyword_params.clj:36)
    at ring.middleware.nested_params$wrap_nested_params$fn__10074.invoke(nested_params.clj:89)
    at ring.middleware.params$wrap_params$fn__4110.invoke(params.clj:67)
    at compojure.api.middleware$wrap_inject_data$fn__12204.invoke(middleware.clj:96)
    at compojure.api.routes.Route.invoke(routes.clj:90)
    at clojure.lang.Var.invoke(Var.java:381)
    at ring.middleware.reload$wrap_reload$fn__1831.invoke(reload.clj:39)
    at ring.middleware.stacktrace$wrap_stacktrace_log$fn__1213.invoke(stacktrace.clj:26)
    at ring.middleware.stacktrace$wrap_stacktrace_web$fn__1279.invoke(stacktrace.clj:96)
    at ring.adapter.jetty$proxy_handler$fn__488.invoke(jetty.clj:25)
    at ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a.handle(Unknown Source)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:499)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:258)
    at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
    at java.lang.Thread.run(Thread.java:748)
ezeaguerre commented 5 years ago

The same happens here, went back to alpha26.

danielcompton commented 5 years ago

I'm also running into this issue. I haven't been able to look into it too deeply yet, but the last succeeding versions I can find is [compojure-api "2.0.0-alpha26"] and [metosin/spec-tools "0.8.0"] (haven't tried higher spec-tools versions). Updating to alpha 27 triggers this issue. That makes me think that the issue is not entirely on the spec-tools side, though both libraries deserve more investigation, as it could be the new version of compojure-api is triggering the new problem in spec-tools.

It look like this part of the diff is where the issue is coming from:

https://github.com/metosin/compojure-api/compare/2.0.0-alpha26...2.0.0-alpha27 R122 (GitHub breaks the link if I include the line number). https://github.com/metosin/compojure-api/commit/67721ef774dde0372a94b72d6e3d6ced884c5598#diff-bb049ce0a1f1064262c0f0ca760fea86R124

danielcompton commented 5 years ago

I did some more digging with the debugger and I think I've isolated the issue a little bit further. It seems to be that https://github.com/metosin/compojure-api/blob/2.0.0-alpha27/src/compojure/api/coercion/spec.clj#L83 is returning nil when given a Spec. However I'm uncertain if I'm interpreting the debugger correctly, it seemed a little bit flaky.

ikitommi commented 5 years ago

Hi. Finally had time to check this. It seems to be fixed in https://github.com/metosin/spec-tools/commit/e6041cbcc88281900765c3798d67a29f56511964 and in [metosin/compojure-api "2.0.0-alpha28"] (on Nov 19). Tested the app, all routes work.

mgrbyte commented 5 years ago

I'm running into this with:

[metosin/compojure-api "2.0.0-alpha28"] 
[metosin/spec-tools "0.8.2"]

The spec is of the form:

(stc/spec (s/or :x :x 
                       :y: :y 
                       :z :z))

where :x, :y and :z are map specs.

Works with:

[metosin/compojure-api "2.0.0-alpha26"] 
[metosin/spec-tools "0.7.1"]
apostolou commented 5 years ago

FYI, I was able to reproduce this bug in my project using [metosin/compojure-api "2.0.0-alpha30"] dependency. As a workaround I used : instead of (s/def ::uuid uuid?) (s/def ::user-id ::uuid) (s/def ::user (s/keys :req-un [::user-id]))

you can do :

(def uuid-spec (st/spec {:spec uuid? :type :uuid})) (s/def ::uuid uuid-spec) (s/def ::user-id uuid-spec) (s/def ::user (s/keys :req-un [::user-id]))

Thanks, Michel