# `GeoGenius.ReleaseArtifacts`
[🔗](https://github.com/agoodway/geo_genius/blob/main/lib/geo_genius/release_artifacts.ex#L63)

What files a release was built from, and where each one is on this machine.

GeoGenius stores area identity -- keys, names, codes, relations, centroids,
boundaries -- and deliberately not a source's own wide attribute columns. A
host that wants those keeps a **projection**: its own table, keyed
`(release_id, area_key)`, filled from the same artifacts the import consumed.
Building one means answering two questions this module owns, and nothing
about the file's contents, which the host owns entirely. See
`guides/projections.md`.

Locating an artifact is not a `Path.join/2`. An artifact reaches the machine
through `GeoGenius.Cache`, under a key the manifest supplied or the catalog
derived, and an **operator-supplied** artifact has no `url` to fall back on
-- the cache key is the only address it has. A caller that looked in `priv/`,
or in the directory an import happened to stage a download in, works against
a fixture and raises `:enoent` on a documented fresh install.

Reads the release a collection currently publishes. `:release_id` names a
different one, published or not, which is how a projection is backfilled for
a release before it goes live; it is checked against the collection first, so
a stale id and another collection's id are both refused rather than answered
with the wrong rows or none. Every function also takes the usual `:repo`,
`:prefix`, and adapter options, and passes `opts` to the cache unchanged, so
`:cache_dir` reaches `GeoGenius.Caches.FileSystem`.

# `cache_key`

```elixir
@spec cache_key(map()) :: {:ok, String.t()} | {:error, String.t()}
```

The cache key an artifact row resolves under.

Takes a row of the `release_artifacts` view, as
`GeoGenius.Catalog.release_artifacts/2` returns it. A key the manifest
supplied -- stored on the row's `metadata` -- wins over the one the row's
columns derive, because that is the whole reason a manifest may carry one: a
licensed artifact an operator drops into a shared location has an address of
its own and no `url` to fall back on.

A supplied key is validated the same way a derived one is. A manifest is
reviewed, not trusted, and the metadata comes back out of jsonb rather than
out of manifest validation, so a segment carrying a separator or a parent
reference is refused here too. The error is the detail alone, without an
artifact name, so each caller states the failure in its own terms.

`GeoGenius.Pipeline.Artifacts` resolves its downloads through this function,
so a release read here is addressed exactly as the import that wrote it
addressed it.

# `fetch`

```elixir
@spec fetch(String.t(), String.t(), keyword()) ::
  {:ok, GeoGenius.ReleaseArtifacts.Artifact.t()}
  | {:error, GeoGenius.ArtifactError.t()}
```

One artifact of a release, by the logical name its manifest gave it.

Resolves the artifact whether or not its file is present, so a caller can ask
where a licensed artifact *would* be before an operator has placed it.
`path/3` is the same lookup for a caller that wants a file it can open.

# `list`

```elixir
@spec list(String.t(), keyword()) ::
  {:ok, [GeoGenius.ReleaseArtifacts.Artifact.t()]}
  | {:error, GeoGenius.ArtifactError.t()}
```

Every artifact a release composes, ordered by logical name -- the order
`GeoGenius.Catalog.release_artifacts/2` reads them in, so it is PostgreSQL's
guarantee rather than this module's.

`:release_id` reads a release other than the published one. It must be a
release of `collection_key`: an id the catalog does not carry, or one
belonging to another collection, is refused rather than read.

Returns `{:error, %GeoGenius.ArtifactError{reason: :no_published_release}}`
for a collection publishing nothing, rather than an empty list: a release
that legitimately composes no artifacts is a different answer, and a caller
repopulating a projection needs to tell them apart.

One artifact whose stored cache key is unusable fails the whole call. A
catalog row nothing can address is a defect in what was registered, and
quietly returning the rest would let a projection be rebuilt from a subset of
the files it was supposed to read.

# `path`

```elixir
@spec path(String.t(), String.t(), keyword()) ::
  {:ok, Path.t()} | {:error, GeoGenius.ArtifactError.t()}
```

The local file one artifact of a release resolves to.

Returns `{:error, %GeoGenius.ArtifactError{reason: :not_cached}}` when
nothing is at that path yet, naming both the path and the cache key, so an
operator reading the message knows where to put the file. That is the whole
point of returning an error here rather than the path regardless: a caller
handed a path to a file that does not exist discovers it as an `:enoent`
raised from inside whatever stream it opened, which names neither.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
