View Source Craftgate.Adapter (Craftgate v1.0.42)

Represents an adapter that can be used to interact with a group of related endpoints.

Contains the endpoint/2 macro which can be used to quickly declare methods to access the said endpoints.

To import this macro simply use the Craftgate.Adapter module in your module, e.g.:

defmodule MyAdapter do
  use Craftgate.Adapter

  endpoint retrieve_foo(id: integer()), get: "/foo/v1/foos/:id", return: FooResponse
end

Link to this section Summary

Functions

Declares a method pair that can be used to execute a request against the specified endpoint.

Link to this section Functions

Link to this macro

endpoint(arg, opts)

View Source (macro)

Declares a method pair that can be used to execute a request against the specified endpoint.

Of the generated methods, one carries over the specified name as-is and returns an OK/error tuple with the desired response type (e.g. {:ok, %Craftgate.Response.PaymentResponse{...}} or {:error, %Craftgate.Error{...}}), and the other contains its "bangified" implementation, which either raises an error or returns the desired type.

Both methods will have their typespecs declared automatically, and the un-bangified method will retain its @doc statement.

Also, both methods will have an additional, optional parameter in the end called options which can be used to specify custom options on a particular request.

So to retrieve a resource of type Foo with the given ID and parse it as a FooResponse you can do:

defmodule FooAdapter do
  use Adapter

  endpoint retrieve_foo(id: integer()), get: "/foo/v1/foos/:id", return: FooResponse
end

Which then gets expanded to the following Elixir code

@spec retrieve_foo(integer(), keyword()) :: {:ok, FooResponse} | {:error, any()}
def retrieve_foo(id, options \\ []) do
  ...
end

@doc "Bangified version of the method with the same name
"
@spec retrieve_foo!(integer(), keyword()) :: FooResponse | no_return
def retrieve_foo!(id, options \\ []) do
  ...
end

Note that arguments are specified as a keyword list where the keys represent the argument names (which can then be expanded into the URL via the :<arg_name> syntax) and the values represent the types of each argument, the HTTP method and the destination URL are specified by the get: "..." options, and the expected successful response type is specified as the return option.

Which will allow you to call the FooAdapter.retrieve_foo/1 method like this:

# retrieving the result as a tuple
{:ok, %FooResponse{...}} = FooAdapter.retrieve_foo(42)

# or having the errors be raised
%FooResponse{...} = FooAdapter.retrieve_foo!(42)

Note that in both cases, the argument id will be expanded into the :id path segment specified in the URL and the get key will expand into the HTTP method GET, so in the end a GET request will be sent to /foo/v1/foos/42. Once the request executes, successful results will be parsed as the struct FooResponse, and failed results will be parsed as Craftgate.Error.

Similarly, the body argument will be expanded into the request body, so to create a Foo we can declare:

defmodule FooAdapter do
  ...

  endpoint create_foo(body: CreateFooRequest.t()), post: "/foo/v1/foos", return: FooResponse.t()
end

Which in turn can be called like as follows:

{:ok, foo_response} = FooAdapter.create_foo(%CreateFooRequest{...})

Similarly, the params argument will be expanded into the query string:

defmodule FooAdapter do
  ...

  endpoint search_foos(params: keyword()), get: "/foo/v1/foos", return: SearchFoosResponse.t()
end
%{items: foos} = FooAdapter.search_foos!(name: "foo", age: 42)

You can also use function blocks in the options to specify custom request parameter or bodies. Note that these functions will have the same arity as the original definition and will receive the exact same arguments as the ones passed into the function call.

This can be particularly useful to providing convenience methods to consumers without expecting them to construct full request bodies.

...
endpoint set_foo_status(id: integer(), status: FooStatus.t()),
  put: "/foos/v1/foos/:id/status",
  body: fn _, status -> %UpdateFooStatusRequest{new_status: status} end
...

Finally, the return option can be left out to capture empty responses while still being able to identify failures as Craftgate.Error structs. This can be particularly useful for DELETE endpoints which typically return an empty response body.

So in the end, a set of CRUD operations for a resource Foo can easily be declares as follows:

defmodule FooAdapter do
  use Adapter

  endpoint create_foo(body: CreateFooRequest.t()), post: "/foo/v1/foos", return: FooResponse.t()
  endpoint retrieve_foo(id: integer()), get: "/foo/v1/foos/:id", return: FooResponse.t()
  endpoint update_foo(id: integer(), body: UpdateFooRequest.t()), put: "/foo/v1/foos/:id", return: FooResponse.t()
  endpoint set_foo_status(id: integer(), status: FooStatus.t()),
    put: "/foos/v1/foos/:id/status",
    body: fn _, status -> %UpdateFooStatusRequest{new_status: status} end
  endpoint delete_foo(id: integer()), delete: "/foo/v1/foos/:id"
  endpoint search_foos(params: keyword()), get: "/foo/v1/foos", return: SearchFoosResponse.t()
end