Decodes the metadata objects of the files endpoints into structs.
Dropbox describes every entry in a user's Dropbox with one of three
objects, told apart by a ".tag" key: "file", "folder" or "deleted".
Magpie turns them into Magpie.FileMetadata, Magpie.FolderMetadata and
Magpie.DeletedMetadata, with DateTime timestamps and a first-class
content_hash, so callers pattern match on the struct instead of
inspecting string keys:
client
|> Magpie.Files.ListFolder.stream("/Photos")
|> Enum.map(fn
%Magpie.FileMetadata{name: name, size: size} -> {name, size}
%Magpie.FolderMetadata{name: name} -> {name, :folder}
%Magpie.DeletedMetadata{name: name} -> {name, :deleted}
end)Every function in Magpie.Files, Magpie.Files.ListFolder and
Magpie.Files.UploadSession that returns metadata decodes it before
handing it back, so decode/1 is only needed when you call an endpoint
yourself through Magpie.post/3:
{:ok, raw} = Magpie.post(client, "/files/get_metadata", %{"path" => "/a.txt"})
%Magpie.FileMetadata{} = Magpie.Metadata.decode(raw)Forward compatibility
Decoding is lenient by design. A map whose ".tag" Magpie does not know
(should Dropbox add a fourth kind of entry) is returned untouched, and so
is anything that is not a map — a stubbed test response, or an error
payload that reached the decoder. Fields Dropbox adds later that the
structs do not carry are dropped; the raw payload is always available
through Magpie.post/3.
Summary
Functions
Computes the Dropbox content_hash of data — a binary, or an enumerable
of binaries such as File.stream!/2 — so a transfer can be verified
against Magpie.FileMetadata.content_hash without another request.
Decodes one metadata object.
Decodes the "metadata" of every search match on a /files/search_v2
page. Dropbox wraps each match's entry in a one-variant union
(%{".tag" => "metadata", "metadata" => ...}); Magpie flattens it so the
match holds the struct directly.
Decodes every item under "entries" of a paginated page, leaving the rest
of the page — "cursor", "has_more" — as it is, so the page still drives
Magpie.Pager. kind is passed on to decode/2.
Decodes a response that wraps the metadata under a "metadata" key —
/files/create_folder_v2, /files/delete_v2, /files/copy_v2 and
/files/move_v2 all answer that way — returning just the struct.
Types
@type t() :: Magpie.FileMetadata.t() | Magpie.FolderMetadata.t() | Magpie.DeletedMetadata.t()
Functions
@spec content_hash(binary() | Enumerable.t()) :: String.t()
Computes the Dropbox content_hash of data — a binary, or an enumerable
of binaries such as File.stream!/2 — so a transfer can be verified
against Magpie.FileMetadata.content_hash without another request.
Dropbox hashes the content in 4 MiB blocks with SHA-256, concatenates the block digests and hashes the result once more (see the Content hash reference).
iex> Magpie.Metadata.content_hash("")
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
{:ok, %{body: body}} = Magpie.Files.download(client, "/report.pdf")
{:ok, %Magpie.FileMetadata{content_hash: hash}} = Magpie.Files.get_metadata(client, "/report.pdf")
Magpie.Metadata.content_hash(body) == hash
# => true
Decodes one metadata object.
The ".tag" picks the struct. Dropbox omits the tag when the endpoint's
return type is known up front — /files/upload and /files/list_revisions
always answer with files, /files/create_folder_v2 with a folder — and
kind says what to expect then: :file, :folder, or :infer (the
default), which treats a map with a "rev" as a file (every file has one,
no folder does) and any other map with a "name" as a folder. Unknown tags
and non-map values are returned as they are.
iex> Magpie.Metadata.decode(%{".tag" => "folder", "name" => "Photos", "id" => "id:1"})
%Magpie.FolderMetadata{name: "Photos", id: "id:1"}
iex> Magpie.Metadata.decode(%{"name" => "a.txt", "rev" => "015", "size" => 3})
%Magpie.FileMetadata{name: "a.txt", rev: "015", size: 3}
iex> Magpie.Metadata.decode(%{"name" => "a.txt"}, :file)
%Magpie.FileMetadata{name: "a.txt"}
iex> Magpie.Metadata.decode(%{".tag" => "hologram", "name" => "x"})
%{".tag" => "hologram", "name" => "x"}
Decodes the "metadata" of every search match on a /files/search_v2
page. Dropbox wraps each match's entry in a one-variant union
(%{".tag" => "metadata", "metadata" => ...}); Magpie flattens it so the
match holds the struct directly.
iex> page = %{"matches" => [%{"metadata" => %{".tag" => "metadata", "metadata" => %{".tag" => "file", "name" => "a", "rev" => "1"}}}]}
iex> Magpie.Metadata.decode_matches(page)
%{"matches" => [%{"metadata" => %Magpie.FileMetadata{name: "a", rev: "1"}}]}
Decodes every item under "entries" of a paginated page, leaving the rest
of the page — "cursor", "has_more" — as it is, so the page still drives
Magpie.Pager. kind is passed on to decode/2.
iex> Magpie.Metadata.decode_page(%{"entries" => [%{".tag" => "deleted", "name" => "x"}], "has_more" => false})
%{"entries" => [%Magpie.DeletedMetadata{name: "x"}], "has_more" => false}
Decodes a response that wraps the metadata under a "metadata" key —
/files/create_folder_v2, /files/delete_v2, /files/copy_v2 and
/files/move_v2 all answer that way — returning just the struct.
kind is passed on to decode/2. Responses of any other shape are
returned untouched.
iex> Magpie.Metadata.unwrap(%{"metadata" => %{".tag" => "file", "name" => "a", "rev" => "1"}})
%Magpie.FileMetadata{name: "a", rev: "1"}