Alembic v1.0.0 Alembic.Resource

The individual JSON object of elements of the list of the data member of the JSON API document are resources as are the members of the included member.

Summary

Types

t()

Resource objects” appear in a JSON API document to represent resources

Types

t :: %Alembic.Resource{attributes: Alembic.json_object | nil, id: String.t | nil, links: Alembic.Links.t | nil, meta: Alembic.Meta.t | nil, relationships: Alembic.Relationships.t | nil, type: String.t}

Resource objects” appear in a JSON API document to represent resources.

A resource object MUST contain at least the following top-level members:

  • id
  • type

Exception: The id member is not required when the resource object originates at the client and represents a new resource to be created on the server. (%{action: :create, source: :client})

In addition, a resource object **MAY(( contain any of these top-level members:

  • attributes - an attributes object representing some of the resource’s data.
  • links - an Alembic.Link.links containing links related to the resource.
  • meta - contains non-standard meta-information about a resource that can not be represented as an attribute or relationship.
  • relationships - a relationships object describing relationships between the resource and other JSON API resources.

Functions

from_json(json, error_template)

Specs

from_json(Alembic.json_object, %Alembic.Error{code: term, detail: term, id: term, links: term, meta: Alembic.Meta.t, source: term, status: term, title: term}) ::
  {:ok, t} |
  Alembic.FromJson.error
from_json(nil | true | false | list | float | integer | String.t, Alembic.Error.t) :: Alembic.FromJson.error

Converts a JSON object into a JSON API Resource, t.

Invalid

A non-resource will be matched, but return an error.

iex> Alembic.Resource.from_json(
...>   "1",
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data` type is not resource",
        meta: %{
          "type" => "resource"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}

Action

The Alembic.Error.t meta "action" key influences whether "id" is required: "id" is optional for "action" :create sent from "sender" :client; otherwise, it "id" is required.

Creating

Only "type" is required when creating a resource from a client because the "id" will be assigned by the server.

iex> Alembic.Resource.from_json(
...>   %{ "type" => "thing" },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{:ok, %Alembic.Resource{type: "thing"}}

Only "type" will be marked as missing when creating a resource from a client

iex> Alembic.Resource.from_json(
...>   %{},
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/type` is missing",
        meta: %{
          "child" => "type"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      }
    ]
  }
}

But, normally you’d include some "attributes" too

iex> Alembic.Resource.from_json(
...>   %{
...>     "attributes" => %{
...>       "name" => "Thing 1"
...>     },
...>     "type" => "thing"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Resource{
    attributes: %{
      "name" => "Thing 1"
    },
    type: "thing"
  }
}

"attributes" are quite free-form, but must still be an Alembic.json_object

iex> Alembic.Resource.from_json(
...>   %{
...>     "attributes" => [
...>       "name"
...>     ],
...>     "type" => "thing"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/attributes` type is not json object",
        meta: %{
          "type" => "json object"
        },
        source: %Alembic.Source{
          pointer: "/data/attributes"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}

Deleting

Only "id" and "type" is required when when deleting a resource from a client

iex> Alembic.Resource.from_json(
...>   %{ "id" => "1", "type" => "thing"},
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :delete,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{:ok, %Alembic.Resource{id: "1", type: "thing"}}

With "id", "type" will be marked as missing when deleting a resource from a client

iex> Alembic.Resource.from_json(
...>   %{ "id" => "1" },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :delete,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/type` is missing",
        meta: %{
          "child" => "type"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      }
    ]
  }
}

With "type", "id" will be marked as missing when deleting a resource from a client

iex> Alembic.Resource.from_json(
...>   %{ "type" => "thing" },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :delete,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/id` is missing",
        meta: %{
          "child" => "id"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      }
    ]
  }
}

Both "id" and "type" will be marked as missing when deleting a resource from a client

iex> Alembic.Resource.from_json(
...>   %{},
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :delete,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/id` is missing",
        meta: %{
          "child" => "id"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      },
      %Alembic.Error{
        detail: "`/data/type` is missing",
        meta: %{
          "child" => "type"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      }
    ]
  }
}

Updating

Only "id" and "type" is required when when updating a resource from a client

iex> Alembic.Resource.from_json(
...>   %{ "id" => "1", "type" => "thing"},
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :update,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{:ok, %Alembic.Resource{id: "1", type: "thing"}}

With "id", "type" will be marked as missing when upating a resource from a client

iex> Alembic.Resource.from_json(
...>   %{ "id" => "1" },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :update,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/type` is missing",
        meta: %{
          "child" => "type"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      }
    ]
  }
}

With "type", "id" will be marked as missing when updating a resource from a client

iex> Alembic.Resource.from_json(
...>   %{ "type" => "thing" },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :update,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/id` is missing",
        meta: %{"child" => "id"},
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      }
    ]
  }
}

Both "id" and "type" will be marked as missing when updating a resource from a client

iex> Alembic.Resource.from_json(
...>   %{},
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :update,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/id` is missing",
        meta: %{"child" => "id"},
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      },
      %Alembic.Error{
        detail: "`/data/type` is missing",
        meta: %{
          "child" => "type"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Child missing"
      }
    ]
  }
}

Optional members

"links", "meta" and "relationships" are optional, but if they are present, they MUST be valid or their errors will make the overall t invalid.

"links"

A valid "links" maps link names to either a JSON object with "href" and/or "meta" member or a String.t URL.

iex> Alembic.Resource.from_json(
...>   %{
...>     "links" => %{
...>        "string" => "http://example.com",
...>       "link_object" => %{
...>         "href" => "http://example.com",
...>         "meta" => %{
...>           "last_updated_on" => "2015-12-21"
...>         }
...>       }
...>     },
...>     "type" => "thing"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Resource{
    links: %{
      "link_object" => %Alembic.Link{
        href: "http://example.com",
        meta: %{
          "last_updated_on" => "2015-12-21"
        }
      },
      "string" => "http://example.com"
    },
    type: "thing"
  }
}

Even though "links" is optional, if it has errors, those errors will make the entire resource invalid

iex> Alembic.Resource.from_json(
...>   %{
...>     "links" => ["http://example.com"],
...>     "type" => "thing"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/links` type is not links object",
        meta: %{
          "type" => "links object"
        },
        source: %Alembic.Source{
          pointer: "/data/links"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}

"meta"

A valid "meta" is any JSON object

iex> Alembic.Resource.from_json(
...>   %{
...>     "meta" => %{"copyright" => "© 2015"},
...>     "type" => "thing"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Resource{
    meta: %{
      "copyright" => "© 2015"
    },
    type: "thing"
  }
}

If "meta" isn’t a JSON object, then that error will make the whole resource invalid

iex> Alembic.Resource.from_json(
...>   %{
...>     "meta" => "© 2015",
...>     "type" => "thing"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/meta` type is not meta object",
        meta: %{
          "type" => "meta object"
        },
        source: %Alembic.Source{
          pointer: "/data/meta"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}

Relationships

"relationships" allow linking to other resource in the documents "included" using resource identifiers

iex> Alembic.Resource.from_json(
...>   %{
...>     "relationships" => %{
...>       "shirt" => %{
...>         "data" => %{
...>           "id" => "1",
...>           "type" => "shirt"
...>         }
...>       }
...>     },
...>     "type" => "thing"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Resource{
    relationships: %{
      "shirt" => %Alembic.Relationship{
        data: %Alembic.ResourceIdentifier{
          id: "1",
          type: "shirt"
        }
      }
    },
    type: "thing"
  }
}

If any relationship has an error, then it will make the entire resource invalid

iex> Alembic.Resource.from_json(
...>   %{
...>     "relationships" => %{
...>       "shirt" => %{
...>         "data" => %{}
...>       }
...>     },
...>     "type" => "thing"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/relationships/shirt/data/type` is missing",
        meta: %{
          "child" => "type"
        },
        source: %Alembic.Source{
          pointer: "/data/relationships/shirt/data"
        },
        status: "422",
        title: "Child missing"
      },
      %Alembic.Error{
        detail: "`/data/relationships/shirt/data/id` is missing",
        meta: %{
          "child" => "id"
        },
        source: %Alembic.Source{
          pointer: "/data/relationships/shirt/data"
        },
        status: "422",
        title: "Child missing"
      }
    ]
  }
}