This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-10-10
Channels
- # babashka (37)
- # babashka-sci-dev (22)
- # beginners (16)
- # biff (12)
- # calva (40)
- # cider (6)
- # clj-kondo (7)
- # clojure (183)
- # clojure-austin (20)
- # clojure-doc (22)
- # clojure-europe (16)
- # clojure-nl (2)
- # clojure-norway (39)
- # clojure-romania (1)
- # clojure-uk (9)
- # clojuredesign-podcast (9)
- # clojurescript (29)
- # core-typed (66)
- # cursive (19)
- # data-science (14)
- # docker (5)
- # fulcro (6)
- # hyperfiddle (46)
- # java (5)
- # malli (19)
- # missionary (3)
- # off-topic (84)
- # pedestal (5)
- # portal (36)
- # reitit (35)
- # releases (2)
- # shadow-cljs (30)
- # web-security (2)
- # yamlscript (1)
Hey all, I'm trying to use the required
parameter as described here https://swagger.io/docs/specification/describing-parameters/ and seen here
parameters:
- in: query
name: offset
schema:
type: integer
minimum: 0
default: 0
required: false
and my configuration is
(st/schema
s/Str
{:name "Date to"
:description "TODO"
:openapi/example "2023-06-30"
:required false})
trying this does not work too
(st/schema
s/Str
{:name "Date to"
:description "TODO"
:openapi/example "2023-06-30"
:openapi/required false})
How can I use that parameter if it's available?Figured it out, it looks like you have to use some form of optional
from the coercion library you are using, such as (s/opitonal-key)
from Schema.
@U055NJ5CC I think this is worth documenting, should I attempt to make a PR for it?
❓ I'm trying to generate OpenAPI spec (using 0.7.0-alpha7) for this Clojure Spec:
(s/def ::not-blank-string
(s/and string? (complement str/blank?)))
(s/def ::foo ::not-blank-string)
(s/def ::bar ::not-blank-string)
(s/def ::quux ::not-blank-string)
(s/def ::foo-or-bar (s/or :foo (s/keys :req-un [::foo])
:bar (s/keys :req-un [::bar])))
(s/def ::merged
(s/merge ::foo-or-bar
(s/keys :req-un [::quux])))
Basically I want to require either the :foo
or the :bar
key. This appears to work in a repl, but the behaviour in Swagger UI doesn't make sense to me. (Full example in 🧵 )By "works in repl" I mean:
toy.foo> (gen/sample (s/gen ::merged))
({:foo "z", :quux "G"}
{:foo "r9", :quux "2I"}
{:bar "69", :quux "p4"}
{:bar "03", :quux "AwM"}
{:foo "J", :quux "a"}
{:foo "w1vLd", :quux "371"}
{:foo "6QGxc9", :quux "Uf5C3"}
{:foo "80", :quux "OpD98"}
{:bar "sqHR6", :quux "1"}
{:bar "s92X3yv", :quux "8CykTIik"})
toy.foo> (s/explain ::merged {:foo "234" :quux "bob"})
Success!
nil
toy.foo> (s/explain ::merged {:bar "234" :quux "bob"})
Success!
nil
toy.foo> (s/explain ::merged {:baz "234" :quux "bob"})
{:baz "234", :quux "bob"} - failed: (contains? % :foo) at: [:foo] spec: :toy.foo/foo-or-bar
{:baz "234", :quux "bob"} - failed: (contains? % :bar) at: [:bar] spec: :toy.foo/foo-or-bar
nil
This is the full example. deps.edn contains:
{:deps {expound/expound {:mvn/version "0.9.0"}
org.clojure/clojure {:mvn/version "1.11.1"}
metosin/reitit {:mvn/version "0.7.0-alpha7"}
metosin/ring-swagger-ui {:mvn/version "5.9.0"}
ring/ring-jetty-adapter {:mvn/version "1.10.0"}}}
(probably not all necessary.)In Swagger UI I get this error:
{
"spec": "(spec-tools.core/spec {:spec (clojure.spec.alpha/merge :toy.foo/foo-or-bar (clojure.spec.alpha/keys :req-un [:toy.foo/quux])), :type :map, :leaf? false})",
"problems": [
{
"path": [
"foo"
],
"pred": "(clojure.core/fn [%] (clojure.core/contains? % :foo))",
"val": {
"quux": "string"
},
"via": [
"toy.foo/merged",
"toy.foo/foo-or-bar"
],
"in": []
},
{
"path": [
"bar"
],
"pred": "(clojure.core/fn [%] (clojure.core/contains? % :bar))",
"val": {
"quux": "string"
},
"via": [
"toy.foo/merged",
"toy.foo/foo-or-bar"
],
"in": []
}
],
"type": "reitit.coercion/request-coercion",
"coercion": "spec",
"value": {
"bar": "string",
"quux": "string"
},
"in": [
"request",
"body-params"
]
}
So it looks like somehow both foo
and bar
has become required?
The OpenAPI spec does not suggest that both "foo" and "bar" are required, so I'm not sure what's going on.
This alternative spec acts like I expect:
(s/def ::quux-foo-bar
(s/keys :req-un [::quux]
:opt-un [::foo ::bar]))
... but it does not convey that one of ::foo
or ::bar
is required.could it be that the map keys aren't getting coerced to keywords properly? that's why it's not finding :bar
in the map
I haven't looked in detail into how reitit coercion works for spec, especially for s/merge
Nope, similar error:
{
"spec": "(spec-tools.core/spec {:spec (clojure.spec.alpha/or :foo (clojure.spec.alpha/keys :req-un [:toy.foo/foo]) :bar (clojure.spec.alpha/keys :req-un [:toy.foo/bar])), :type [:or [:map]], :leaf? true})",
"problems": [
{
"path": [
"foo"
],
"pred": "(clojure.core/fn [%] (clojure.core/contains? % :foo))",
"val": {},
"via": [
"toy.foo/foo-or-bar"
],
"in": []
},
{
"path": [
"bar"
],
"pred": "(clojure.core/fn [%] (clojure.core/contains? % :bar))",
"val": {},
"via": [
"toy.foo/foo-or-bar"
],
"in": []
}
],
"type": "reitit.coercion/request-coercion",
"coercion": "spec",
"value": {
"foo": "string"
},
"in": [
"request",
"body-params"
]
}
I wonder if the problem is that it's trying to evaluate the spec under the path "foo" or "bar"?
Hmm, whatever it is it seems like it's local to the Spec coercer. The following Malli schema & route works:
(def NonEmptyString [:string {:min 1}])
(def Merged
[:or
[:map
[:foo NonEmptyString]
[:baz NonEmptyString]]
[:map
[:bar NonEmptyString]
[:baz NonEmptyString]]])
...
["/baz"
{:post {:summary "Baz - Malli"
:coercion reitit.coercion.malli/coercion
:parameters {:body Merged}
:responses {200 {:body Merged}}
:handler (fn [{{merged :body} :parameters}]
(prn merged)
{:status 200
:body merged})}}]
...
... for certain values of "works". It won't actually blow up if you post both :foo and :bar, it will match one of them and strip the other key. So it hides the issue. Not quite what I want 🙂
https://github.com/metosin/reitit/blob/master/doc/coercion/malli_coercion.md#configuring-coercion
I don't want to disable the extra-key stripping. My current approach is making both optional, document that the keys are mutually exclusive, and throw a 422 error if passed both.
Is it possible to intercept a route change in reitit-fronted router and stop the routing? For example if I have a form with unsaved data and want to prevent the browser from routing away
Hi @U023LKF2PQV ! Exactly interested by this topic. Did you find any solution?
no unfortunately, not. I think, the only way is to write custom route-to functions and write an interception function for the <a> tag handler
Thanks @U023LKF2PQV for your confirmation. This is also the approach I thought of.
Hello. I'm building a web server with Ring, Reitit, and Malli. I'm currently encountering issues with the incoming data coercion. I'm going to share the issue and the code in a thread to avoid bothering.
The scenario is as follows: I have an endpoint where I send an HTTP PUT request with a subset of values from an entity. For example, I have a user entity with a name and last name, and in the HTTP call, I'm only sending the lastname with the new value to update. I have a schema defined with {:optional true} on both properties, which I use for coercion in body parameters of the endpoint route. However, when making the call to the endpoint, the server fails with a 500 error and only says "error connection closed." I understand that the issue may lie in the value coercion and the Malli configuration. The rest of the endpoints (create, read, delete) work just fine so the problem shouldn't be in the routes. For this case, the server configuration (I copied from a github metosin/reitit/examples) is as follows: ;; schema ---------------------------------------------------- (def update-user-schema (mc/schema [:map [:username {:optional true} [:string {:min 8 :max 32}]] [:password {:optional true} [:string {:min 8 :max 32}]]])) ;; handler --------------------------------------------------- (defn update-one! [{:keys [db parameters]} :as req] (try (let [id (:id (:path parameters)) body (:body parameters) original (get-one! req) ;; next.jdbc/get-by-id newone (merge original body) foo (sql/update! db :user {:username (:username newone) :password (:password newone) :updated_at (java.time.Instant/now)} {:id id} {:suffix "RETURNING *"})] {:status 200 :body foo}) (catch Exception e {:status 400 :body e}))) ;; app -------------------------------------------------------- (defn create-app [db] (ring/ring-handler (ring/router routes {:exception pretty/exception :data {:db db :coercion (malli/create {:error-keys #{#_:type :coercion :in :schema :value :errors :humanized #_:transformed} :compile mu/closed-schema :strip-extra-keys true :default-values true :options nil}) :muuntaja m/instance :middleware [swagger/swagger-feature muuntaja/format-negotiate-middleware muuntaja/format-response-middleware exception/exception-middleware muuntaja/format-request-middleware coercion/coerce-exceptions-middleware coercion/coerce-request-middleware coercion/coerce-response-middleware mw/db [cors/wrap-cors :access-control-allow-origin [#"."] :access-control-allow-origin [:get :post :put :delete]]]}}) default-routes-handler))Hello. I'm building a REST API with Ring, Reitit, and Malli. I'm currently encountering issues with the incoming data coercion. The scenario is as follows: I have an endpoint where I send an HTTP PUT request with a subset of values from an entity. For example, I have a user entity with a name and last name, and in the HTTP call, I'm only sending the lastname with the new value to update. I have a schema defined with {:optional true} on both properties, which I use for coercion in body parameters of the endpoint route. However, when making the call to the endpoint, the server fails with a 500 error and only says "error connection closed." I understand that the issue may lie in the value coercion and the Malli configuration. The rest of the endpoints (create, read, delete) work just fine so the problem shouldn't be in the routes. For this case, the server configuration (I copied from a github metosin/reitit/examples) is as follows: ;; schema ---------------------------------------------------- (def update-user-schema (mc/schema [:map [:username {:optional true} [:string {:min 8 :max 32}]] [:password {:optional true} [:string {:min 8 :max 32}]]])) ;; handler --------------------------------------------------- (defn update-one! [{:keys [db parameters]} :as req] (try (let [id (:id (:path parameters)) body (:body parameters) original (get-one! req) ;; next.jdbc/get-by-id newone (merge original body) foo (sql/update! db :user {:username (:username newone) :password (:password newone) :updated_at (java.time.Instant/now)} {:id id} {:suffix "RETURNING *"})] {:status 200 :body foo}) (catch Exception e {:status 400 :body e}))) ;; app -------------------------------------------------------- (defn create-app [db] (ring/ring-handler (ring/router routes {:exception pretty/exception :data {:db db :coercion (malli/create {:error-keys #{#_:type :coercion :in :schema :value :errors :humanized #_:transformed} :compile mu/closed-schema :strip-extra-keys true :default-values true :options nil}) :muuntaja m/instance :middleware [swagger/swagger-feature muuntaja/format-negotiate-middleware muuntaja/format-response-middleware exception/exception-middleware muuntaja/format-request-middleware coercion/coerce-exceptions-middleware coercion/coerce-request-middleware coercion/coerce-response-middleware mw/db [cors/wrap-cors :access-control-allow-origin [#"."] :access-control-allow-origin [:get :post :put :delete]]]}}) default-routes-handler))
❓ It looks like coercion from JSON is not working properly for uri?
using the Malli coercer. (But it works for the spec coercer.) Am I doing something wrong, or is this a bug? 🧵
This snippet shows the issue
["/uri-spec"
{:post {:summary "Post a URI - successfully coerced"
:coercion reitit.coercion.spec/coercion
:parameters {:body {:uri uri?}}
:responses {200 {:body {:uri uri?}}}
:handler (fn [{{{uri :uri} :body} :parameters}]
(prn uri)
{:status 200 :body {:uri uri}})}}]
["/uri-malli"
{:post {:summary "Post a URI - fails coercion"
:coercion reitit.coercion.malli/coercion
:parameters {:body {:uri uri?}}
:responses {200 {:body {:uri uri?}}}
:handler (fn [{{{uri :uri} :body} :parameters}]
(prn uri)
{:status 200 :body {:uri uri}})}}]]
The routes are the same, except for the coercion used. With the spec version the uri in the body is coerced into a java.net.URI as expected, but with the Malli coercer I get this error:
{
"value": {
"uri": " "
},
"type": "reitit.coercion/request-coercion",
"coercion": "malli",
"in": [
"request",
"body-params"
],
"humanized": {
"uri": [
"should be a uri"
]
}
}
Complete example
looks like malli doesn't have any encoders/decoders for uri?
, you might need to add them yourself
Uh, I guess not 🙂 https://github.com/metosin/malli/blob/master/src/malli/transform.cljc#L256
There's no entry for :uri in the json-decoder just above.
Not a Reitit issue, so continued in https://clojurians.slack.com/archives/CLDK6MFMK/p1697018108767659