Ep 103: Explorify!

Play Episode

Each week, we discuss a different topic about Clojure and functional programming.

If you have a question or topic you'd like us to discuss, tweet @clojuredesign, send an email to feedback@clojuredesign.club, or join the #clojuredesign-podcast channel on the Clojurians Slack.

This week, the topic is: "exploring new data and APIs". We peruse APIs to uncover the data hidden beneath.

Our discussion includes:

Selected quotes

Example code

Here is an example that uses the Cloudflare Streams API. It requires authentication, so we want to factor that out.

First, define some endpoints to work with. Create pure functions for just the part unique to each endpoint.

(defn check-status-req [id]
  {:method :get
   :path id})

(defn delete-req [id]
  {:method :delete
   :path id})

Then create a function to expand the request with the "common" parts:

(defn full-req
  [cloudflare api-req]
  (let [{:keys [api-key account-id]} cloudflare
        {:keys [method path], throw? :throw} api-req]
    {:async true
     :method method
     :uri (format "https://api.cloudflare.com/client/v4/accounts/%s/stream/%s" account-id path)
     :headers {"Authorization" (str "Bearer " api-key)
               "Accept" "application/json"}
     :throw throw?}))

For the purposes of illustration, the cloudflare configuration is:

(def cloudflare
  {:api-key "super-secret"
   :account-id "42424242"})

See the full request like so:

(full-req cloudflare (check-status-req "abcdefg123456789"))

Which returns:

{:async true
 :method :get
 :uri "https://api.cloudflare.com/client/v4/accounts/42424242/stream/abcdefg123456789"
 :headers {"Authorization" "Bearer super-secret"
           "Accept" "application/json"}
 :throw nil}

Use [babashka.http-client :as http], and call the endpoints like so:

@(http/request (full-req cloudflare (check-status-req "abcdefg123456789")))
@(http/request (full-req cloudflare (delete-req "abcdefg123456789")))

Note, that in a REPL-connected editor, you evaluate each form, so you can see just the check-status-req part, or move out one level and see the full-req part, or move out one more level and actually run it. That lets you iron out the details to make sure you have them right.

Finally, you can make some helpers to use in the REPL or imperative code. The helpers stitch the process together:

(defn request!
  [cloudflare req]
  (-> @(http/request (full-req cloudflare req))
      (update :body #(json/parse-string % true))))

(defn check-status!
  [cloudflare id]
  (request! cloudflare (check-status-req id)))

(defn delete-stream!
  [cloudflare id]
  (request! cloudflare (delete-req id)))

They can be called like:

(check-status! cloudflare "abcdefg123456789")
(delete-stream! cloudflare "abcdefg123456789")

The helpers should do nothing except compose together other parts for convenience. All the real work is in the pure functions.