Fork me on GitHub
#clojure-norway
<
2023-12-19
>
cjohansen07:12:03

Det ble en liten bloggpost til før jula tar oss, denne gangen en liten praktisk sak om hvordan du kan gjøre ñ om n når du trenger det: https://parenteser.mattilsynet.io/unicode-normalisering/

🙌 3
❤️ 1
augustl08:12:29

go'mørning!

😂 2
👌 2
leifericf09:12:01

Er det slik en vanligvis legger til en ny nøkkel-verdi i et map? :thinking_face:

(let [request {:headers {"Accept" "application/foo"
                         "Content-Type" "application/json"
                         "Authorization" "token my-access-token"}
               :query-params {:organization "my-org"}}]
  (update-in request [:query-params]
             merge {:continuationToken "continuation-token"}))
Resultat:
{:headers {"Accept" "application/foo", "Content-Type" "application/json", "Authorization" "token my-access-token"},
 :query-params {:organization "my-org", :continuationToken "continuation-token"}}

cjohansen09:12:54

Nei:

(let [request {:headers {"Accept" "application/foo"
                         "Content-Type" "application/json"
                         "Authorization" "token my-access-token"}
               :query-params {:organization "my-org"}}]
  (assoc-in request [:query-params :continuationToken] "continuation-token"))

leifericf09:12:22

Hehe! Det ante meg at jeg gjorde noe tungvindt der, ja.

cjohansen09:12:33

Du trenger forøvrig ikke på bruke update-in når du bare har ett element i vektoren. Da kan du heller gjøre (update m :query-params f)

leifericf09:12:19

Typ når man ikke har en nøsta struktur med maps-i-maps?

cjohansen09:12:48

Ja, hvis du bare vil oppdatere verdien til en key rett i mappet, sånn som du gjorde over der

👍 1
cjohansen09:12:48

Altså:

(update-in request [:query-params] merge {:continuationToken "token")
Er det samme som:
(update request :query-params merge {:continuationToken "token"})

💡 1
leifericf09:12:01

Aha, jeg forvekslet begrepene "legge til" og "oppdatere," men det er vel synonymer i denne konteksten ("legge til" er også en "oppdatering" av mappet en begynner med).

🎯 1
cjohansen09:12:06

{update,assoc}-in-variantene er for gjøre ting i nøsta strukturer

cjohansen09:12:57

Flere måter å oppnå samme resultat:

(assoc-in request [:query-params :continuationToken] "token")
(update request :query-params assoc :continuationToken "token")

💡 1
leifericf09:12:53

Nice, takk! "`*-in` er for nøsta" er en fin tommelfingerregel.

leifericf09:12:37

Jeg var ikke klar over at en kunne bruke en vektor med keywords på den måten får å "rusle nedover i maps." Det er jo veldig praktisk!

cjohansen09:12:18

Hvis du ikke skal det trenger du ikke *-in

👍 1
cjohansen09:12:55

Nå gjør jo alle disse til slutt det samme, men jeg mener assoc-in-varianten er den som tydeligst kommuniserer hensikten din

cjohansen09:12:50

Disse er spesielt nyttige sammen med thread first:

(let [request {:headers {"Accept" "application/foo"
                         "Content-Type" "application/json"
                         "Authorization" "token my-access-token"}
               :query-params {:organization "my-org"}}]
  (-> request
      (assoc :method :get)
      (assoc-in [:query-params :continuationToken] "continuation-token")
      (assoc-in [:headers :lol] "lol!")))

👀 1
augustl09:12:15

kan være fint å blande update og assoc noen ganger og. Eks:

(-> thing
    (update :style assoc 
       :color "#fff" 
       :font-weight "bold"))

augustl09:12:55

(what Christian said)

cjohansen09:12:13

Ja, jeg prøvde å vise en blanding av update/update-in/assoc/assoc-in, men fantasien min sviktet meg 😂

cjohansen09:12:27

Kriminell formattering der @U0MKRS1FX!

😂 1
augustl09:12:38

haha ja sorry, sånn går det når Slack er editoren og jeg ikke har paredit

slipset09:12:26

Og sånn bare for ordensskyld. Jeg er av den klare oppfatning at https://en.wikipedia.org/wiki/Law_of_Demeter også gjelder for Clojure. Det betyr at hvis du enten gjør: (update-in x [:foo :bar :baz 0 :qix] eller begynner å synes at specter ser bra ut, så gjør du noe feil.

😅 2
🎯 1
cjohansen09:12:46

Enig! Det er mange gode use cases for update-in og assoc-in med to nivåer (som eksemplifisert over), men så faller det bratt deretter

augustl09:12:52

ah, fint at den tingen der har et navn. Jeg kaller det “skrive til nøsta data er et rødt flagg”

leifericf09:12:48

Jeg kom innpå temaet fordi jeg oppdaget at man kan bygge opp HTTP requests som rene maps på denne måten:

(ns pulumi
  (:require [cheshire.core :as json]
            [org.httpkit.client :as http-kit]))

(def base-uri "")

(def access-token "X")

(def headers
  {"Accept" "application/foo"
   "Content-Type" "application/json"
   "Authorization" (str "token " access-token)})

(defn fetch-stacks [request callback]
  (http-kit/request (assoc request
                           :url (str base-uri "/api/user/stacks")
                           :method :get)
                    callback))

(def base-request
  {:query-params {:organization "my-org"}
   :headers headers})

(defn parse-response [response]
  (-> (:body response)
      json/parse-string))

(def stacks (fetch-stacks base-request parse-response))

(get @stacks "continuationToken")

leifericf09:12:07

Og da måtte jeg finne noen måter å "gradigvis bygge opp" request maps.

slipset09:12:51

Det som kan være fint er:

(update foo :lol my-fn-that-knows-about-lolz :bar :baz)

cjohansen09:12:16

Enig i prinsippet, men jeg syns eksempelet med http requests er et godt eksempel på fin bruk av assoc-in. Det er ikke å forgripe seg å vite at inne i headers ligger det headere.

☝️ 2
👍 1
slipset09:12:34

Enig i det.

leifericf09:12:40

Nå prøver jeg å finne ut hvordan jeg kan legge til continuation-token som et optional parameter til fetch-stacks funksjonen min, sånn at jeg kan hente alle batches/"pages" fra API-et til Pulumi ved hjelp av iteration 😁

terjesb10:12:26

Med if kan det bli:

(if continue?
  (assoc-in request [:query-params :continuationToken] "continuation-token")
  request))
Her syns jeg cond-> er et fint alternativ:
(cond-> request
  continue? (assoc-in [:query-params :continuationToken] "continuation-token"))
Hvis du fra før har
(-> m
  x
  y)
og skal legge til en ny optional, kan det bli:
(cond-> m
  true x
  true y
  continue? …)

magnars10:12:47

eller

(-> m
    x
    y
    (cond-> continue? z))

💡 2
magnars10:12:48

det er nesten som om Rich har tenkt litt når han satte sammen disse byggeklossene til oss 😄

augustl11:12:10

cond-> i threading-macro er supert!

slipset11:12:03

Jeg må si at akkurat som med veldig smarte destructurings, så sliter jeg litt med smarte threadings også…

cjohansen11:12:26

Enig i det, men den siste til Magnar kunne jeg ha brukt

leifericf11:12:49

Dumt spørsmål: I eksempelet ditt, @U0523NZUF, er continue? en predicate-funksjon som sjekker hvorvidt en continuation token eksisterer i responsen?

terjesb12:12:04

Siden du skal bruke iteration, bruker du :kf for å lese ut continuation-token fra responsen til foregående step:

(iteration step :kf (fn [response] (:continuationToken response)) ...)
eller :kf #(:continuationToken %) iteration sender (kf ret) (hvis ikke nil) som arg til step-funksjonen din:
(defn step [continuation-token] …)
som da kan bruke:
(cond-> request
        continuation-token (assoc-in [:query-params :continuationToken] continuation-token))
På første kall er continuation-token nil (eller :initk, hvis du inkluderer den til iteration), deretter er den alltid non-nil (kf ret) i hvert step. Koden over legger den altså kun på fra og med step 2. Hvis du trenger å initialisere noe inn i step-funksjonen, kan du også gjøre det slik:
(defn step [& {:keys [access-token]}]
  ;; setup og returner faktisk step-fn
  (fn step* [continuation-token]
    (let [request {:headers {"Authorization" (format "token %s" access-token)}}]
      ...)))
Og sette den opp slik: (iteration (step :access-token access-token) …) Ev. kan du også gjøre om headers og base-request fra def til defn:
(defn headers [& {:keys [access-token]}])
(defn base-request [& {:keys [access-token]}])
og bruke:
(fn step* [continuation-token]
    (let [request (cond-> (base-request :access-token access-token)
                          continuation-token (assoc-in [:query-params :continuationToken] continuation-token))]
      ...))
%%% En annen approach kunne være å droppe cond-> og alltid legge på continuation-token, og så kjøre en clean/sanitize-funksjon som fjerner nil-verdier før du sender requesten. Hvis API-et ikke skiller på blank og fraværende continuation-token, kan du faktisk alltid sende den med. Syns her at cond-> som over er helt grei, eventuelt via:
(defn with-continuation-token [request continuation-token]
  (cond-> request
          continuation-token (assoc-in [:query-params :continuationToken] continuation-token)))
(let [request (with-continuation-token (base-request…) continuation-token)] …) Og har du kommet helt hit, kan du kanskje droppe with-continuation-token, og bare ta den med i base-request:
(defn base-request [& {:keys [access-token continuation-token]}])
og gjøre cond-> der:)
(fn step* [continuation-token]
  (post (base-request :access-token access-token :continuation-token continuation-token)))
..men er kanskje finest å ha continuation-token høyere opp i step* og ikke sende den ned mange nivåer.

😮 1
👀 1
slipset13:12:58

Super nitpick:

:kf :continuationToken
siden key words er funksjoner 🙂

leifericf13:12:44

Tusen takk for tipsene! Jeg sliter litt med å henge med, må lese litt sakte og teste det ut her.

slipset13:12:37

La igjen en liten kommentar på gist’en

💜 1
leifericf09:12:09

Jeg kom frem til dette:

(def stack-request
  {:headers headers
   :query-params {:organization pulumi-org}
   :url (str pulumi-uri "/api/user/stacks")
   :method :get})

(defn get-page! [continuation-token]
  (let [request stack-request]
    (if continuation-token
      (assoc-in request [:query-params :continuationToken] continuation-token)
      request)
    @(http-kit/request request response-body->json)))
Men request blir ikke oppdatert med continuation token når jeg kjører (get-page! "XYZ") Er det ikke lov å gjøre assoc-in på en let-binding? :thinking_face:

cjohansen09:12:51

Det kan du gjøre til krampa tar deg, men den er immutable 🙂

😂 1
slipset09:12:57

Du må huske på at du jobber med immutable data 🙂

😂 1
cjohansen09:12:15

Du endrer ikke på request, du lager en ny request - som du sporenstreks kaster fra deg

slipset09:12:19

Du får deg en ny versjon request som du bare kaster 🙂

slipset09:12:32

Seems to be an echo in here 🙂

cjohansen09:12:22

(defn get-page! [continuation-token]
  (-> stack-request
      (cond-> continuation-token
        (assoc-in [:query-params :continuationToken] continuation-token))
      (http-kit/request response-body->json)
      deref))
Sånn, bare for å provosere @slipset litt 😄

slipset09:12:43

Jeg ville jo kanskje hatt den testen på continuation-token på utsiden av denne funksjonen?

cjohansen09:12:10

Det er jo det eneste den gjør 😄

slipset09:12:45

mnja, det kan man si, men den mekker jo egentlig en request og så utfører den requesten.

leifericf09:12:53

Ja, egentlig ville jeg sende inn request map som parameter også, men da får jeg ikke bruke get-page! med iteration (`step` må ta ett parameter).

cjohansen09:12:08

partial 🙂

💡 1
leifericf09:12:35

Oh, my! Ja, så klart.

cjohansen09:12:55

Evt #(get-page! stack-request %) (som er det samme)

💡 1
leifericf09:12:50

Det er egentlig utrolig frustrerende å vite om alle disse bitene som kan komponeres, men ikke være smart nok til å tenke på dem og sette dem sammen riktig 😂

cjohansen09:12:31

Det tar tid å få god nok oversikt, og ha dem i fingertuppene 🙂

leifericf09:12:39

Med en gang dere svarer, tenker jeg "så klart!" og "gudbedre så tafatt og bakstrebersk jeg er" haha!

slipset09:12:41

Tror forøvrig at @U9MKYDN4Q sin cond-> ikke er helt riktig?

cjohansen09:12:00

Joda, bare litt snålt formattert

slipset09:12:18

Jepp. fintekode.

😂 2
slipset09:12:31

Gjorde du det med vilje?

leifericf09:12:57

Hvorfor bruker man forresten deref istedenfor @? :thinking_face: Er det bare en smakssak?

slipset09:12:24

Går sikkert ikke å threade inn i @ siden det sikkert er en (reader) makro

leifericf09:12:33

Ahaaa, right.

cjohansen09:12:04

@slipset tja, det ble seende litt rart ut uansett. godt mulig man ikke burde drive med det der med mindre det går på en linje

magnars09:12:21

Jeg syns det leser helt fint 🤷

slipset09:12:36

jeg leste nemlig at du thread’a continuation-token hvilket ikke gir mening.

cjohansen09:12:44

Ja, jeg ser den

magnars09:12:25

i likhet med andre former i en -> så må man ha med seg at man er i en ->

cjohansen09:12:46

Joda. Men jeg er enig i at threading inni threading er "pushing it"

cjohansen09:12:07

Men jeg er ikke så glad i if inni threading heller

slipset09:12:10

Jo, men det hadde nok vært mer åpenbart (for meg) hvis det hadde vært

(cond-> 
   continuation-token (assoc-in ...))

cjohansen09:12:28

Da må @U01PE7630AC slutte å ha så lange navn 😄

😅 1
magnars09:12:36

langt å foretrekke over en drøss med true i en topp level cond-> i hvert fall

🎯 1
cjohansen09:12:51

det er ikke et alternativ i min bok

💯 1
cjohansen09:12:02

My final offer:

cjohansen09:12:03

(defn get-page! [token]
  (-> stack-request
      (cond-> token (assoc-in [:query-params :continuationToken] token))
      (http-kit/request response-body->json)
      deref))

slipset09:12:56

(defn get-page! [token]
  @(http-kit/request (cond-> stack-request 
                         token (assoc-in [:query-params :continuationToken] token)) response-body->json))

cjohansen09:12:40

Jeg er litt kjølig innstilt til så bred kode 😅

slipset09:12:52

Modulo linjeskift da 🙂

leifericf09:12:00

Haha, jeg tenkte det samme "den var brei" 😅

cjohansen09:12:07

Jeg er heller ikke noen fan av at et argument "midt i" er over flere linjer

slipset09:12:17

Men, jeg tror min største insigelse er at jeg ville nok hatt stack-request som param.

👍 1
cjohansen09:12:43

Jeg ville heller formattert det sånn i så fall:

(defn get-page! [token]
  @(http-kit/request
    (cond-> stack-request 
      token (assoc-in [:query-params :continuationToken] token))
    response-body->json))

slipset09:12:58

Mye av dette ville jo vært ungått dersom man hadde brukt et ikke-async http-client bibliotek

💡 1
cjohansen09:12:01

Men jeg liker threading-varianten bedre

leifericf09:12:18

> Da må @U01PE7630AC slutte å ha så lange navn 😄 Yrkesskade fra C# 😂

cjohansen09:12:41

Alt over tre bokstaver kommer med en viss skam for ekte lisp-folk 😁

cjohansen09:12:13

cnt-tok

😂 1
leifericf09:12:15

Har en kar som puster meg i nakken, men skal bytte til et av Babashka sine innebygde ikke-async HTTP klienter etter jeg har fått det til å funke.

slipset09:12:44

Bryr vi oss egentlig om hva slags token det er?

slipset09:12:20

altså, jeg vil si at du “overspesifiserer” navnet til tokenet.

cjohansen09:12:21

Nei, derfor kalte jeg det token

🎯 1
cjohansen09:12:34

Mitt forrige forslag var en spøk

leifericf09:12:28

Det ble slik for nå:

(defn get-page!
  ([] (get-page! nil))
  ([token]
   (-> stack-request
       (cond-> token (assoc-in [:query-params :continuationToken] token))
       (http-kit/request response-body->json)
       deref)))
Da kan jeg gjøre: (def stacks (get-page!)) Og med token: (get-page! (get stacks "continuationToken")) Men jeg skal få ut stack-request til et parameter og bytte HTTP klient etter jeg har fått oppgaven i land 😄

cjohansen09:12:14

Eventuelt:

(defn get-page! [& [token]]
  (-> stack-request
      (cond-> token (assoc-in [:query-params :continuationToken] token))
      (http-kit/request response-body->json)
      deref))

🔥 1
cjohansen09:12:45

Sikkert mikroskopisk dårligere ytelse, men tipper den forskjellen spises opp av nettverksaktiviteten der 🙂

leifericf09:12:48

& args og destructuring er også greier jeg sliter litt med å forstå og bruke smart.

slipset09:12:30

Jeg er litt var for bruk av ulike former for multi-arity fns fordi da plukker ikke clj-kondo opp at jeg gjør feil.

💡 1
cjohansen09:12:35

Er det en villet mangel i clj-kondo? Jeg har merka det sjøl, virker som om den ikke skjønner multi-arity

leifericf09:12:39

Ahaaa, det forklarer visse squigglies i koden min.

slipset09:12:42

Jo, men hvis du har en (defn foo [& opts]...) så er det jo ingen mulighet å sjekke for at du har med deg noe som helst. (defn foo [opt1 opt2 opt3]…) sier jo at du krever disse tre opt’sene

cjohansen09:12:11

Ja, men om du gir funksjonen to signaturer sånn @U01PE7630AC gjorde først så syns clj-kondo at alt er dritt

😂 1
slipset09:12:46

Hmm, det har ikke jeg merka.

leifericf10:12:17

Kult! Nå funker iteration! Wohooo! Men den henter visst kun den første siden 😛 Så det er noe rart der.

(def all-stacks (iteration get-page!
                             :vf #(get % "stacks")
                             :kf :continuationToken))
Én response fra get-page! ser slik ut:
{"stacks"
 [{"orgName" "my-org",
   "projectName" "my-project",
   "stackName" "my-stack-1",
   "lastUpdate" 1692103259,
   "resourceCount" 14}
  {"orgName" "my-org",
   "projectName" "my-project",
   "stackName" "my-stack-2",
   "lastUpdate" 1695911748,
   "resourceCount" 16}
  {"orgName" "my-org",
   "projectName" "my-project",
   "stackName" "my-stack-3",
   "lastUpdate" 1700757647,
   "resourceCount" 14}
  ...],
 "continuationToken"
 "XYZ"}

leifericf10:12:18

Aaaah! Kanskje fordi "continuationToken" er en string og ikke et keyword (`:continuationToken`) i responsen!

cjohansen10:12:35

Du må sørge for at response-body->json mapper keys til keywords

leifericf10:12:42

Dette funka fett!

(def all-stacks (iteration get-page!
                             :vf #(get % "stacks")
                             :kf #(get % "continuationToken")))

leifericf10:12:53

Men, ja, sikkert penere å gjøre konvertering fra strings til keywords utenfor.

cjohansen11:12:36

(defn response-body->json [response]
  (-> (:body response)
      (json/parse-string keyword)))

😮 1
leifericf11:12:58

Altså, dette er bare heeelt rått.

(def all-stacks
    (iteration get-page!
               :vf :stacks
               :kf :continuationToken))

  (def stack-names
    (->> all-stacks
         seq
         flatten
         (map :stackName)
         sort))

slipset11:12:33

Kjør på med en (mapcat :stackName) 🙂

slipset11:12:48

Og du trenger vel ikke seq hller?

leifericf11:12:51

Det tar aldri slutt med improvements. Må digge Clojure.

slipset11:12:36

Og du trenger vel strengt tatt ikke def’e all-stacks lenger?

leifericf11:12:40

http-kit/request returnerer en "promise." Jeg måtte konvertere den til en seq for å gjøre mer med den.

slipset11:12:14

Jo, men kan du ikke bare deref’e rett på http-kit/request ?

leifericf11:12:19

iteration mente jeg

leifericf11:12:44

Jeg fikk ikke lov til å gjøre flatten på det iteration gav meg.

slipset11:12:23

(->> (iteration get-page! :vf :stacks :kf :contunuationToken) seq (mapcat :stackName) sort)

slipset11:12:04

Dette er jo faktisk ganske så nice 🙂

leifericf11:12:18

Hmmm, mapcat returnerer nil

slipset11:12:49

Meep! Det er jeg som tenker feil

slipset11:12:56

Det funker ikke med mapcat.

👍 1
leifericf11:12:31

Nå med request map som parameter!

(defn get-page! [request & [token]]
  (-> request
      (cond-> token (assoc-in [:query-params :continuationToken] token))
      (http-kit/request response-body->json)
      deref))

(get-page! stack-request)

(iteration (partial get-page! stack-request)
             :vf :stacks
             :kf :continuationToken)

leifericf11:12:21

En liten hjelper (for mine kollegaer som ikke kan Clojure) 🙂

(defn get-all-pages! [request token-key]
  (iteration (partial get-page! request)
             :vf :stacks
             :kf token-key))

terjesb11:12:11

partial funker utmerket slik du bruker den her, når du også ønsker å bruke get-page! i andre sammenhenger (hvorfor utropstegn på get?). Hvis du kun skulle bruke den som step-fn, kunne du også gjøre det slik:

(defn get-page! [request]
  (fn [token]
    (-> request …)))
Og så kalle den:
(iteration (get-page! stack-request) :vf …) 

👍 1
leifericf12:12:12

hvorfor utropstegn på get?Jeg trodde det var god praksis å bruke ! på funksjoner med sideeffekter, og jeg trodde get-page! var en slik 😅 Men når jeg tenker meg om har vel mine fleste funksjoner sideeffekter fordi dette er Babashka/Shell greier.

augustl12:12:05

http-kit/request har selv ikke noe utropstegn. Utropstegnet er ofte for “destruktive” ting, eller noe sånt? :thinking_face:

👍 1
augustl12:12:42

“conventional ending for an unsafe operation” sier clojure doc

👍 1
cjohansen12:12:28

Jeg bruker ! for skrive-operasjoner ja. Selv ville jeg unngått "get" for nettverks-requests, bruker typ "fetch" eller lignende i stedet

👍 1
leifericf14:12:35

I dag har jeg oppdatert ca. 3100 Pulumi stacks via Clojure og kjørt det i produksjon i en live demo for kollegaer 😎 Det var veldig kult å kunne vise live hvordan man skape og inspisere HTTP request maps som ren data før de sendes, og kunne filtrere ut enkelte for å teste før man kjører på alt. Jeg er spesielt fornøyd med denne (men har allerede noen tanker om forbedringer):

(defn create-permission-request [team-name permission stack]
  (let [codes {:read 101 :edit 102 :admin 103}
        payload (-> {"addStackPermission" {"projectName" (:projectName stack)
                                           "stackName" (:stackName stack)
                                           "permission" (permission codes)}}
                    json/generate-string)]
    {:headers headers
     :url (format (str pulumi-uri "/orgs/%s/teams/%s")
                  pulumi-org team-name)
     :body payload
     :method :patch}))

(doall
 (->> (fetch-all-pages stack-request :continuationToken)
      seq
      flatten
      (get-stacks-by-name "dev")
      (take 1)
      (map #(create-permission-request "Manual-Access-Group" :read %))
      (map grant-stack-permission)))

1
👍 2
leifericf08:12:40

Kanskje dette er litt bedre 🙂 Bort med let i @U9MKYDN4Q’s ånd.

(defn create-permission-request [team permission stack]
  {:headers headers
   :url (format (str pulumi-uri "/orgs/%s/teams/%s") pulumi-org team)
   :body (-> {"addStackPermission"
              {"projectName" (:projectName stack)
               "stackName" (:stackName stack)
               "permission" (permission {:read 101
                                         :edit 102
                                         :admin 103})}}
             json/generate-string)
   :method :patch})

magnars08:12:00

Forbedring! 👍

👍 1
leifericf09:12:37

Jeg prøver å få til destructuring på stack sånn at jeg slipper å gjøre (:projectName stack) og (:stackName stack) 🙂

magnars09:12:45

det tenker jeg er ganske unødvendig - du ender opp med flere "løse" bindinger som ikke øker lesbarheten nevneverdig

💯 1
💡 1
magnars09:12:21

men det er altså [team permission {:keys [projectName stackName]}] i tilfelle du vil prøve

👍 2
augustl09:12:16

jeg er forøvrig glad i ….. eksplisitt destructuring? I mangel på et bedre navn. [team permission {project-name :projectName stack-name :stackName}]

👀 1
augustl09:12:33

er noe med å kunne greppe etter :projectName jeg har en liten hangup på

cjohansen09:12:34

Da forsvinner hele poenget med destructuring i mine øyne

cjohansen09:12:24

Målet er å bli kvitt noen navn og få ting litt mer kompakt. Det der fører bare til Java-style dobbel-navning. Da ville jeg foretrukket å bruke keyword-oppslag uten destructuring.

cjohansen09:12:39

Men enig i at det er ålreit å kunne greppe etter enkelte ting. Jeg prøver å bruke stadig mer namespaced keys, og er forsiktig med å destructure dem, med samme mål.

leifericf11:12:06

Ref. https://clojurians.slack.com/archives/C061XGG1W/p1703064598161949?thread_ts=1702976581.541629&cid=C061XGG1W til @slipset Sånn! Nå har jeg endret til å bruke babashka.http-client og babashka.json istedenfor org.httpkit.client og cheshire.core. Altså, ikke-async, færre avhengigheter, mindre kode, og 100% Babashka! Og jeg fant en bug i babashka.http-client i porteringsprosessen (er i ferd med å åpne et issue). https://gist.github.com/leifericf/242fb222966c8cfc8beb04539aaf8fab er oppdatert med ny kode nå.

👏 2
1
leifericf12:12:07

Nå også https://www.linkedin.com/feed/update/urn:li:activity:7145749161152352256/ for å promotere Clojure/Babashka litt med et virkelig use case 🙂

slipset11:12:32

Fikk de to for prisen av en?

😂 1
leifericf09:12:09

Jeg kom frem til dette:

(def stack-request
  {:headers headers
   :query-params {:organization pulumi-org}
   :url (str pulumi-uri "/api/user/stacks")
   :method :get})

(defn get-page! [continuation-token]
  (let [request stack-request]
    (if continuation-token
      (assoc-in request [:query-params :continuationToken] continuation-token)
      request)
    @(http-kit/request request response-body->json)))
Men request blir ikke oppdatert med continuation token når jeg kjører (get-page! "XYZ") Er det ikke lov å gjøre assoc-in på en let-binding? :thinking_face:

leifericf14:12:35

I dag har jeg oppdatert ca. 3100 Pulumi stacks via Clojure og kjørt det i produksjon i en live demo for kollegaer 😎 Det var veldig kult å kunne vise live hvordan man skape og inspisere HTTP request maps som ren data før de sendes, og kunne filtrere ut enkelte for å teste før man kjører på alt. Jeg er spesielt fornøyd med denne (men har allerede noen tanker om forbedringer):

(defn create-permission-request [team-name permission stack]
  (let [codes {:read 101 :edit 102 :admin 103}
        payload (-> {"addStackPermission" {"projectName" (:projectName stack)
                                           "stackName" (:stackName stack)
                                           "permission" (permission codes)}}
                    json/generate-string)]
    {:headers headers
     :url (format (str pulumi-uri "/orgs/%s/teams/%s")
                  pulumi-org team-name)
     :body payload
     :method :patch}))

(doall
 (->> (fetch-all-pages stack-request :continuationToken)
      seq
      flatten
      (get-stacks-by-name "dev")
      (take 1)
      (map #(create-permission-request "Manual-Access-Group" :read %))
      (map grant-stack-permission)))

1
👍 2