reitit 2024-10-10

Hello, I'm trying to extend this example (https://github.com/metosin/reitit/blob/master/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs) to add a router to /item/:id. So, I add this to the routes: [`"item/:id"` {:name ::item :view views/item-page :parameters {:path {:id int?} :query {(ds/opt :foo) keyword?}}}] views/item-page: (defn item-page [match] (js/console.log match) ;; <--- undefined (let [{:keys [path query]} (:parameters match) {:keys [id]} path] [:div [:h2 "Selected item " id] (if (:foo query) [:p "Optional foo query param: " (:foo query)])])) And the link: [:a {:href (href ::item {:id 10})} "Link to item 10"] But when I click on the link, the match is undefined. Do you have any tips here? Note: I tried with https://github.com/metosin/reitit/blob/master/examples/frontend/src/frontend/core.cljs and works as expected, but cannot make the same using the re-frame example.

The parameters your view fn gets depend on your main component, are you passing in the match value there?

but you can subscribe to the match in the component instead

(let [match @(re-frame/subscribe [current-route])] ...)

you can modify the code to also pass in match on the re-frame version

It works, I was really missing the match value to the view. But I will also try it with a subscribe. Thanks!

I guess I am doing something wrong, But I cannot get (s/or :success ::success :error ::error) working with a coercion. Here are the sample specs:

(s/def ::type string?)
(s/def ::message string?)

(s/def ::error (s/keys :req-un [::type
                                ::message]))

(s/def ::collection (s/coll-of map?))

(s/def ::success (s/keys :req-un [::collection]))

(s/def ::callback (s/or :error (s/keys :req-un [::error])
                        :success ::success))
And my route looks like this:
["/test"
   {:post {:parameters {:body ::callback}
           :responses {200 {:body map?}}
           :handler identity}}]
When I do this, then they succeed:
(comment
  (->> (slurp "contrib/success.json")
       <-json
       (s/conform ::callback))

  (->> (slurp "contrib/error.json")
       <-json
       (s/conform ::callback)))
But when I try to do that through reitit with either success.json or error.json, then it fails.

I guess I have some time to look at this right now, just a moment

🙏 1

I’ve created a repro repository, but the case is exactly the same as described in #494

the problem seems to be st/strip-extra-keys-transformer (which reitit.coercion.spec uses)

(is (= {:b []} (st/coerce ::subject/body {:b []} st/json-transformer))) ;;fails
  (is (= {:b []} (st/coerce ::subject/body {:b []} st/strip-extra-keys-transformer))) ;;passes

That is what I was also thinking, when I was trying to debug this yesterday late evening

a workaround seems to be to use st/spec to wrap the child schemas

ouch!

didn't get it to work yet tho

ah yes the st/spec needs to be inside the s/or

this seems to work for me:

(s/def ::a string?)
(s/def ::A (s/keys :req-un [::a]))

(s/def ::b vector?)
(s/def ::B (s/keys :req-un [::b]))

(s/def ::body (s/or :foo (st/spec ::A) :bar (st/spec ::B)))

(def app
  (ring/ring-handler
   (ring/router
    [["/test" {:post {:coercion   reitit.coercion.spec/coercion
                      :parameters {:body ::body}

That is exactly what I am trying ATM

Yes, that seems to solve my original issue as well

that's great news, but also horrible

👆 1

Oh gosh! 🤦‍♂️ I’ve just realized that I ran into this this January.

I even referenced this error in one of our commits: https://github.com/metosin/spec-tools/issues/212

yeah but that issue gives you the impression that the problem has been fixed

Yeah, but it isn’t

Could be a regression though

all the tests added by #219 still pass tho

But interestingly the repro case described in https://github.com/metosin/reitit/issues/494 fails today as well

what a mess of issues...

😅 1

what I'm going to do next is document the current state of the issue a bit better and add a lot of crosslinks to existing issues

meanwhile, I recommend not using spec for coercion, it's not meant for that, and trying to force it to do that is just a world of pain (as we've discovered in spec-tools)

Looking at the issues, it seems to me that the root cause wasn’t fixed

Well, I am using reitit w/ clojure.spec the way it is documented

yes, I know. that was just my personal recommendation 😇

I realise it might be impossible to refactor an existing codebase

I can live with the workaround for now, but I’ll keep an eye on this error and the one that you are about to report

btw, another workaround might be to disable the strip-extra-keys-transformer by overriding the transformer that the reitit coercion uses

I was trying that yesterday

nil-ing out the transformer

But that did not help

you probably want to still have st/json-transformer in there

(def coercion
  (-> reitit.coercion.spec/default-options
      ;; (assoc-in [:transformers :body :default] nil)
      (assoc-in [:transformers :body :default] st/fail-on-extra-keys-transformer)
      ;; (hashmap/dissoc-in [:transformers :body :default])
      reitit.coercion.spec/create))

as you can see I did multiple tries here

hmph. there might be another problem along the way then.

Yeah, that’s why I have the feeling that the root cause lies somewhere deep

IMHO, the root cause is spec-tools. It shouldn't exist 😅

Haha 😄

I've fixed some bugs in there and it just feels like whack-a-mole

🫣 1

Anyhow, thank you very much for your help!

Thank you very much!

I'm afraid I won't be looking further into this right now, but I'll bump it in our open source backlog...

🙏 1

Oh! I hope it is not related to this bug: https://clojure.atlassian.net/browse/CLJ-2079

funnily enough, we have a similar bug with malli, it seems! https://github.com/metosin/malli/issues/1112

Now I’m really excited to see what’s underneath it all.

Oh sweet baby jesus, the st/spec workaround messes up the Swagger doc generation

java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Keyword
	at clojure.lang.RT.seqFrom(RT.java:577)
	at clojure.lang.RT.seq(RT.java:557)
	at clojure.core$seq__5486.invokeStatic(core.clj:139)
	at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:24)
	at clojure.core.protocols$fn__8252.invokeStatic(protocols.clj:74)
	at clojure.core.protocols$fn__8252.invoke(protocols.clj:74)
	at clojure.core.protocols$fn__8203$G__8198__8216.invoke(protocols.clj:13)
	at clojure.core$reduce.invokeStatic(core.clj:6965)
	at clojure.core$reduce.invoke(core.clj:6947)
	at spec_tools.impl$unlift_keys.invokeStatic(impl.cljc:166)
	at spec_tools.impl$unlift_keys.invoke(impl.cljc:165)
	at spec_tools.openapi.core$eval46249$fn__46250.invoke(core.cljc:34)
...

I am out of luck here

I could either have a spec with st/spec for input validation and :no-doc true to fix the problem above, or the coercion is not working

aww that sucks

if you have the energy, try openapi docs instead of swagger. openapi has native support for s/or (anyOf in json schema speak), so it should be a better fit!

I believe that was failing as well, I just referred that as Swagger

But I will check this after lunch

Yeah, both /openapi.json and /swagger.json are failing

I'll try to formulate another bug against spec-tools for the openapi/swagger behaviour later today

🙏 1

great! thank you very much!

When I try with error.json:

{
  "spec": "(spec-tools.core/spec {:spec (clojure.spec.alpha/or :error (clojure.spec.alpha/keys :req-un [:test/error]) :success :test/success), :type [:or [:map]], :leaf? true})",
  "problems": [
    {
      "path": [
        "error"
      ],
      "pred": "(clojure.core/fn [%] (clojure.core/contains? % :error))",
      "val": {},
      "via": [
        "test/callback"
      ],
      "in": []
    },
    {
      "path": [
        "success"
      ],
      "pred": "(clojure.core/fn [%] (clojure.core/contains? % :collection))",
      "val": {},
      "via": [
        "test/callback",
        "test/success"
      ],
      "in": []
    }
  ],
  "type": "reitit.coercion/request-coercion",
  "coercion": "spec",
  "value": {
    "error": {
      "type": "HTTPException",
      "message": "500: {'cause': 'Publishing error', 'info': 'Service Unavailable'}"
    }
  },
  "in": [
    "request",
    "body-params"
  ]
}

When I try with success.json:

{
  "spec": "(spec-tools.core/spec {:spec (clojure.spec.alpha/or :error (clojure.spec.alpha/keys :req-un [:test/error]) :success :test/success), :type [:or [:map]], :leaf? true})",
  "problems": [
    {
      "path": [
        "error"
      ],
      "pred": "(clojure.core/fn [%] (clojure.core/contains? % :error))",
      "val": {},
      "via": [
        "test/callback"
      ],
      "in": []
    },
    {
      "path": [
        "success"
      ],
      "pred": "(clojure.core/fn [%] (clojure.core/contains? % :collection))",
      "val": {},
      "via": [
        "test/callback",
        "test/success"
      ],
      "in": []
    }
  ],
  "type": "reitit.coercion/request-coercion",
  "coercion": "spec",
  "value": {
    "collection": [
      {}
    ]
  },
  "in": [
    "request",
    "body-params"
  ]
}

I’ve found https://github.com/metosin/reitit/issues/494 but this seems to be fixed

But if I take that repro case, that still fails for me

If I flip the s/or then the ::B case succeeds and the ::A case fails from the #494 repro case

just checking: are you sure you're not accidentally using an ancient version of spec-tools? that's where the fix was

but it sounds like you have a good repro, so feel free to open a new issue!

I am using 0.10.7, that should has the fix. Technically the problem is exactly the same as #494 with the exact same repro.