Skip to content

Latest commit

 

History

History
109 lines (81 loc) · 3.59 KB

t3.md

File metadata and controls

109 lines (81 loc) · 3.59 KB

Tutorial 3 - Client object

We have a running example where the requests are made with credentials, etc... but we're missing a critical element of client objects that execute the requests.

What most users want is a client they can create with parameters injected from an environment and support for multiple client objects working with different configurations (but calling same API).

This library doesn't provide anything specifically for this purpose and leaves it up to the user, but we will develop a client function to demonstrate a sample solution.

We've left tutorial 2 with this:

(defrest
  {"https://api.github.com"
   {["users/{username}/orgs" string?] {GET (list-user-organizations [])}
    "organizations" {GET (list-organizations [since (s/nilable pos-int?)])}
    ["repos/{owner}/{repo}" string? string?]
    {"assignees" {GET (list-assignees [])}
     "issues" {["{issue_no}" pos-int?] {GET (get-issue [])
                                        "assignees" {POST (add-issue-assignees [^:key assignees (s/coll-of string?)])
                                                     DELETE (remove-issue-assignees [^:key assignees (s/coll-of string?)])}}}}}}
  :defaults {:basic-auth ["user" "personal-token"]})

Instead of having URL and basic-auth in rest definition we'd rather have a client object that encapsulates them.

(defrest
  {["users/{username}/orgs" string?] {GET (list-user-organizations [])}
   "organizations" {GET (list-organizations [since (s/nilable pos-int?)])}
   ["repos/{owner}/{repo}" string? string?]
   {"assignees" {GET (list-assignees [])}
    "issues" {["{issue_no}" pos-int?] {GET (get-issue [])
                                       "assignees" {POST (add-issue-assignees [^:key assignees (s/coll-of string?)])
                                                    DELETE (remove-issue-assignees [^:key assignees (s/coll-of string?)])}}}}})

(defn git-client [url user pass]
  (fn [req]
    (-> req
      (assoc :basic-auth [user pass])
      (update :url #(str url "/" %))
      client/request)))

Now we can declare a client with credentials injected from elsewhere. Example:

(def cli (git-client "https://api.github.com" "RokLenarcic" "api-key"))

Then we can execute the request with this object:

(cli (list-organizations nil))

You can also opt to have the body extracted by default:

(defn git-client [url user pass]
  (fn [req]
    (-> req
      (assoc :basic-auth [user pass])
      (update :url #(str url "/" %))
      client/request
      :body)))

There are other options:

clj-http middleware

Another helper is prefix-middleware function in clj-rest-client.core, which returns clj-http middleware that prefixes urls with the given prefix. Here's an example:

(ns example.core
  (:require [clj-rest-client.core :refer [defrest prefix-middleware]
            [clj-http.client :as client]]))

(defrest {"person" {GET (get-person)}})
; execute request with extra middleware
(client/with-additional-middleware [(prefix-middleware "http://my-server")]
  (client/request (get-person)))

Or simply use set! or alter-var-root or binding to add prefix middleware to client/*current-middleware.

Or you can make the basic host a path param. E.g.

Host as a param

Another way to make url a parameter:

(defrest {"{url}" {"person" {GET (get-person [url string?])}}})
; execute request
(client/request (get-person "http://my-server"))

Production style

In production, you will want to reuse clj-http connection manager, or your performance will be poor.