lib/phx_json_rpc.ex

defmodule PhxJsonRpc do
  @moduledoc """
  A simple implementation of the [JSON-RPC](https://www.jsonrpc.org/specification).

  JSON-RPC is a remote procedure call protocol encoded in JSON.
  It is similar to the XML-RPC protocol, defining only a few data types and commands.

  JSON-RPC allows for notifications (data sent to the server that does not require a response)
  and for multiple calls to be sent to the server which may be answered asynchronously.

  ## Getting started

  Add `:phx_json_rpc` to your dependencies

  ```
  {:phx_json_rpc, "~> 0.6.0"}
  ```

  ## Usage with phoenix

  1. Prepare your service specification, written as JSON SCHEMA

      The bunch of examples can be found [here](https://github.com/open-rpc/examples).

      Usually it should be stored as a dependency or just a file in your repo (for ex `myapp/priv/static/openrpc.json`).

  2. Configure rpc router, specifying json schema version and batch size params

  ```
  defmodule MyApp.Rpc.Router do
    use PhxJsonRpc.Router,
      otp_app: :rpc_router,
      schema: "[PATH_TO_YOUR_SCHEMA]",
      version: "2.0",
      max_batch_size: 20

    alias MyAppRpc.PetController

    ## Middleware group (optional)
    # Uncomment the line below, when neccessary (see `PhxJsonRpc.Router.Middleware` for usage)
    # middleware([AuthMiddleware])

    ## Pet's service
    rpc("pet.create", PetController, :create, "#/components/schemas/Pet")
    rpc("pet.list", PetController, :list, "#/components/schemas/Pets")
    rpc("pet.update", PetController, :update, "#/components/schemas/Pet")
    rpc("pet.delete", PetController, :delete, "#/components/schemas/PetId")
  end
  ```

  3. Specify service module

  ```
  defmodule MyAppRpc.PetController do
    @moduledoc "My Pet Service"

    def create(%{"name" => name}, context) do
      "Created"
    end

    def list(_params, context) do
      [
        "Cat",
        "Dog"
      ]
    end

    def update(params, context) do
      "Update your pet here"
    end

    def delete(%{"id" => id}, context) do
      "Pet removed from the store"
    end
  end
  ```

  4. Create controller for handling requests via http

  ```
  defmodule MyAppWeb.RpcController do
    use MyAppWeb, :controller

    alias MyApp.Rpc.Router

    def rpc(conn, request) do
      # Handles rpc requests
      # The second parameter is optional and used to share current context
      response = Router.handle(request, conn)
      render(conn, "response.json", response)
    end
  end
  ```

  5. Import helpers inside your view and define render function

  ```
  defmodule MyAppWeb.RpcView do
    use MyAppWeb, :view

    import PhxJsonRpc.Views.Helpers

    def render("response.json", %{response: response}) do
      render_json(response)
    end
  end
  ```

  Optionally, you can `import PhxJsonRpc.Views.Helpers` inside the `view_helpers` quote block in your `web.ex` like so:

  ```
    defp view_helpers do
      quote do
        ...

        import PhxJsonRpcWeb.Views.Helpers
      end
    end
  ```

  6. Add endpoint path to your phoenix router under the `api` scope

  ```
  scope "/api", MyAppWeb do
    pipe_through :api

    post("/rpc", RpcController, :rpc)
  end
  ```

  7. Start phoenix server and make an http request

  ```
  curl -X POST \
     -H 'Content-Type:application/json' \
     -d '{"jsonrpc":"2.0","id":"[ID]","method":"pet.create","params":{"name":"Kitty"}}' \
     http://localhost:4000/api/rpc
  ```

  Request and response using postman:

  ![request](test/priv/static/assets/request.png "request")

  ![response](test/priv/static/assets/response.png "response")

  """
end