{"id":19294000,"url":"https://github.com/igrishaev/farseer","last_synced_at":"2025-04-22T07:32:33.155Z","repository":{"id":147469914,"uuid":"356663383","full_name":"igrishaev/farseer","owner":"igrishaev","description":"A set of modules for handling JSON RPC in Clojure","archived":false,"fork":false,"pushed_at":"2021-06-02T13:57:40.000Z","size":547,"stargazers_count":23,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-01T20:51:27.406Z","etag":null,"topics":["clojure","rpc-client","rpc-server"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igrishaev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-10T18:27:21.000Z","updated_at":"2023-09-02T12:03:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"a7155bc2-99ed-4bfb-b392-f8481e66bfc2","html_url":"https://github.com/igrishaev/farseer","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Ffarseer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Ffarseer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Ffarseer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Ffarseer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igrishaev","download_url":"https://codeload.github.com/igrishaev/farseer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250195054,"owners_count":21390230,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","rpc-client","rpc-server"],"created_at":"2024-11-09T22:36:48.570Z","updated_at":"2025-04-22T07:32:33.146Z","avatar_url":"https://github.com/igrishaev.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Farseer\n\n[JSON-RPC]: https://en.wikipedia.org/wiki/JSON-RPC\n\nA set of modules for [JSON RPC][JSON-RPC]. Includes a transport-independent\nhandler, Ring HTTP handler, Jetty server, HTTP client, local stub,\ndocumentation, and more.\n\n## Table of Contents\n\n\u003c!-- toc --\u003e\n\n- [What and Why is JSON RPC?](#what-and-why-is-json-rpc)\n  * [Benefits](#benefits)\n  * [Disadvantages](#disadvantages)\n- [The Structure of this Project](#the-structure-of-this-project)\n  * [Installation](#installation)\n- [RPC Handler](#rpc-handler)\n  * [Method handlers](#method-handlers)\n  * [Specs](#specs)\n    + [Input Spec](#input-spec)\n    + [Output Spec](#output-spec)\n    + [In Production](#in-production)\n  * [More on Context](#more-on-context)\n    + [Static Context](#static-context)\n    + [Dynamic Context](#dynamic-context)\n  * [Request \u0026 Response Formats](#request--response-formats)\n    + [Request](#request)\n    + [Response](#response)\n    + [Error Codes](#error-codes)\n  * [Notifications](#notifications)\n  * [Batch Requests](#batch-requests)\n    + [Note on Parallelism](#note-on-parallelism)\n    + [Configuring \u0026 Limiting Batch Requests](#configuring--limiting-batch-requests)\n  * [Errors \u0026 Exceptions](#errors--exceptions)\n    + [Runtime (Unexpected) Errors](#runtime-unexpected-errors)\n    + [Expected Errors](#expected-errors)\n    + [Raising Exceptions](#raising-exceptions)\n  * [Configuration](#configuration)\n- [Ring HTTP Handler](#ring-http-handler)\n    + [Negative Responses](#negative-responses)\n    + [Batch Requests in HTTP](#batch-requests-in-http)\n    + [Configuration](#configuration-1)\n    + [Middleware \u0026 Authorization](#middleware--authorization)\n    + [HTTP Context](#http-context)\n- [Jetty Server](#jetty-server)\n  * [Configuration](#configuration-2)\n  * [With-server macro](#with-server-macro)\n  * [Component](#component)\n- [HTTP Stub](#http-stub)\n    + [Multiple Stub](#multiple-stub)\n    + [Tests](#tests)\n    + [Negative Responses](#negative-responses-1)\n- [HTTP Client](#http-client)\n    + [Configuration](#configuration-3)\n    + [Handling Response](#handling-response)\n    + [Auth](#auth)\n    + [Notifications](#notifications-1)\n    + [Batch Requests](#batch-requests-1)\n    + [Connection Manager (Pool)](#connection-manager-pool)\n    + [Component](#component-1)\n- [Documentation Builder](#documentation-builder)\n  * [Configuration](#configuration-4)\n  * [Building](#building)\n  * [Demo](#demo)\n  * [Selmer \u0026 Context](#selmer--context)\n  * [Rendering Specs](#rendering-specs)\n- [Ideas \u0026 Further Development](#ideas--further-development)\n- [Author](#author)\n\n\u003c!-- tocstop --\u003e\n\n## What and Why is JSON RPC?\n\nBriefly, JSON RPC is a protocol based on HTTP \u0026 JSON. When calling the server,\nyou specify the method (procedure) name and its parameters. The parameters could\nbe either a map or a vector. The server returns a JSON response with the\n`result` or `error` fields. For example:\n\nRequest:\n\n~~~json\n{\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1, 2], \"id\": 3}\n~~~\n\nResponse:\n\n~~~json\n{\"jsonrpc\": \"2.0\", \"result\": 3, \"id\": 3}\n~~~\n\nPay attention: the protocol depends on neither HTTP method, nor query params,\nnor HTTP headers and so on. Although looking a bit primitive, this schema\neventually appears to be robust, scalable and reliable.\n\n### Benefits\n\nRPC protocol brings significant changes into your API, namely:\n\n- There is single API endpoint on the server, for example `/api`. You don't need\n  to concatenate strings manually to build the paths like\n  `/post/42/comments/52352` in REST.\n\n- All the data is located in one place. There is no need to parse the URI, query\n  params, check out the method and so on. You don't need to guess which HTTP\n  method to pick (PUT, PATCH) for an operation when several entities change.\n\n- RPC grows horizontally with ease. Once you've set it up, you only extend\n  it. Technically it means adding a new key into a map.\n\n- RPC doesn't depend on transport. You can save the payload in Cassandra or push\n  to Kafka. Later on, you can replay the sequence as it has everything you need.\n\n- RPC is a great choice for interaction between internal services. When all the\n  services follow the same protocol, it's easy to develop and maintain them.\n  When protected with authentication, RPC can be provided to the end customers\n  as well.\n\n### Disadvantages\n\nThe only disadvantage of RPC protocol is that it's free from caching. On the\nother hand, we rarely want to get cached data. Most often, it's important to get\nactual data on each request. If you share some public data that update rarely,\nperhaps you should organize ordinary GET endpoints.\n\n## The Structure of this Project\n\nFarseer consists from several minor projects that complement each other. Every\nsub-project requires its own dependencies. If it was a single project, you would\ndownload lots of stuff you don't really need. Instead, with sub-projects, you\ninstall only those parts (and transient dependencies) you really need in your\nproject.\n\nThe root project is named `com.github.igrishaev/farseer-all`. It unites all the\nsub-projects listed below:\n\n- `com.github.igrishaev/farseer-common`: dependency-free parts required by other\n  sub-projects;\n\n- `com.github.igrishaev/farseer-handler`: a transport-free implementation of RPC\n  handler;\n\n- `com.github.igrishaev/farseer-http`: HTTP Ring handler for RPC;\n\n- `com.github.igrishaev/farseer-jetty`: HTTP Jetty server for RPC;\n\n- `com.github.igrishaev/farseer-stub`: an HTTP stub for RPC server, useful for tests;\n\n- `com.github.igrishaev/farseer-client`: HTTP RPC client based on `clj-http`;\n\n- `com.github.igrishaev/farseer-doc`: RPC documentation builder.\n\n[groups]: https://github.com/clojars/clojars-web/wiki/Groups\n\n### Installation\n\nThe \"-all\" bundle:\n\n- Lein:\n\n```clojure\n[com.github.igrishaev/farseer-all \"0.1.1\"]\n```\n\n- Deps.edn\n\n```clojure\ncom.github.igrishaev/farseer-all {:mvn/version \"0.1.1\"}\n```\n\n- Maven\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.igrishaev\u003c/groupId\u003e\n  \u003cartifactId\u003efarseer-all\u003c/artifactId\u003e\n  \u003cversion\u003e0.1.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nAlternatevely, install only what you need:\n\n- Lein:\n\n```clojure\n[com.github.igrishaev/farseer-http \"0.1.1\"]\n[com.github.igrishaev/farseer-client \"0.1.1\"]\n```\n\n[com.github.igrishaev]: https://clojars.org/groups/com.github.igrishaev\n\nand so on (see the list of packages [on Clojars][com.github.igrishaev]).\n\n## RPC Handler\n\nThe `com.github.igrishaev/farseer-handler` package provides basic implementation\nof RPC protocol. It has no any transport layer, only a handler that serves RPC\nrequests no matter where the data comes from. Other packages provide HTTP layer\nfor this handler. You can develop another transport layer as well.\n\nFirst, add the package to the project:\n\n```clojure\n[com.github.igrishaev/farseer-handler ...]\n```\n\nHere is the minimal usage example. Prepare a namespace:\n\n```clojure\n(ns demo\n  (:require\n   [farseer.handler :refer [make-handler]]))\n```\n\nCreate a method handler and the config:\n\n```clojure\n(defn rpc-sum\n  [_ [a b]]\n  (+ a b))\n\n(def config\n  {:rpc/handlers\n   {:math/sum\n    {:handler/function rpc-sum}}})\n```\n\nNow declare a handler and call it:\n\n```clojure\n(def handler\n  (make-handler config))\n\n(handler {:id 1\n          :method :math/sum\n          :params [1 2]\n          :jsonrpc \"2.0\"})\n\n;; {:id 1, :jsonrpc \"2.0\", :result 3}\n```\n\n### Method handlers\n\nThe `rpc-sum` function is a handler for the `:math/sum` method. The function\ntakes **exactly two** arguments. The first argument is the context map which\nwe'll discuss later. The second is the parameters passed to the method in\nrequest. They might be either a map or a vector. If a method doesn't accept\nparameters, the arguments will be `nil`.\n\nThe function might be defined in another namespace. In this case, you import it\nand pass to the map as usual:\n\n```clojure\n(ns demo\n  (:require\n   [com.project.handlers.math :as math]))\n\n(def config\n  {:rpc/handlers\n   {:math/sum\n    {:handler/function math/sum-handler}}})\n```\n\nIt's useful to pass the functions as vars using the `#'` syntax:\n\n```clojure\n(def config\n  {:rpc/handlers\n   {:math/sum\n    {:handler/function #'rpc-sum}}})\n```\n\nIn this case, you can update the function by evaling its `defn` form in REPL,\nand the changes come into play without re-creating the RPC handler. For example,\nwe change plus to minus in the `rpc-sum` function:\n\n```clojure\n(defn rpc-sum\n  [_ [a b]]\n  (- a b))\n```\n\nThen we go to the closing bracket of the `defn` form and perform\n`cider-eval-last-sexp`. Now we make a new RPC call and get a new result:\n\n```clojure\n(handler {:id 1\n          :method :math/sum\n          :params [1 2]\n          :jsonrpc \"2.0\"})\n\n{:id 1, :jsonrpc \"2.0\", :result -1}\n```\n\nOnly the methods declared in the config are served by the RPC handler. If you\nspecify a non-existing one, you'll get a negative response:\n\n```clojure\n(handler {:id 1\n          :method :system/rmrf\n          :params [1 2]\n          :jsonrpc \"2.0\"})\n\n{:error\n {:code -32601, :message \"Method not found\", :data {:method :system/rmrf}},\n :id 1,\n :jsonrpc \"2.0\"}\n```\n\n### Specs\n\nThe code above doesn't validate the incoming parameters and thus is dangerous to\nexecute. If you pass something like `[\"one\" nil]` instead of two numbers, you'll\nend up with `NPE`, which is not good.\n\nFor each handler, you can specify a couple of specs, the input and output\nones. The input spec validates the incoming `params` field, and the output spec\nis for the result of the function call with these parameters.\n\n#### Input Spec\n\nTo protect our `:math/sum` handler from weird data, you declare the specs:\n\n```clojure\n(s/def :math/sum.in\n  (s/tuple number? number?))\n\n(s/def :math/sum.out\n  number?)\n```\n\nThen you add these specs to the method config. Their keys are `:handler/spec-in`\nand `:handler/spec-out`:\n\n```clojure\n(def config\n  {:rpc/handlers\n   {:math/sum\n    {:handler/function #'rpc-sum\n     :handler/spec-in :math/sum.in\n     :handler/spec-out :math/sum.out}}})\n```\n\nNow if you pass something wrong to the handler, you'll get a negative response:\n\n```clojure\n(handler {:id 1\n          :method :math/sum\n          :params [\"one\" nil]\n          :jsonrpc \"2.0\"})\n\n{:id 1\n :jsonrpc \"2.0\"\n :error {:code -32602\n         :message \"Invalid params\"\n         :data {:explain \"\u003cspec explain string\u003e\"}}}\n```\n\n[expound]: https://github.com/bhb/expound\n\nThe `:data` field of the `:error` object has an extra `explain` field. Inside\nit, there is standard explain string produced by the `s/explain-str`\nfunction. This kind of message looks noisy sometimes, and in the future, most\nlikely Farseer will use [Expound][expound].\n\nAccording to the RPC specification, the `:params` field might be either a map or\na vector. Thus, for the input spec, you probably use `s/tuple` or `s/keys`\nspecs. Our `:math/sum` method accepts vector params. Let's rewrite it and the\nspecs such that they work with a map:\n\n```clojure\n;; a new handler\n(defn rpc-sum\n  [_ {:keys [a b]}]\n  (+ a b))\n\n;; new input spec\n(s/def :sum/a number?)\n(s/def :sum/b number?)\n\n(s/def :math/sum.in\n  (s/keys :req-un [:sum/a :sum/a]))\n```\n\nThe output spec and the config are still the same:\n\n```clojure\n(s/def :math/sum.out\n  number?)\n\n(def config\n  {:rpc/handlers\n   {:math/sum\n    {:handler/function #'rpc-sum\n     :handler/spec-in :math/sum.in\n     :handler/spec-out :math/sum.out}}})\n```\n\nNow we pass a map, not vector:\n\n```clojure\n(handler {:id 1\n          :method :math/sum\n          :params {:a 1 :b 2}\n          :jsonrpc \"2.0\"})\n\n{:id 1, :jsonrpc \"2.0\", :result 3}\n```\n\n#### Output Spec\n\nIf the result of the function doesn't match the output spec, it triggers an\ninternal error. Let's reproduce this scenario by spoiling the spec:\n\n```clojure\n(s/def :math/sum.out\n  string?)\n\n(handler {:id 1\n          :method :math/sum\n          :params {:a 1 :b 2}\n          :jsonrpc \"2.0\"})\n\n\n{:id 1,\n :jsonrpc \"2.0\"\n :error {:code -32603,\n         :message \"Internal error\",\n         :data {:method :math/sum}}}\n```\n\nYou'll see the following log entry:\n\n```\n10:18:31.256 ERROR farseer.handler - RPC result doesn't match the output spec,\n             id: 1, method: :math/sum, code: -32603, message: Internal error\n```\n\nThere is no the `s/explain` message, because sometimes it's huge and also\ncontains private data.\n\n#### In Production\n\nYou can turn off checking the input or the output specs globally in the\nconfiguration (see the \"Configuration\" section below). In real projects, we\nalways validate the input data. Regarding the output, we validate it only in\ntests to save time in production.\n\n### More on Context\n\n[component]: https://github.com/stuartsierra/component\n[integrant]: https://github.com/weavejester/integrant\n\nSumming numbers is good for tutorial but makes no sense in real projects. We're\nrather interested in networking IO and database access. Until now, it wasn't\nclear how a function can reach Postgres or Kafka clients, especially if the\nproject relies on a system framework (e.g. [Component][component] or\n[Integrant][integrant]).\n\nIn OOP languages, the environment for the RPC method usually comes from the\n`this` parameter. It's an instance of some `RPCHadler` class that has fields for\nthe database connection, message queue client and so on. In Clojure, we act\nalmost like this, but instead of `this` object, we use context.\n\nA context is a map that carries the data needed by the method handler in\nruntime. This is the first argument of a function from the `:handler/function`\nkey. By default, the context has the current id and method of the RPC call. If\nyou print the first argument of the function, you'll see:\n\n```clojure\n(defn rpc-sum\n  [context {:keys [a b]}]\n  (println context)\n  (+ a b))\n\n#:rpc{:id 1, :method :math/sum}\n```\n\nBoth fields are prefixed with the `:rpc/` namespace to prevent the keys from\nclashing, e.g. `:id` for the RPC call and `:id` for the current user. Instead,\nthe framework passes the `:rpc/id` field, and you should pass `:user/id` one.\n\nThere are two ways of passing context: a static and dynamic ones.\n\n#### Static Context\n\nWhen you call the `make-handler` function to build an RPC handler, the second\nargument might be a context map. This map will be available in all the RPC\nfunctions. For example:\n\n~~~clojure\n(def handler\n  (make-handler\n   config\n   {:db (open-db-connection {...})\n    :version (get-app-version)}))\n~~~\n\nWe assume the `open-db-connection` returns a connection pool, which is available\nto all the RPC functions as the `:db` field of the context. The `:version` field\nis the application version that is fetched from a text file.\n\nNow, if we had an RPC method that fetches a user by id, it would look like this:\n\n~~~clojure\n(defn get-user-by-id\n  [{:keys [db]}        ;; context\n   {:keys [user-id]}]  ;; params\n  (jdbc/get-by-id db :users user-id))\n\n(s/def :user/id pos-int?)\n\n(s/def :user/user-by-id.in\n  (s/keys :req-un [:user/id]))\n\n(s/def :user/user-by-id.out\n  (s/nilable map?))\n~~~\n\nThe config:\n\n~~~clojure\n(def config\n  {:rpc/handlers\n   {:user/get-by-id\n    {:handler/function #'get-user-by-id\n     :handler/spec-in :user/user-by-id.in\n     :handler/spec-out :user/user-by-id.out}}})\n~~~\n\nThe call:\n\n~~~clojure\n(handler {:id 1\n          :method :user/get-by-id\n          :params {:id 5}\n          :jsonrpc \"2.0\"})\n\n{:id 1, :jsonrpc \"2.0\", :result {:id 5 :name \"Test\"}}\n~~~\n\n#### Dynamic Context\n\nUse dynamic context to pass a value needed only for the current RPC request or\nyou don't have a value yet when building a handler. In that case, pass the\ncontext map as the second argument to the function made by the\n`make-handler`. The example with the database would look like this:\n\n~~~clojure\n(def handler (make-handler config))\n\n;; dynamic context, the second arg\n(handler {:id 1\n          :method :user/get-by-id\n          :params {:id 5}\n          :jsonrpc \"2.0\"}\n         {:db hikari-cp-pool})\n~~~\n\nYou can use both ways to pass the context. Most likely the database is needed by\nall the RPC functions, so its place in the global context. Some minor fields\nmight be passes on demand for certain calls:\n\n~~~clojure\n(def handler\n  (make-handler config {:db (make-db ...)}))\n\n(handler {:id 1 :method ...} {:version \"0.2.1\"})\n~~~\n\nThe context maps are always merged, so from the function's point of view, there\nis only a single map.\n\nThe local context map gets merged into the global one. It gives you an\nopportunity to override the default values from the context. Let's say, if the\nmethod `:user/get-by-id` needs a special (read-only) database, we can override\nit like this:\n\n~~~clojure\n(def handler\n  (make-handler config {:db (make-db ...)}))\n\n(handler {:id 1 :method :user/get-by-id ...}\n         {:db read-only-db})\n~~~\n\n### Request \u0026 Response Formats\n\n#### Request\n\nAn RPC request is a map of the following fields:\n\n- `:id` is either a number or a string value representing this request. The handler\n  must return the same id in response unless it was a notification (see below).\n\n- `:method` is either a string or a keyword (the latter is preferred) that\n  specify the RPC method. If a method was a string, it gets coerced to the\n  keyword anyway. We recommend using the full qualified keywords with\n  namespaces. The namespaces help to group methods by semantic.\n\n- `:params` is either a map of [`keyword?`, `any?`] pairs, or a vector of `any?`\n  values (`sequential?`, if more precisely). This field is optional because some\n  methods don't require arguments.\n\n- `:jsonrpc`: a string with exact value `\"2.0\"`, the required one.\n\nExamples:\n\n```clojure\n;; all the fields\n{:id 1\n :method :math/sum\n :params [1 2]\n :jsonrpc \"2.0\"}\n\n;; no params\n{:id 2\n :method :app/version\n :jsonrpc \"2.0\"}\n\n;; no id (notification)\n{:method :user/delete-by-id\n :params {:id 3}\n :jsonrpc \"2.0\"}\n```\n\nThe RPC request might be of a batch form then it's a vector of such maps. Batch\nis useful to perform multiple actions per one call. See the \"Batch Requests\"\nsection below.\n\n#### Response\n\nThe response is map with the `:id` and `:jsonrpc` fields. The ID is the same you\npassed in the request so you can match the request and the response by ID. If\nthe response was positive, its `:result` field carries the value that the RPC\nfunction returned:\n\n~~~clojure\n{:id 1, :jsonrpc \"2.0\", :result 3}\n~~~\n\nA negative response has no the `:result` field but the `:error` one instead. The\nerror node consists from the `:code` and `:message` fields which are the numeric\ncode representing an error and a text message explaining it. In addition, there\nmight be the `:data` fields which is an arbitrary map with some extra\ncontext. The library adds the `:method` field to the context automatically.\n\n~~~clojure\n{:id 1\n :jsonrpc \"2.0\"\n :error {:code -32603\n         :message \"Internal error\"\n         :data {:method :math/div}}}\n~~~\n\n#### Error Codes\n\n- `-32700 Parse error`: Used then the server gets a non-JSON/broken payload.\n\n- `-32600 Invalid Request`: The payload is JSON but has a wrong shape.\n\n- `-32601 Method not found`: No such RPC method.\n\n- `-32602 Invalid params`: The parameters do not match the input spec.\n\n- `-32603 Internal error`: Either uncaught exception or the result doesn't match\n  the output spec.\n\n- `-32000 Authentication failure`: Something is wrong with auth/credentials.\n\n[jsonrpc-spec]: https://www.jsonrpc.org/specification\n\nFind more information about the error codes [on this page][jsonrpc-spec].\n\n### Notifications\n\nSometimes, you're not interested in the response from an RPC server. Say, if you\ndelete a user, there is nothing for you to return. In this case, you send a\nnotification rather than a request. Notifications are formed similar but have no\nthe `:id` field. The server sends nothing back for a notification. For example:\n\n~~~clojure\n(handler {:method :math/sum\n          :params [1 2]\n          :jsonrpc \"2.0\"})\n\nnil\n~~~\n\nNotifications are useful to trigger some side effects on the server.\n\nRemember, if you pass a missing method or wrong input data (or any other error\noccurs), you'll get a negative response anyway despite the fact it was a\nnotification:\n\n~~~clojure\n(handler {:method :math/sum\n          :params [1 \"a\"]\n          :jsonrpc \"2.0\"})\n\n{:error\n {:code -32602\n  :message \"Invalid params\"}\n :jsonrpc \"2.0\"}\n~~~\n\n### Batch Requests\n\nBatch requests is the main feature of JSON RPC. It allows you to send multiple\nrequest maps in one call. The server executes the requests and returns a list of\nresult maps. For example, you have a method `user/get-by-id` which takes a\nsingle ID and returns a user map from the database. Now you got ten IDs. With\nordinary REST API, you would run a cycle and performed ten HTTP calls. With RPC,\nyou make a batch call.\n\nIn our example, if we want to solve several math expressions at once, we do:\n\n~~~clojure\n(handler [{:id 1\n           :method :math/sum\n           :params [1 2]\n           :jsonrpc \"2.0\"}\n          {:id 2\n           :method :math/sum\n           :params [3 4]\n           :jsonrpc \"2.0\"}\n          {:id 3\n           :method :math/sum\n           :params [5 6]\n           :jsonrpc \"2.0\"}])\n~~~\n\nThe result:\n\n~~~clojure\n({:id 1 :jsonrpc \"2.0\" :result 3}\n {:id 2 :jsonrpc \"2.0\" :result 7}\n {:id 3 :jsonrpc \"2.0\" :result 11})\n~~~\n\nIf some of the tasks fail, they won't affect the others:\n\n~~~clojure\n(handler [{:id 1\n           :method :math/sum\n           :params [1 2]\n           :jsonrpc \"2.0\"}\n          {:id 2\n           :method :math/sum\n           :params [3 \"aaa\"]  ;; bad input\n           :jsonrpc \"2.0\"}\n          {:id 3\n           :method :math/missing ;; wrong method\n           :params [5 6]\n           :jsonrpc \"2.0\"}])\n~~~\n\nThe result:\n\n~~~clojure\n({:id 1 :jsonrpc \"2.0\" :result 3}\n {:error\n  {:code -32602\n   :message \"Invalid params\"\n   :data\n   {:explain ...\n    :method :math/sum}}\n  :id 2\n  :jsonrpc \"2.0\"}\n {:error\n  {:code -32601 :message \"Method not found\" :data {:method :math/missing}}\n  :id 3\n  :jsonrpc \"2.0\"})\n~~~\n\nYou can mix ordinary RPC tasks with notifications in a batch. There will be no\nresponse maps for notifications in the result vector:\n\n~~~clojure\n(handler [{:id 1\n           :method :math/sum\n           :params [1 2]\n           :jsonrpc \"2.0\"}\n          {:method :math/sum ;; no ID\n           :params [3 4]\n           :jsonrpc \"2.0\"}])\n\n[{:id 1 :jsonrpc \"2.0\" :result 3}]\n~~~\n\n#### Note on Parallelism\n\n[pmap]: https://clojuredocs.org/clojure.core/pmap\n\nBy default, Farseer uses the standard [`pmap` function][pmap] to deal with\nmultiple tasks. It executes the tasks in semi-parallel way. Maybe in the future,\nwe could use a custom fixed thread executor for more control.\n\n#### Configuring \u0026 Limiting Batch Requests\n\nThe following options help you to control batch requests:\n\n- `:rpc/batch-allowed?` (default is `true`): whether or not to allow batch\n  requests. If you set this to `false` and someone performs a batch call, they\n  will get an error:\n\n~~~clojure\n(def config\n  {:rpc/batch-allowed? false\n   :rpc/handlers ...})\n\n(def handler\n  (make-handler config))\n\n\n(handler [{:id 1\n           :method :math/sum\n           :params [1 2]\n           :jsonrpc \"2.0\"}\n          {:id 2\n           :method :math/sum\n           :params [3 4]\n           :jsonrpc \"2.0\"}])\n\n{:error {:code -32602, :message \"Batch is not allowed\"}}\n~~~\n\n- `:rpc/batch-max-size` (default is 25): the max number of tasks in a single\n  batch request. Sending more tasks than is allowed in one request would lead to\n  an error:\n\n~~~clojure\n(def config\n  {:rpc/batch-allowed? true\n   :rpc/batch-max-size 2\n   :rpc/handlers ...})\n\n(def handler\n  (make-handler config))\n\n\n(handler [{...} {...} {...}])\n\n{:error {:code -32602, :message \"Batch size is too large\"}}\n~~~\n\n- `:rpc/batch-parallel?` (default is `true`): whether or not to prefer `pmap`\n  over the standard `mapv` for tasks processing. When `false`, the tasks get\n  executed one by one.\n\n### Errors \u0026 Exceptions\n\n#### Runtime (Unexpected) Errors\n\nThe RPC handler wraps the whole logic into `try/catch` form with the `Throwable`\nclass. It means you'll get a negative response even if something weird happens\ninside it. Here is an example of unsafe division what might lead to exception:\n\n~~~clojure\n(defn rpc-div\n  [_ [a b]]\n  (/ a b))\n\n(def config\n  {:rpc/handlers\n   {:math/div\n    {:handler/function #'rpc-div}}})\n\n(def handler\n  (make-handler config))\n\n(handler {:id 1\n          :method :math/div\n          :params [1 0]\n          :jsonrpc \"2.0\"})\n\n{:id 1\n :jsonrpc \"2.0\"\n :error {:code -32603\n         :message \"Internal error\"\n         :data {:method :math/div}}}\n~~~\n\nAll the unexpected exceptions end up with the \"Internal error\" response with the\ncode -32603. In the console, you'll see the the logged exception:\n\n```\n10:19:35.948 ERROR farseer.handler - Divide by zero, id: 1, method: :math/div, code: -32603, message: Internal error\njava.lang.ArithmeticException: Divide by zero\n\tat clojure.lang.Numbers.divide(Numbers.java:188)\n\tat demo$rpc_div.invokeStatic(form-init9886809666544152192.clj:190)\n\tat demo$rpc_div.invoke(form-init9886809666544152192.clj:188)\n    ...\n```\n\n#### RPC (Expected) Errors\n\nSometimes, you know that you cannot serve the current request, and it must be\nfailed. The easiest way to end up the request is to throw an exception. But to\nget a proper RPC response, there should be a special exception with the fields\nthat take place in the response. The namespace `farseer.error` provides several\nfunctions for such exceptions.\n\nWhen an RPC handler catches an exception, it fetches its data using the\n`ex-data` function. Then it looks for some special fields to compose the\nresponse. Namely, these fields are:\n\n- `:rpc/code`: a number representing the error. When specified, it becomes the\n  `code` field of the error response.\n\n- `:rpc/message`: a string explaining the error. Becomes the `message` field of\n  the error response.\n\n- `:rpc/data`: a map with arbitrary data sent to the client. Becomes the `data`\n  field of the error response.\n\n- `:log/level`: a keyword representing the logging level of this error. Valid\n  values are those that the functions from `clojure.tools.logging` package\n  accept, e.g. `:debug`, `:info`, `:warn`, `:error`.\n\n- `:log/stacktrace?`: boolean, whether to log the entire stack trace or the\n  message only. Useful for \"method not found\" or \"wrong input\" cases because\n  there is no need for the full stack trace in such cases.\n\nThe data fetched from the exception instance gets merged with the default error\nmap declared in the `internal-error` variable:\n\n```clojure\n(def internal-error\n  {:log/level       :error\n   :log/stacktrace? true\n   :rpc/code        -32603\n   :rpc/message     \"Internal error\"})\n```\n\nThus, if you didn't specify some of the fields, they would come from this map.\n\n#### Raising Exceptions\n\nThere are some shortcut functions to simplify raising exceptions, namely:\n\n- `parse-error!`\n- `invalid-request!`\n- `not-found!`\n- `invalid-params!`\n- `internal-error!`\n- `auth-error!`\n\nExamples:\n\n- JSON parse error:\n\n```clojure\n(farseer.error/parse-error!)\n```\n\n- RPC Method is not found:\n\n```clojure\n(farseer.error/not-found!\n  {:rpc/message \"I don't have such method\"})\n```\n\n- Wrong input parameters:\n\n```clojure\n(farseer.error/invalid-params!\n  {:rpc/data {:spec-explain \"...\"}})\n```\n\n- Internal error:\n\n```clojure\n(farseer.error/internal-error! nil caught-exception)\n```\n\nThe signature of all these functions is `[\u0026 [data e]]` meaning that you can call\na function even without arguments. Each function has its own default `data` map\nthat gets merged to the `data` you passed. For example, these are default values\nfor the `invalid-params!` function:\n\n```clojure\n(def invalid-params\n  {:log/level       :info\n   :log/stacktrace? false\n   :rpc/code        -32602\n   :rpc/message     \"Invalid params\"})\n```\n\nThe logging level is `:info` because this is expected behaviour. We also we\ndon't log the whole stack trace for the same reason.\n\n### Configuration\n\n## Ring HTTP Handler\n\n[ring-json]: https://github.com/ring-clojure/ring-json\n\nThe Ring package creates an HTTP handler from an RPC configuration. The HTTP\nhandler follows the official Ring protocol: it's a function that takes an HTTP\nrequest map and returns a response map. The handler uses JSON format for\ntransport. It's already wrapped with [Ring JSON middleware][ring-json] that\ndecodes and encodes the payload. You can pass other middleware stack to use\nsomething other that JSON, say MessagePack or EDN.\n\nAdd the package:\n\n~~~clojure\n;; deps\n[com.github.igrishaev/farseer-http \"...\"]\n\n;; module\n(ns ...\n  (:require\n   [farseer.http :as http]))\n~~~\n\nThe package reuses the same config we wrote above. All the HTTP-related fields\nhave default values, so you can just pass the config to the `make-app` function:\n\n~~~clojure\n(def app\n  (http/make-app config))\n~~~\n\nNow let's compose the HTTP request for the app:\n\n~~~clojure\n(def rpc\n  {:id 1\n   :jsonrpc \"2.0\"\n   :method :math/sum\n   :params [1 2]})\n\n(def request\n  {:request-method :post\n   :uri \"/\"\n   :headers {\"content-type\" \"application/json\"}\n   :body (-\u003e rpc json/generate-string .getBytes)})\n~~~\n\nand call it like an HTTP server:\n\n~~~clojure\n(def response\n  (-\u003e (app request)\n      (update :body json/parse-string true)))\n\n{:status 200\n :body {:id 1 :jsonrpc \"2.0\" :result 3}\n :headers {\"Content-Type\" \"application/json; charset=utf-8\"}}\n~~~\n\n#### Negative Responses\n\nA quick example of how would the handler behave in case of an error:\n\n~~~clojure\n(def rpc\n  {:id 1\n   :jsonrpc \"2.0\"\n   :method :math/missing ;; wrong method\n   :params [nil \"a\"]})\n\n(def request\n  {:request-method :post\n   :uri \"/\"\n   :headers {\"content-type\" \"application/json\"}\n   :body (-\u003e rpc json/generate-string .getBytes)})\n\n(def response\n  (-\u003e (app request)\n      (update :body json/parse-string true)))\n\n{:status 200\n :body\n {:error\n  {:code -32601 :message \"Method not found\" :data {:method \"math/missing\"}}\n  :id 1\n  :jsonrpc \"2.0\"}\n :headers {\"Content-Type\" \"application/json; charset=utf-8\"}}\n~~~\n\nPay attention that the server **always** responds with the status code 200. This\nis the main deference from the REST approach. In RPC, HTTP is nothing else than\njust a transport layer. Its purpose is only to deliver messages without\ninterfering into the pipeline. It's up to you how to check if the RPC response\nwas correct or not. However, the HTTP client package (see below) provides an\noption to raise an exception in case of error response.\n\n#### Batch Requests in HTTP\n\nIf your configuration allows batch requests, you can send them via HTTP. For\nthis, replace the `rpc` variable above with the vector of RPC maps. The result\nwill be a vector of response maps.\n\n~~~clojure\n(def rpc\n  [{:id 1\n    :jsonrpc \"2.0\"\n    :method :math/sum\n    :params [1 2]}\n   {:id 2\n    :jsonrpc \"2.0\"\n    :method :math/sum\n    :params [3 4]}])\n\n(def request\n  ...)\n\n(def response\n  ...)\n\n{:status 200\n :headers {\"Content-Type\" \"application/json; charset=utf-8\"}\n :body ({:id 1 :jsonrpc \"2.0\" :result 3}\n        {:id 2 :jsonrpc \"2.0\" :result 7})}\n~~~\n\nEverything said above for batch requests also apply to HTTP as well.\n\n#### Configuration\n\nHere is a list of HTTP options the library supports:\n\n- `:http/method` (default is `:post`) is an HTTP method to listen. POST is the\n  one recommended by the RPC specification.\n\n- `:http/path` (default is `\"/\"`) URI path to listen. You may specify something\n  like `\"/api\"`, `\"/rpc\"` or similar.\n\n- `:http/health?` (default is `true`) whether or not the health endpoint is\n  available. When true, `GET /health` or `GET /healthz` requests receive an\n  empty `200 OK` response. This is useful for monitoring your server.\n\n- `:http/middleware` (default is the `farseer.http/default-middleware` vector) a\n  list of HTTP middleware that get applied to the HTTP handler. See the next\n  section.\n\n#### Middleware \u0026 Authorization\n\nBy default, the handler gets wrapped into a couple of middleware. These are the\nstandard `wrap-json-body` and `wrap-json-response` from the\n`ring.middleware.json` package. The first one is set up such that passing an\nincorrect JSON payload will return a proper RPC response (pay attention to the\nstatus 200):\n\n~~~clojure\n(app {:request-method :post\n      :uri \"/\"\n      :headers {\"content-type\" \"application/json\"}\n      :body (.getBytes \"1aaa-\")})\n\n{:status 200,\n :headers {\"Content-Type\" \"application/json\"},\n :body \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"error\\\":{\\\"code\\\":-32700,\\\"message\\\":\\\"Invalid JSON was received by the server.\\\"}}\"}\n~~~\n\nNote: we use the `wrap-json-body` middleware but not `wrap-json-params` to make\nit work with batch requests. As the payload is not a map, it cannot be merged\nwith the `:params` field.\n\nThe `:http/middleware` parameter must be a vector of middleware. Each middleware\nis either a function or a vector of `[function, arg2, arg3, ...]`. In the second\ncase, it will be applied to the handler as `(apply function handler arg2, arg3,\n...)`. For example, if a middleware takes additional params, you specify it as a\nvector `[middleware, params]`.\n\nYou can bring your own logic into the HTTP pipeline by overriding the\n`:http/middleware` field. Here is a quick example how you protect the handler\nwith Basic Auth:\n\n~~~clojure\n;; ns imports\n[farseer.http :as http]\n[ring.middleware.basic-authentication :refer [wrap-basic-authentication]]\n\n;; preparing a middleware stack\n(let [fn-auth?\n      (fn [user pass]\n        (and (= \"foo\" user)\n             (= \"bar\" pass)))\n\n      middleware-auth\n      [wrap-basic-authentication fn-auth? \"auth\" http/non-auth-response]\n\n      middleware-stack [middleware-auth\n                        http/wrap-json-body\n                        http/wrap-json-resp]\n\n      config*\n      (assoc config :http/middleware middleware-stack)]\n  ...)\n~~~\n\nAlso, you can replace JSON middleware with the one that uses some other format\nlike MessagePack, Transient or whatever.\n\n#### HTTP Context\n\nThe function `http/make-app` also takes an additional context map. This map will\nbe merged with the data the RPC function accepts when being called.\n\n~~~clojure\n(def app\n  (make-app config {:app/version \"0.0.1\"}))\n\n(defn rpc-func [context params]\n  {:message (str \"The version is \" (:app/version context))})\n~~~\n\nThe HTTP handler adds the `:http/request` item into the context. This is the\ninstance of the request map that the handler accepted. Having the request, you\ncan handle some extra logic in your function. For example, some middleware\nsupplements the request with the `:user` field, and you check if the user has\npermissions.\n\n~~~clojure\n(defn some-rpc [context params]\n  (let [{:http/keys [request]} context\n        {:keys [user]} request]\n    (when-not request\n      (throw ...))))\n~~~\n\n## Jetty Server\n\nThe Jetty sub-package allows you to run an RPC server using Jetty Ring\nadapter. Add it to the project:\n\n~~~clojure\n;; deps\n[com.github.igrishaev/farseer-jetty \"...\"]\n\n;; require\n(require '[farseer.jetty :as jetty])\n~~~\n\nAll the Jetty config fields have default values, so we pass a minimal config\nwe've been using so far.\n\n~~~clojure\n(def server\n  (jetty/start-server config))\n\n;; #object[org.eclipse.jetty.server.Server 0x3e82fe49 \"Server@3e82fe49{STARTED}[9.4.12.v20180830]\"]\n~~~\n\nThe default port is 8080. Now that your server is being run, test it with cURL:\n\n~~~bash\ncurl -X POST 'http://127.0.0.1:8080/' \\\n  --data '{\"id\": 1, \"jsonrpc\": \"2.0\", \"method\": \"math/sum\", \"params\": [1, 2]}' \\\n  -H 'content-type: application/json' | jq\n\n{\n  \"id\": 1,\n  \"jsonrpc\": \"2.0\",\n  \"result\": 3\n}\n~~~\n\nPay attention to the `content-type` header. Without it, the request payload won't\nbe decoded and the call will fail.\n\nTo stop the sever, pass it to the `stop-server` function:\n\n~~~clojure\n(jetty/stop-server server)\n~~~\n\nThe `start-server` function also accepts a second optional argument which is a\ncontext map.\n\n### Configuration\n\n[jetty-params]: https://github.com/ring-clojure/ring/blob/master/ring-jetty-adapter/src/ring/adapter/jetty.clj#L162\n\n- `:jetty/port` (8080 by default) is the port to listen to.\n\n- `:jetty/join?` (`false` by default) is whether or not to wait for the server\n  being stopped. When it's `true`, the main thread hangs until you press\n  Ctrl-C.\n\n- any other [Jetty-related keys][jetty-params] with the `:jetty/` namespace, for\n  example: `:jetty/ssl-context`, `:jetty/max-threads` and so on. The library\n  will scan the config for the `:jetty/`-prefixed keys, select them, unqualify\n  and pass to the `run-jetty` function.\n\n### With-server macro\n\nThe macro `with-server` temporary spawns an RPC server. It accepts a config, an\noptional context map and a block of code to execute.\n\n~~~clojure\n(jetty/with-server [config {:foo 42}]\n  (println 1 2 3))\n~~~\n\n### Component\n\n[component]: https://github.com/stuartsierra/component\n\nThe Jetty package also provides a component object for use with the [Component\nlibrary][component]. The `jetty/component` function creates a component. It\ntakes a config and an optional context map.\n\n~~~clojure\n(def jetty\n  (jetty/component config {:some \"field\"}))\n~~~\n\nNow that you have an initiated component, you can start and stop it with\nfunctions from the Component library.\n\n~~~clojure\n(require '[com.stuartsierra.component :as component])\n\n(def jetty-started\n  (component/start jetty))\n\n;; The server starts working\n\n(def jetty-stopped\n  (component/stop jetty-started))\n\n;; Now it's shut down.\n~~~\n\nOf course, it's better to place the component into a system. One more benefit of\na system is, all the dependencies will become a context map. For example, if\nyour Jetty component depends on the database, cache, Kafka, and waterier else,\nyou'll have them all in the context map.\n\n~~~clojure\n(defn make-system\n  [rpc-config db-config cache-config]\n  (component/system-using\n   (component/system-map\n    :cache (cache-component cache-config)\n    :db-pool (db-component db-config)\n    :rpc-server (jetty/component rpc-config))\n   {:rpc-server [:db-pool :cache]}))\n~~~\n\nThe function above will make a new system which consists from the RPC server,\ncache and database pooling connection. Once the system gets started, the context\nmap of all RPC functions will have the `:db-pool` and `:cache` keys. Here is how\nyou reach them in your RPC function:\n\n~~~clojure\n(defn rpc-user-get-by-id\n  [{:keys [db-pool cache]} [user-id]]\n  (or (get-user-from-cache cache user-id)\n      (get-user-from-db db-pool user-id)))\n~~~\n\n## HTTP Stub\n\nThe Stub package provides a couple of macros for making HTTP RPC stubs. These\nare local HTTP servers that run on your machine. The difference with the Jetty\npackage is that a stub returns a pre-defined data which is useful for testing.\n\nImagine you have a piece of code that interacts with two RPC endpoints. To make\nthis code well tested, you need to cover the cases:\n\n- both sources work fine;\n- the first one works, the second returns an error;\n- the first one is unavailable, the second one works;\n- neither of them work.\n\nThe package provides the `with-stub` macro which accepts a config map and a\nblock of code. The config must have the `:stub/handlers` field which is a map of\nmethod =\u003e result. For example:\n\n~~~clojure\n(def config\n  {:stub/handlers\n   {:user/get-by-id {:name \"Ivan\"\n                     :email \"test@test.com\"}\n    :math/sum 42}})\n~~~\n\nAs the Stub package works on top of Jetty, it takes into account all the Jetty\nkeys. To specify the port number, pass the `:jetty/port` field to the config:\n\n~~~clojure\n(def config\n  {:jetty/port 18080\n   :stub/handlers {...}})\n~~~\n\nIn the example above, we defined the handlers such that the methods\n`:user/get-by-id` and `:math/sum` would always return the same response.\n\nTo run a server out from this config, there is the macro `with-stub`:\n\n~~~clojure\n(stub/with-stub config\n  ;; Execute any expressions\n  ;; while the RPC server is running.\n  )\n~~~\n\nWhile the server is running, you can reach it as you normally do with any HTTP\nclient. If you send either `:user/get-by-id` or `:math/sum` requests to it,\nyou'll get the result you defined in the config. Quick check with cURL:\n\n~~~bash\ncurl -X POST 'http://127.0.0.1:8080/' \\\n  --data '{\"id\": 1, \"jsonrpc\": \"2.0\", \"method\": \"math/sum\", \"params\": [1, 2]}' \\\n  -H 'content-type: application/json' | jq\n\n{\n  \"id\": 1,\n  \"jsonrpc\": \"2.0\",\n  \"result\": 42\n}\n~~~\n\n#### Multiple Stub\n\nThere is a multiple version of this macro called `with-stub`. It's useful when\nyou interact with more than one RPC server at once. The macro takes a vector of\nconfig maps. For each one, it runs a local HTTP Stub. All of them get stopped\nonce you exit the macro.\n\n~~~clojure\n(stub/with-stubs [config1 config1 ...]\n  ...)\n~~~\n\n#### Tests\n\nTo test you application with stubs, you need:\n\n- define a port for the HTTP stub, e.g. 18080;\n- pass this port to the stub config: `:jetty/port ...`;\n- wrap the testing code with the `with-stub` macro;\n- Aim the code which interacts with RPC at the local address like this one:\n  `http://127.0.0.1:18080/...`.\n\n[stub_test]: https://github.com/igrishaev/farseer/blob/master/farseer-stub/test/farseer/stub_test.clj\n\nHaving everything said above, you can easily check how does your application\nbehave when getting a positive or a negative responses from an RPC server. You\ncan check out the source code of the [testing module][stub_test] as an example.\n\n#### Negative Responses\n\nThe result of a method can be not only regular data but also a function. Inside\nit, you can raise an exception or even trigger something weird to imitate a\ndisaster. For example, to divide by zero:\n\n~~~clojure\n(def config\n  {:stub/handlers\n   {:some/failure\n    (fn [\u0026 _]\n      (/ 0 0))}})\n~~~\n\nThis would lead to a real exception on the server side. Another way of\ntriggering a negative response is to pass one of the predefined functions:\n\n- `stub/invalid-request`\n- `stub/not-found`\n- `stub/invalid-params`\n- `stub/internal-error`\n- `stub/auth-error`\n\nPassing them will return an RPC error result. If you want to play the scenario\nwhen a user is not authenticated on the server, compose the config:\n\n~~~clojure\n(def config\n  {:stub/handlers\n   {:user/get-by-id stub/auth-error}})\n~~~\n\n## HTTP Client\n\nThe Client package is to communicate with an RPC Server by HTTP protocol. It\nrelies on `clj-http` library for making HTTP requests. Add it to the project:\n\n~~~clojure\n;; deps\n[com.github.igrishaev/farseer-client ...]\n\n;; module\n[farseer.client :as client]\n~~~\n\nTo reach an RPC server, first you create an instance of the client. This is done\nwith the `make-client` function which accepts the config map:\n\n~~~clojure\n(def config-client\n  {:http/url \"http://127.0.0.1:18080/\"})\n\n(def client\n  (client/make-client config-client))\n~~~\n\nThere is only one mandatory field in the config: the `:http/url` one which is\nthe endpoint of the server. Other fields have default values.\n\nFor further experiments we will spawn a local Jetty RPC server and will work\nwith it using the client.\n\n~~~clojure\n(def config\n  {:jetty/port 18080\n   :rpc/handlers\n   {:math/sum\n    {:handler/function #'rpc-sum\n     :handler/spec-in :math/sum.in\n     :handler/spec-out :math/sum.out}}})\n\n(def server\n  (jetty/start-server config))\n~~~\n\nOnce you have the client, make a request with the `client/call` function. It\naccepts the client, method, and optional parameters.\n\n~~~clojure\n(def response\n  (client/call client :math/sum [1 2]))\n\n;; {:id 81081, :jsonrpc \"2.0\", :result 3}\n~~~\n\nThe parameters might be either a vector or map. If the method doesn't accept\nparameters, you may omit them.\n\n~~~clojure\n;; map params\n(client/call client :user/create\n             {:name \"Ivan\" :email \"test@test.com\"})\n\n;; no params\n(client/call client :some/side-effect)\n~~~\n\nAn example of a negative response:\n\n~~~clojure\n(client/call client :math/sum [nil \"a\"])\n\n{:error\n {:code -32602,\n  :message \"Invalid params\",\n  :data\n  {:explain\n   \"nil - failed: number? in: [0] at: [0] spec: :math/sum.in\\n\\\"a\\\" - failed: number? in: [1] at: [1] spec: :math/sum.in\\n\",\n   :method \"math/sum\"}},\n :id 73647,\n :jsonrpc \"2.0\"}\n~~~\n\nYou won't get an exception; the result shown above is just data. If you prefer\nexceptions, you can adjust the client configuration (see below).\n\n#### Configuration\n\nThe following fields affect the client's behaviour.\n\n- `:rpc/fn-before-send` (default is `identity`) a function which is called\n  before the HTTP request gets sent to the server. It accepts the Clj-http\n  request map and should return it as well. The function useful for signing\n  requests, authentication and so on.\n\n- `:rpc/fn-id` (default is `:id/int`) determines an algorithm for generating\n  IDs. The `:id/int` value means an ID will be a random integer; `:id/uuid`\n  stands for a random UUID. You can also pass a custom function of no arguments\n  that must return either an integer or a string.\n\n- `:rpc/ensure?` (default is `false`) when false, return the body of the\n  response as is. When true, either return the `:result` field of the body or\n  throw an exception if the `:error` field presents (see below).\n\n- `:http/method` (default is `:post`) an HTTP method for request.\n\n- `:http/headers` (default is `{:user-agent \"farseer.client\"}`) a map of HTTP\n  headers.\n\n- `:http/as` (default is `:json`) how to treat the response body.\n\n- `:http/content-type` (`:json`) how to encode the request body.\n\nThe HTTP package takes into account all the keys prefixed with the `:http/`\nnamespace. These are the standard Clj-http keys, .e.g `:http/socket-timeout`,\n`:http/throw-exceptions?` and others, so you configure the HTTP part as you\nwant. When making a request, the client scans the config for the\n`:http/`-prefixed keys, selects them, removes the namespace and passes to the\n`clj-http/request` function as a map.\n\nThe `:conn-mgr/` keys specify options for the connection manager:\n\n- `:conn-mgr/timeout`\n- `:conn-mgr/threads`\n- `:conn-mgr/default-per-route`\n- `:conn-mgr/insecure?`\n\nand others. They have default values copied from Clj-http. The connection\nmanager is not created by default. You need to setup it manually (see below).\n\n#### Handling Responses\n\nBy default, calling the server just returns the body of the HTTP response. It's\nup to you how to handle the `:result` and `:error` fields. Sometimes, the good\nold exception-based approach is convenient: you either get a result or an error\npops up.\n\nThe `:rpc/ensure?` option is exactly for that. When it's false, you get a parsed\nbody of the HTTP response. When it's true, the following logic takes control:\n\n- for a positive response (no `:error` field) you'll get the content of the\n  `:result` field. For example:\n\n~~~clojure\n(def config-client\n  {:rpc/ensure? true\n   :http/url \"http://127.0.0.1:18080/\"})\n\n(def client\n  (client/make-client config-client))\n\n(client/call client :math/sum [1 2])\n;; 3\n~~~\n\nFor a negative response, you'll get an exception:\n\n~~~clojure\n(client/call client :math/sum [1 \"two\"])\n\n17:04:34.780 INFO  farseer.handler - RPC error, id: 94415, method: math/sum, code: -32602, message: Invalid params\n\nUnhandled clojure.lang.ExceptionInfo\nRPC error, id: 94415, method: :math/sum, code: -32602, message: Invalid\nparams\n#:rpc{:id 94415,\n      :method :math/sum,\n      :code -32602,\n      :message \"Invalid params\",\n      :data\n      {:explain \"\\\"two\\\" - failed: number? in: [1] at: [1] spec: :math/sum.in\\n\",\n       :method \"math/sum\"}}\n             client.clj:  122  farseer.client/ensure-handler\n             client.clj:   97  farseer.client/ensure-handler\n             client.clj:  148  farseer.client/make-request\n             client.clj:  128  farseer.client/make-request\n             client.clj:  187  farseer.client/call\n             client.clj:  179  farseer.client/call\n~~~\n\nAt the moment, the `:rpc/ensure?` option doesn't affect batch requests (see\nbelow).\n\n#### Auth\n\n[clj-http-auth]: https://github.com/dakrone/clj-http#authentication\n\nHandling authentication for the client is simple. Clj-http [already\ncovers][clj-http-auth] most of the authentication types, so you only need to\npass proper options to the config. If the server is protected with Basic auth,\nyou extend the config with the `:http/basic-auth` field:\n\n~~~clojure\n(def config-client\n  {:http/url \"http://127.0.0.1:18080/\"\n   :http/basic-auth [\"user\" \"password\"]})\n~~~\n\nFor oAuth2, you pass another key:\n\n~~~clojure\n(def config-client\n  {:http/url \"http://127.0.0.1:18080/\"\n   :http/oauth-token \"***********\"})\n~~~\n\nIf the server requires a constant token, you put it directly into headers:\n\n~~~clojure\n(def config-client\n  {:http/url \"http://127.0.0.1:18080/\"\n   :http/headers {\"authorization\" \"Bearer *********\"}})\n~~~\n\nFinally, the `:rpc/fn-before-send` parameter allows your to do everything with\nthe request before it gets sent to the server. There might be a custom function\nwhich supplements the request with additional headers that are calculated on the\nfly. For example:\n\n~~~clojure\n(defn sign-request\n  [{:as request :keys [body]}]\n  (let [body-hash (calc-body-hash body)\n        sign (sign-body-hash body-hash \"*******\")\n        header (str \"Bearer \" sign)]\n    (assoc-in request [:headers \"authorization\"] header)))\n\n(def config-client\n  {:http/url \"http://127.0.0.1:18080/\"\n   :rpc/fn-before-send sign-request})\n~~~\n\n#### Notifications\n\nA notification is when you're not interested in the response from the server. To\nsend a notification, use the `client/notify` function. Its signature looks the\nsame: the client, method, and optional params. The result will be `nil`.\n\n~~~clojure\n(client/notify client :math/sum [1 2])\n;; nil\n~~~\n\n#### Batch Requests\n\nTo send batch requests, there is the `client/batch` function. It takes the\nclient and a vector of tasks. Each task is a pair of (method, params).\n\n~~~clojure\n(client/batch client\n              [[:math/sum [1 2]]\n               [:math/sum [2 3]]\n               [:math/sum [3 4]]])\n\n[{:id 51499 :jsonrpc \"2.0\" :result 3}\n {:id 45992 :jsonrpc \"2.0\" :result 5}\n {:id 84590 :jsonrpc \"2.0\" :result 7}]\n~~~\n\nSome important notes on batches:\n\n- There will be only one HTTP request.\n\n- The order of the result maps always match the order of the tasks.\n\n- If one of the tasks fails, you'll get a negative map for it. The whole request\n  won't fail.\n\n~~~clojure\n(client/batch client\n              [[:math/sum [1 2]]\n               [:math/sum [\"aa\" nil]]\n               [:math/sum [3 4]]])\n\n[{:id 75623 :jsonrpc \"2.0\" :result 3}\n {:error\n  {:code -32602\n   :message \"Invalid params\"\n   :data\n   {:explain \"\\\"aa\\\" - failed: number? in: [0] at: [0] spec: :math/sum.in\\nnil - failed: number? in: [1] at: [1] spec: :math/sum.in\\n\"\n    :method \"math/sum\"}}\n  :id 43075\n  :jsonrpc \"2.0\"}\n {:id 13160 :jsonrpc \"2.0\" :result 7}]\n~~~\n\nThe `:rpc/ensure?` option doesn't apply to batch requests (which is a subject to\nchange in the future).\n\nSometimes, you want one of the tasks in a batch to be a notification. To make a\ntask a notification, prepend its vector with the `^:rpc/notify` metadata tag:\n\n~~~clojure\n(client/batch client\n              [[:math/sum [1 2]]\n               ^:rpc/notify [:math/sum [2 3]]\n               [:math/sum [3 4]]])\n\n[{:id 54810 :jsonrpc \"2.0\" :result 3}\n {:id 34377 :jsonrpc \"2.0\" :result 7}]\n~~~\n\n#### Connection Manager (Pool)\n\n[pool]: https://github.com/dakrone/clj-http#persistent-connections\n\nClj-http offers a [connection manager][pool] for HTTP requests. It's a pool of\nopen TCP connections. Sending requests within a pool is much faster then opening\nand closing connections every time. The package provides some bits to handle\nconnection manager for the client.\n\nThe function `client/start-conn-mgr` takes a client and returns it with the new\nconnection manager associated under the `:http/connection-manager` key. If you\npass the new client to the `client/call` function, it will take the manager into\naccount, and the request will work faster.\n\nThe function considers the keys which start with the `:conn-mgr/`\nnamespace. These keys become a map of standard parameters for connection\nmanager.\n\n~~~clojure\n(def config-client\n  {:conn-mgr/timeout 5\n   :conn-mgr/threads 4\n   :http/url \"http://127.0.0.1:18080/\"})\n\n(def client\n  (-\u003e config-client\n      client/make-client\n      client/start-conn-mgr))\n~~~\n\nThe opposite function `client/stop-conn-mgr` stops the manager (if present) and\nreturns the client without the key.\n\n~~~clojure\n(client/stop-conn-mgr client)\n~~~\n\nThe macro `client/with-conn-mgr` enables the connection manager temporary. It\ntakes a binding form and a block of code to execute. Inside the macro, the\nclient is bound to the first symbol from the vector form.\n\n~~~clojure\n;; a client without a pool\n(def client\n  (client/make-client config-client))\n\n;; temporary assing a pool\n(client/with-conn-mgr [client-mgr client]\n  (client/call client-mgr :math/sum [1 2]))\n~~~\n\n#### Component\n\nSince the client might have a state (a connection manager), you can put it into\nthe system. There is a function `client/component` which returns an HTTP client\ncharged with the `start` and `stop` methods. These methods turn on and off\nconnection pool for the client.\n\n~~~clojure\n;; no pool yet\n(def client\n  (client/component config-client))\n\n;; enabling the pool\n(def client-started\n  (component/start client))\n\n;; closing the pool\n(component/stop client-started)\n~~~\n\n## Documentation Builder\n\nThe config map for the server has enough data to be rendered as a document. It\nwould be nice to pass it into a template and generate a file each time you build\nor the application. The Docs package serves exactly for this purpose.\n\nAdd the `com.github.igrishaev/farseer-doc` library into your project:\n\n~~~clojure\n;; deps\n[com.github.igrishaev/farseer-doc ...]\n\n;; ns\n[farseer.doc :as doc]\n~~~\n\nPay attention that generating a docfile is usually a separate task, but not a\npart of business logic. That's why the application **must not include that\nlibrary in production**. The `:dev`-specific dependencies would be a better\nplace for this package.\n\n### Configuration\n\nTo generate a doc file, you extend the server config with the keys that have\n`:doc/` namespace. Here is an example:\n\n~~~clojure\n(def config\n  {:doc/title \"My API\"\n   :doc/description \"Long API Description\"\n\n   :rpc/handlers\n   {:user/delete\n    {:doc/title \"Delete a user by ID\"\n     :doc/description \"Long text for deleting a user.\"\n     :handler/spec-in pos-int?\n     :handler/spec-out (s/keys :req-un [:api/message])}\n\n    :user/get-by-id\n    {:doc/title \"Get a user by ID\"\n     :doc/description \"Long text for getting a user.\"\n     :doc/ignore? false\n     :doc/resource \"docs/user-get-by-id.md\"\n     :handler/spec-in int?\n     :handler/spec-out\n     (s/map-of keyword? (s/or :int int? :str string?))}\n\n    :hidden/api\n    {:doc/title \"Non-documented API\"\n     :doc/ignore? true\n     :handler/spec-in any?\n     :handler/spec-out any?}}})\n~~~\n\nThe the list of the fields used for documentation:\n\n- `:doc/title` (string). A title of an API or an RPC method.\n\n- `:doc/description` (string). A description of an API or an RPC method.\n\n- `:doc/resource` (string). A path to a resource with the detailed text with\n  examples, edge cases and so on. Useful for large chunks of text.\n\n- `:doc/endpoint` (string). An URL of this RPC server.\n\n- `:doc/ignore?` (boolean, `false` by default). When true, the method is not\n  included into the documentation.\n\n- `:doc/sorting` (keyword, `:method` or `:title`). How to sort RPC methods. The\n  `:method` keyword means to sort by machine names, e.g. `:user/get-by-id`. The\n  `:title` means to sort by the `:doc/title` field, e.g. \"Get user by ID\".\n\n### Building\n\nOnce you have a documentation-powered config, render it with the `generate-doc`\nfunction:\n\n~~~clojure\n(doc/generate-doc\n   config\n   \"templates/farseer/default.md\"\n   \"dev-resources/default-out.md\")\n~~~\n\n[doc-default]: https://raw.githubusercontent.com/igrishaev/farseer/master/farseer-doc/resources/templates/farseer/default.md\n\nThis function takes a config map, a resource template and a path of the output\nfile. The Doc package provides the [default Markdown template][doc-default] which\ncan be found by the path `\"templates/farseer/default.md\"`.\n\nIn your project, most likely you create a `dev` namespace with this function\nthat builds the documentation file. Every time the application gets run on CI,\nyou generate a file and host it somewhere.\n\n### Demo\n\n[doc-demo]: https://github.com/igrishaev/farseer/blob/master/farseer-doc/dev-resources/default-out.md\n\nYou can checkout a [real demo][doc-demo] generated by the test module. The file\nlists all the non-ignored methods and their specs. The specs are put under\ncollapsible items as sometimes they might be huge.\n\n### Selmer \u0026 Context\n\n[selmer]: https://github.com/yogthos/Selmer\n\nThe Doc package uses the great [Selmer library][selmer] which is inspired by\nDjango Templates. You can pass your own template, and not only Markdown one, but\nHTML, AsciiDoc, or LaTeX. The template might have any graphic elements, your\nlogo, JavaScript, and so on.\n\nThe Doc package passes the config not directly but with transformation. Here is\nan example of the context map that you have when rendering a template. Note that\nall the keys are free from namespaces. The `:handlers` field is not a map but a\nvector of maps sorted according to the `:doc/sorting` option.\n\n~~~clojure\n{:title \"My API\"\n :description \"Long API Description\"\n :resource nil\n :handlers\n ({:method \"user/delete\"\n   :title \"Delete a user by ID\"\n   :description \"Long text for deleting a user.\"\n   :resource nil\n   :spec-in {:type \"integer\" :format \"int64\" :minimum 1}\n   :spec-out\n   {:type \"object\"\n    :properties {\"message\" {:type \"string\"}}\n    :required [\"message\"]}}\n  {:method \"user/get-by-id\"\n   :title \"Get a user by ID\"\n   :description \"Long text for getting a user.\"\n   :resource \"\\n### Get user by ID examples\\n\\n........\"\n   :spec-in {:type \"integer\" :format \"int64\"}\n   :spec-out\n   {:type \"object\"\n    :additionalProperties\n    {:anyOf [{:type \"integer\" :format \"int64\"} {:type \"string\"}]}}})}\n~~~\n\n### Rendering Specs\n\n[spec-tools]: https://github.com/metosin/spec-tools\n[json-schema]: https://github.com/metosin/spec-tools/blob/master/docs/04_json_schema.md\n\nThe `:handler/spec-in` and `:handler/spec-out` fields get transformed to JSON\nSchema using the [Spec-tools][spec-tools] library. You may see the result of\ntransformation in the context map above. For more control of transformation,\ncheck out [the manual page][json-schema] from the Spec-tools repository.\n\nTo render the spec in a template, use the `json-pretty` filter. It turns the\nClojure data into a JSON string being well printed. To prevent quoting some\nsymbols, add the `safe` filter to the end. Everything together gives the\nfollowing snippet:\n\n```jinja\n{% if handler.spec-in %}\n\u003cdetails\u003e\n\u003csummary\u003eIntput schema\u003c/summary\u003e\n\n~~~json\n{{ handler.spec-in|json-pretty|safe }}\n~~~\n\n\u003c/details\u003e\n{% endif %}\n```\n\nPay attention to the empty lines before and after the JSON code block. Without\nthem, GitHub renders the content in a weird way.\n\n## Ideas \u0026 Further Development\n\nIt would be nice to:\n\n- Keep the entire server config in an EDN file. The functions should be resolved\n  by their full symbols.\n\n- Provide a nested map like method =\u003e overrides. With this map, one could\n  specify custom options for specific methods. For example, to enable batch\n  requests in common, but disallow them for specific methods.\n\n[fetch-api]:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\n\n- Develop a browser version of the client. The module would rely on [Fetch\n  API][fetch-api].\n\n- Create a wrapper for re-frame. Instead of calling functions, one triggers\n  events.\n\n## Author\n\nIvan Grishaev, 2021\nhttps://grishaev.me\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Ffarseer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figrishaev%2Ffarseer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Ffarseer/lists"}