README.md

# Carve

DSL for building JSON APIs fast. Creates endpoint views, renders linked data automatically.

Features:

* Rendering structured JSON views automatically
* Retrieving links between different data types
* Creating index/show methods for views
* Encoding and decoding IDs (w/ unique salt per data type) using HashIds

Example endpoint view generated by Carve:

```json
{
  "result": {
    "id": "D3Wcorr0oa",
    "type": "user",
    "data": { ... }
  },
  "links": [
    {
      "id": "Xk9Lp2Rr4m",
      "type": "team",
      "data": { ... }
    },
    {
      "id": "Bz7Jt5Yq1n",
      "type": "profile",
      "data": { ... }
    }
  ]
}
```

## Installation

Add `carve` to your list of dependencies in `mix.exs`:


```elixir
def deps do
  [
    {:carve, "~> 0.1.0"}
  ]
end
```


To configure custom hashing salt, add to `config.exs`:

```elixir
config :carve,
  salt: "your-secret-salt",
  min_length: 10
```

## Usage

In your Phoenix JSON view, just declare how your endpoint should look like:

```elixir
defmodule UserJSON do
    use Carve.View, :user

    view fn user ->
      %{
        id: hash(user.id), # provided by Carve.View, encodes numerical ID (123) with an entity-specific salt (D3Wcorr0oa).
        name: user.name
      }
    end
end
```

And that's it -- this macro will make `index(%{ result: users })` and `show(%{ result: user })` methods available for your controller.

Of course, just formatting the view alone is not Carvers only point; you can declare the links of each view, and Carve will automatically output them for the client:

```elixir
defmodule UserJSON do
    use Carve.View, :user

    # Return the links of a given user as ViewModule => id
    links fn user ->
        %{
            FooWeb.TeamJSON => user.team_id, # You can also pass list of ids or the Ecto record(s)
            FooWeb. ProfileJSON => user.profile_id 
        }
    end

    # Provide a method to retrieve user by id. This will be used by Carve to render links automatically.
    get fn id ->
        Foo.Users.get_by_id!(id)
    end

    view fn user ->
      %{
        id: hash(user.id),
        team_id: FooWeb.TeamJSON.hash(user.team_id),
        profile_id: FooWeb.ProfileJSON.hash(user.profile_id),
        name: user.name
      }
    end
end
```

After activating `show` and `index` methods in the controller:

```elixir
render(conn, :index, result: requests) # or render(conn, :show, result: record)
```

Example response Carve will render for this view:

```json
{
  "result": {
    "id": "D3Wcorr0oa",
    "type": "user",
    "data": {
      "id": "D3Wcorr0oa",
      "name": "John Doe",
      "team_id": "Xk9Lp2Rr4m",
      "profile_id": "Bz7Jt5Yq1n"
    }
  },
  "links": [
    {
      "id": "Xk9Lp2Rr4m",
      "type": "team",
      "data": {
        "id": "Xk9Lp2Rr4m",
        "name": "Engineering Team",
        "description": "Our awesome engineering team"
      }
    },
    {
      "id": "Bz7Jt5Yq1n",
      "type": "profile",
      "data": {
        "id": "Bz7Jt5Yq1n",
        "bio": "Software engineer passionate about Elixir",
        "avatar_url": "https://example.com/avatars/johndoe.jpg"
      }
    }
  ]
}
```

## How does it work?

* Carve macros create view functions `index(%{ result: users })` and `show(%{ result: user })`
* Controller calls these view functions
* Carve pulls the list of links for given data (list or single record)
* Carve calls the `get_by_id` (`get` macro expanded) and `prepare_for_view` (`view` macro expanded) functions for each link
* The final expanded list of links get flattened & cleaned, returned to user with the main result: `{ result: {} || [], links: [] }`