This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-01-18
Channels
- # aleph (1)
- # announcements (2)
- # aws (4)
- # beginners (73)
- # boot (2)
- # boot-dev (3)
- # cider (6)
- # cljs-dev (40)
- # clojure (64)
- # clojure-austin (2)
- # clojure-belgium (1)
- # clojure-dev (25)
- # clojure-estonia (1)
- # clojure-europe (16)
- # clojure-italy (11)
- # clojure-nl (4)
- # clojure-spec (90)
- # clojure-sweden (2)
- # clojure-uk (105)
- # clojurescript (58)
- # core-async (10)
- # cursive (23)
- # data-science (1)
- # datascript (3)
- # datomic (14)
- # duct (11)
- # fulcro (48)
- # graphql (1)
- # hyperfiddle (3)
- # kaocha (95)
- # liberator (1)
- # lumo (6)
- # nrepl (1)
- # off-topic (14)
- # onyx (2)
- # overtone (8)
- # portkey (3)
- # re-frame (31)
- # reagent (6)
- # shadow-cljs (185)
- # sql (12)
- # tools-deps (6)
- # vim (6)
- # yada (224)
@borkdude hey, we were talk about exceptions on web page in yada before somewhere. So how do you deal with it https://clojurians.slack.com/archives/C0702A7SB/p1547229018025500 ?
this is basically what I do: https://gist.github.com/borkdude/441379ec59d8da7f72348453ee15f32a note: this code is almost 2 years old and yada might have better ways right now
You can add :show-stack-traces? true
to your yada resource map
I don’t see in this code option to turn off it for all errors. I see you use defmethod render-error "text/plain"
Or of course, :show-stack-traces? false
@malcolmsparks woo how can I know about that?
all the schema is defined in yada.schema. It's a single ns that defines all possible things you can have in a yada/resource
there should definitely be a documentation chapter explaining it all
but it's good to know where the actual source is because documentation can be out of date
if you want to do something to multiple resources at once, the standard answer is to clojure.walk/postwalk
your resources
you can add some namespaced entries in your resources if you need to filter them, etc.
@malcolmsparks we have a common resource model that we merge with overridable data per resource, that also works
yes, that too - it's data
no - everything is at a resource level - this is intentional
sometimes you want different policies for different resources
and yada discourages putting policies on the global or 'route prefix' level
This is partly to make resources self-contained (which can help when testing them individually).
It's also about making it easier to reason about resources on their own
sure I understand it, but this one I would like to see as global conf 🙂 So I will ended up with function wrapper to create resource without stacktrace like everybody 😉
@kwladyka like I said, you can make a “global conf” by using a global map with your default and then merge differences in per resource
Do you have other cool tips like https://github.com/juxt/yada/blob/master/src/yada/schema.clj for me? 🙂
I've begun to work more actively on yada lately, and I'm trying to add documentation to each feature as I commit. But I do have some outstanding documentation to author.
One good general tip is to read the source code.
Once you understand how yada works it's not that frightening. The overall design is quite simple once you understand it - it's a series of functions that are called in a sequence, acting on a single map (called the yada context)
once the context is fully built, the response is ready to be returned
The trick is that there is enough data in the yada context to figure out things like content negotiation, conditional responses and so on.
agree, as a feedback (please think about it positive! I have good intention!) in my feeling doc is too long without right value. It could be 20% of that length with the same value or better. I mean there is a lot of content which doesn’t give me any improvement.
thanks for the feedback
we are working on some Edge tutorials which may be easier to follow
@malcolmsparks have you thought about swapping bidi for e.g. reitit to make it work with spec?
@borkdude I have looked at reitit and really like it - however, it does have some constraints such as segmenting on /
only.
There is some integration between bidi and yada to makes URI forming 'easier' which I dislike
@borkdude Why reitit use [reitit.coercion.spec]
instead of core one? Sorry I am asking, but I don’t know this library. Is it possible to use it with [clojure.spec.alpha :as s]
?
@borkdude there are alternative ways of doing bidi url forming without going via the yada ctx, and I think the use of the yada ctx is a bit of an anti-pattern. I think bidi and yada should work more independently. I don't think there's any reason today why yada and reitit can't work well together. It's not really a case of 'swapping' bidi for reitit, just to support both and give the user the choice.
Edge could have some examples where reitit is used instead of bidi.
I was thinking of swapping out schema for clojure.spec when clojure.spec first came out. But I have since reconsidered. Schema plays 2 roles in yada - the first is by checking the data given to yada resources. The second is for parameters and Swagger. I don't think clojure.spec is a good choice for either.
We could take schema out of yada, but then you'd lose the shorthands and they're quite handy. Not having coercions would make yada resources quite a bit more verbose.
I do think that the use of schema in yada parameters is a bit of a problem. I would prefer schema just be an internal thing really.
For parameters, I think that parameter validation (to produce 400 errors) is really a separate concern and not one that yada should be involved with. The declaration of parameters isn't part of the HTTP standards and was really driven by Swagger. I used to think it was a big advantage to have Swagger 'built-in' but now I think it would be fine if all the Swagger stuff were available as an extension and not part of yada core. That would reduce the dependencies and overall size of yada a lot.
(I still think parameters validation is important, but something where there should be more options rather than 'built-in')
@malcolmsparks > I don’t think clojure.spec is a good choice for either. Why?
1. https://stackoverflow.com/questions/45188850/is-use-of-clojure-spec-for-coercion-idiomatic
2. I don't think clojure.spec is designed for validation of web forms - there are more purpose-built libraries for that
Hmm I did exactly such library haha https://github.com/kwladyka/form-validator-cljs 😛
@kwladyka you should not run spec against data from the Internet until you have validated it.
But the biggest advantage is I can use spec from BE on FE and be sure everything is consistent
It's a security risk, due to not being able to constrain the registry to only safe ones
If a library contains a slow spec, for example, let's say it takes 1s per character. Then I can send a long string with that key, even though your endpoint doesn't accept it.
Additionally, if you want to spec that :password and :password-repeat are equal, you cannot put the error message on :password-repeat, only on the whole form.
While you can get reason of fail like [::form ::form-map ::password ::password-not-empty]
you can get [::form ::form-map ::password-repeat ::password-not-empty]
instead
(ns form-validator-doc.spec
(:require [cljs.spec.alpha :as s]
[clojure.test.check.generators]))
(s/def ::email (s/and string? (partial re-matches #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$")))
(s/def ::password-not-empty not-empty)
(s/def ::password-length #(<= 6 (count %)))
(s/def ::password (s/and string? ::password-not-empty ::password-length))
(s/def ::form (s/keys :req-un [::email ::password]
:opt-un [::password-repeat]))
if you create (s/def ::password-repeat ...)
you can get for example [::form ::form-map ::password-repeat ::password-not-empty]
But sure, you can do anything if you add extra validation, but then you have to put custom logic on top
In that specific example not, because I don’t even want to have :password-repeat as part of my deep logic. It is not something what I will send to BE. It is only for UI purpose.
@malcolmsparks {:show-stack-traces? false}
how can I add this to for example HTTP 500? Do I have to define all HTTP responses for that purpose?
https://github.com/juxt/yada/blob/master/src/yada/schema.clj this is very helpful
interesting fact: after change liberator to yada I removed about half dependencies and about half code
I had the same experience with an http-kit/ring based stack. I really like the batteries included of yada. I don’t mind the dependencies as long as it’s cohesive
Do you know best practice example for yada + lacinia? Something what can inspire me or boost my process of learning how to glue things
lacinia does have a lib for pedestal. I’m not sure what exactly it’s doing and I never used it in anger
I did try lacinia once I must say, but we dropped it because the GraphQL standard was too narrow for us
My goal is to make GraphQL API with option to login by web browser + by token for integrations
https://github.com/juxt/edge/blob/master/phonebook-graphql/src/edge/phonebook/graphql.clj
Generally when join to project this part is already done. But now I start something from 0 and I have to decide about everything. It is biggest challenge, than I thought 🙂
I have no good idea how to do it using only GraphQL in secure way. I need this POST REST API method to login (get token based on email and password)
Maybe it should be like that or maybe it should be moved to GraphQL mutations, but then it start to confuse me how to implement it
but there is not a lot of system which based only on GraphQL without REST API I think, so people don’t have this experience
And there are some resources like download PDF which are not GraphQL… ok it is off topic now 🙂
GraphQL is designed for fetching nested data, to prevent unnecessary data sent from the sever, right? What stops you from making extra REST points together with it, that do not have that problem?
yes, it could be done, but still there is a lot of decision to make. Like for example about auth.
For example I started with sessions in Redis, but now I change concept to not use sessions at all
How to run yada to use easy parameters from POST?
curl -vs -H "Accept: application/json" --data 'query="{ game_by_id(id: "1237") { name designers { name }}}"' -X POST -i
<- for example I want to read query
parameter form POST method.
It's just much more convenient to do authentication outside of graphql because of all the web standards like cookies
Different topic: how do you make POST request (yada/response-for authentication :post "/authentication" {:body "{\"email\": \"
? I am trying to use response-for
but I can’t guess the right way 🙂
{:status 400,
:headers {"content-length" "67", "content-type" "text/plain;charset=utf-8"},
:body "No body present but body is expected for request.\r\n\r\n{:status 400}\n"}
curl --data '[email protected]&password=qwaszx' -X POST -i Fri Jan 18 23:08:35 2019
HTTP/1.1 400 Bad Request
Content-Length: 129
Content-Type: text/plain;charset=utf-8
Server: Aleph/0.4.4
Connection: Keep-Alive
Date: Fri, 18 Jan 2019 22:09:11 GMT
Bad form fields
{:status 400,
:error
{:email missing-required-key,
:password missing-required-key,
nil disallowed-key}}
Also this issue. I assume I have to have ring middleware to make :parameteres
work? But on the other hand it will be odd to add extra ring middleware on default yada things.Seriously I have no idea how to use POST in yada after a few hours of trying 😕 I will appreciate example of code.
there is no example in tests https://github.com/juxt/yada/blob/ba10db43e1a332f0bfd352cd8ea39f176190c4ff/test/yada/request_for_test.clj
And for some reason it doesn’t work also with curl
:parameters {:form {:email String
:password String}}
I was trying with :form
and :body
. It just doesn’ work.
{:status 400,
:error
{:email missing-required-key,
:password missing-required-key,
nil disallowed-key}}
@kwladyka I have this in my tests:
(defn request [method url clj-body]
{:method method
:content-type "application/json"
:accept "application/json"
:as :json
:url url
:body (cheshire/encode clj-body)
:throw-exceptions false})
(defmacro with-server
"Runs resource in server and defines url for use in body"
[url-bind resource & body]
`(with-level :level/off "yada.handler"
(let [resource# ~resource
vmodel# (vhosts-model [:* ["/api/foo" resource#]])
listener# (y/listener vmodel#)
port# (:port listener#)
close# (:close listener#)
~url-bind (str "" port# "/api/foo")]
~@body
(close#))))
(deftest schema-validation-error-test
(er/define-error-renderers true)
(with-server url
(y/resource {:methods
{:post {:parameters {:body {:foo s/Str}}
:response (fn [ctx] {:a 1})
:produces #{"application/json"}
:consumes #{"application/json"}}}})
(let [response (client/request
(request :post url {}))
body (cheshire/decode (str (:body response)) true)]
(is (= "missing-required-key" (-> body :error :foo)))
(is (= "Schema validation error" (-> body :message))))))
Do you have any idea why https://clojurians.slack.com/archives/C0702A7SB/p1547849446131300 ?
I don’t use response-for, because it has some limitations, I can’t remember which, maybe to do with not all interceptors being present
it’s been 2 years since I wrote this code. This code only tests some error logic. Most of my other API tests I run completely outside of yada, as integration tests
the with-server macro starts a real aleph server, so it’s more like what you would normally see
oh, I committed it to yada itself too: https://github.com/juxt/yada/blob/ba10db43e1a332f0bfd352cd8ea39f176190c4ff/src/yada/test.clj#L40
you can see its use here: https://github.com/juxt/yada/blob/ba10db43e1a332f0bfd352cd8ea39f176190c4ff/test/yada/classpath_resource_test.clj#L49
(def authentication
(build-resource
{:id :authentication
:consumes #{"application/x-www-form-urlencoded"}
; :access-control
:methods {:post {:produces "application/json"
:parameters {:form {:email String
:password String}}
:response (fn [{:keys [parameters] :as ctx}]
(println (pr-str ctx))
(println (pr-str parameters))
;(println (pr-str form))
(let [{:keys [email password]} (:body parameters)]
{:token "foo"
:email email
:password password}))}}}))
curl -d '[email protected]&password=qwaszx' -X POST -i
HTTP/1.1 400 Bad Request
Content-Length: 129
Content-Type: text/plain;charset=utf-8
Server: Aleph/0.4.4
Connection: Keep-Alive
Date: Fri, 18 Jan 2019 23:11:04 GMT
Bad form fields
{:status 400,
:error
{:email missing-required-key,
:password missing-required-key,
nil disallowed-key}}
oh I see I have to have parameters in map to have them in ctx
. So for POST it has to be different way than :form
and :body
hmm
this is super weird:
curl -H "Content-Type: application/x-www-form-urlencoded" -X POST -i Sat Jan 19 00:35:45 2019
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 45
Content-Type: application/json
Server: Aleph/0.4.4
Connection: Keep-Alive
Date: Fri, 18 Jan 2019 23:36:06 GMT
{"token":"foo","email":null,"password":null}
curl -d '[email protected]&password=qwaszx' -X POST -i Sat Jan 19 00:36:06 2019
HTTP/1.1 400 Bad Request
Content-Length: 129
Content-Type: text/plain;charset=utf-8
Server: Aleph/0.4.4
Connection: Keep-Alive
Date: Fri, 18 Jan 2019 23:36:56 GMT
Bad form fields
{:status 400,
:error
{:email missing-required-key,
:password missing-required-key,
nil disallowed-key}}
So when I send empty POST I don’t have any error, but when I send this fields I have error these keys are requiredit could be that yada doesn’t coerce the body against the schema in that content-type
also, I can’t tell what your build-resource function returns. I’m off to bed. good luck 🙂
#yada.resource.Resource{:id :authentication,
:consumes [{:media-type #yada.media_type.MediaTypeMap{:name "application/x-www-form-urlencoded",
:type "application",
:subtype "x-www-form-urlencoded",
:parameters {},
:quality 1.0}}
{:media-type #yada.media_type.MediaTypeMap{:name "application/json",
:type "application",
:subtype "json",
:parameters {},
:quality 1.0}}
{:media-type #yada.media_type.MediaTypeMap{:name "application/edn",
:type "application",
:subtype "edn",
:parameters {},
:quality 1.0}}],
:methods {:post {:response #object[api.core$eval40522$fn__40524
0x41e5148f
"api.core$eval40522$fn__40524@41e5148f"],
:produces [{:media-type #yada.media_type.MediaTypeMap{:name "application/json",
:type "application",
:subtype "json",
:parameters {},
:quality 1.0}}],
:parameters {:form {:email java.lang.String, :password java.lang.String}}}},
:show-stack-traces? false}