metosin / reitit

A fast data-driven routing library for Clojure/Script
https://cljdoc.org/d/metosin/reitit/
Eclipse Public License 1.0
1.42k stars 254 forks source link

Extend -compile-model for reitit.coercsion.spec #683

Open jonasseglare opened 4 months ago

jonasseglare commented 4 months ago

This PR extends the -compile-model method in the reitit.coercion.spec namespace to work for one more special case: If the model parameter contains more than one spec but they are all equal, then the merged spec is trivially any of those specs.

Background

We encountered the following error when trying to instantiate our router:

{:message "Can't merge nested clojure specs",
 :spec [#object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"]
        #object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"]]}

So, what it means is that it fails to merge model if it is

[clojure.core/any? clojure.core/any?]

We can fix this by observing that because it is a vector with the same element being repeated many times, the merged version would trivially be that element, in this case clojure.core/any?.

See relevant part of exception with stack trace:

clojure.lang.ExceptionInfo: :reitit.coercion.spec/model-error ``` clojure.lang.ExceptionInfo: :reitit.coercion.spec/model-error {:message "Can't merge nested clojure specs", :spec [#object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"] #object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"]]} {:type :reitit.coercion.spec/model-error, :data {:message "Can't merge nested clojure specs", :spec [#object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"] #object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"]]}, :reitit.exception/cause #error { :cause ":reitit.coercion.spec/model-error" :data {:type :reitit.coercion.spec/model-error, :data {:message "Can't merge nested clojure specs", :spec [#object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"] #object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"]]}} :via [{:type clojure.lang.ExceptionInfo :message ":reitit.coercion.spec/model-error" :data {:type :reitit.coercion.spec/model-error, :data {:message "Can't merge nested clojure specs", :spec [#object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"] #object[clojure.core$any_QMARK_ 0x19105a87 "clojure.core$any_QMARK_@19105a87"]]}} :at [reitit.exception$fail_BANG_ invokeStatic "exception.cljc" 8]}] :trace [[reitit.exception$fail_BANG_ invokeStatic "exception.cljc" 8] [reitit.exception$fail_BANG_ invoke "exception.cljc" 4] [reitit.coercion.spec$create$reify__71053 _compile_model "spec.cljc" 126] [reitit.ring$_compile_coercion$fn__71903 invoke "ring.cljc" 56] [reitit.impl$_path_vals$_path_vals__68882$fn__68884 invoke "impl.cljc" 31] [clojure.core.protocols$iter_reduce invokeStatic "protocols.clj" 49] [clojure.core.protocols$fn__8230 invokeStatic "protocols.clj" 75] [clojure.core.protocols$fn__8230 invoke "protocols.clj" 75] [clojure.core.protocols$fn__8178$G__8173__8191 invoke "protocols.clj" 13] [clojure.core$reduce invokeStatic "core.clj" 6887] [clojure.core$reduce invoke "core.clj" 6869] [reitit.impl$_path_vals$_path_vals__68882 invoke "impl.cljc" 26] [reitit.impl$_path_vals$_path_vals__68882$fn__68884 invoke "impl.cljc" 32] [clojure.core.protocols$iter_reduce invokeStatic "protocols.clj" 49] [clojure.core.protocols$fn__8230 invokeStatic "protocols.clj" 75] [clojure.core.protocols$fn__8230 invoke "protocols.clj" 75] [clojure.core.protocols$fn__8178$G__8173__8191 invoke "protocols.clj" 13] [clojure.core$reduce invokeStatic "core.clj" 6887] [clojure.core$reduce invoke "core.clj" 6869] [reitit.impl$_path_vals$_path_vals__68882 invoke "impl.cljc" 26] [reitit.impl$_path_vals$_path_vals__68882$fn__68884 invoke "impl.cljc" 32] [clojure.core.protocols$iter_reduce invokeStatic "protocols.clj" 49] [clojure.core.protocols$fn__8230 invokeStatic "protocols.clj" 75] [clojure.core.protocols$fn__8230 invoke "protocols.clj" 75] [clojure.core.protocols$fn__8178$G__8173__8191 invoke "protocols.clj" 13] [clojure.core$reduce invokeStatic "core.clj" 6887] [clojure.core$reduce invoke "core.clj" 6869] [reitit.impl$_path_vals$_path_vals__68882 invoke "impl.cljc" 26] [reitit.impl$_path_vals invokeStatic "impl.cljc" 35] [reitit.impl$_path_vals invoke "impl.cljc" 24] [reitit.impl$path_update invokeStatic "impl.cljc" 41] [reitit.impl$path_update invoke "impl.cljc" 40] [reitit.ring$_compile_coercion invokeStatic "ring.cljc" 56] [reitit.ring$_compile_coercion invoke "ring.cljc" 55] [reitit.ring$compile_result$__GT_endpoint__71916 invoke "ring.cljc" 64] [reitit.ring$compile_result$fn__71923 invoke "ring.cljc" 81] [clojure.lang.PersistentArrayMap kvreduce "PersistentArrayMap.java" 429] [clojure.core$fn__8525 invokeStatic "core.clj" 6909] [clojure.core$fn__8525 invoke "core.clj" 6889] [clojure.core.protocols$fn__8257$G__8252__8266 invoke "protocols.clj" 175] [clojure.core$reduce_kv invokeStatic "core.clj" 6920] [clojure.core$reduce_kv invoke "core.clj" 6911] [reitit.ring$compile_result invokeStatic "ring.cljc" 78] [reitit.ring$compile_result invoke "ring.cljc" 58] [reitit.impl$compile_route invokeStatic "impl.cljc" 163] [reitit.impl$compile_route invoke "impl.cljc" 162] [reitit.impl$compile_routes$fn__69035 invoke "impl.cljc" 166] [clojure.core$keep$fn__8649 invoke "core.clj" 7406] [clojure.lang.LazySeq sval "LazySeq.java" 42] [clojure.lang.LazySeq seq "LazySeq.java" 51] [clojure.lang.RT seq "RT.java" 535] [clojure.core$seq__5467 invokeStatic "core.clj" 139] [clojure.core.protocols$seq_reduce invokeStatic "protocols.clj" 24] [clojure.core.protocols$fn__8236 invokeStatic "protocols.clj" 75] [clojure.core.protocols$fn__8236 invoke "protocols.clj" 75] [clojure.core.protocols$fn__8178$G__8173__8191 invoke "protocols.clj" 13] [clojure.core$reduce invokeStatic "core.clj" 6887] [clojure.core$into invokeStatic "core.clj" 6959] [clojure.core$into invoke "core.clj" 6951] [reitit.impl$compile_routes invokeStatic "impl.cljc" 166] [reitit.impl$compile_routes invoke "impl.cljc" 165] [reitit.core$router invokeStatic "core.cljc" 346] [reitit.core$router invoke "core.cljc" 317] [reitit.ring$router invokeStatic "ring.cljc" 134] [reitit.ring$router invoke "ring.cljc" 104] [jobtech_taxonomy.api.handler$app invokeStatic "handler.clj" 27] [jobtech_taxonomy.api.handler$app invoke "handler.clj" 25] [clojure.lang.AFn applyToHelper "AFn.java" 154] ```
opqdonut commented 1 month ago

This seems like a great feature! Could you please add a test case?