# `Whiff.Input`
[🔗](https://github.com/SinAnima/whiff-elixir/blob/main/lib/whiff/input.ex#L1)

Canonical description of one extraction input for Elixir plugins.

Built by the public API before native extraction and passed to
`Whiff.Extractor` / `Whiff.Analyzer` callbacks. Prefer deciding
`supports?/2` from descriptors (`:extension`, `:filename`, `:size`) instead
of reading entire large files.

## Fields

* `:source` — `:path` or `:binary`
* `:path` — absolute or relative filesystem path when `source: :path`
* `:bytes` — payload when `source: :binary` (may be large; do not log it)
* `:filename` — basename used for detection and `supports?/2`
* `:extension` — lowercase extension including the dot (e.g. `".pdf"`), or `nil`
* `:mime` — optional caller-supplied MIME type
* `:size` — byte size when known

## Examples

    iex> input = Whiff.Input.from_path("/tmp/report.pdf")
    iex> input.source
    :path
    iex> input.filename
    "report.pdf"
    iex> input.extension
    ".pdf"

    iex> {:ok, input} = ok_binary()
    iex> input.source
    :binary
    iex> input.extension
    ".trade"
    iex> Map.has_key?(Whiff.Input.present(input), :bytes)
    false

The helper below is only for doctests:

    iex> false
    false

# `source`

```elixir
@type source() :: :path | :binary
```

Whether the payload is on disk or already in memory.

# `t`

```elixir
@type t() :: %Whiff.Input{
  bytes: binary() | nil,
  extension: String.t() | nil,
  filename: String.t() | nil,
  mime: String.t() | nil,
  path: String.t() | nil,
  size: non_neg_integer() | nil,
  source: source()
}
```

Input struct passed to Elixir plugins. See the module doc for fields.

# `from_binary`

```elixir
@spec from_binary(binary(), keyword()) :: t() | {:error, Whiff.Error.t()}
```

Builds input from in-memory bytes.

## Arguments

* `bytes` — binary payload
* `opts` — optional keywords:
  * `:name` / `:filename` — basename for detection (default `"input.bin"`)
  * `:mime` — optional MIME string

## Returns

* `%Whiff.Input{}` on success
* `{:error, %Whiff.Error{reason: :invalid_option}}` if `:name` is present but not a binary
* `{:error, %Whiff.Error{reason: :unsupported_input}}` if `bytes` is not a binary

## Examples

    iex> input = Whiff.Input.from_binary("TRADE/1\nid=1\n", name: "pos.trade")
    iex> match?(%Whiff.Input{source: :binary, extension: ".trade", size: 14}, input)
    true

    iex> Whiff.Input.from_binary("x", name: :bad)
    {:error, %Whiff.Error{reason: :invalid_option, message: "name must be a string", path: nil, details: %{}}}

    iex> Whiff.Input.from_binary(:not_a_binary)
    {:error, %Whiff.Error{reason: :unsupported_input, message: "expected a binary", path: nil, details: %{}}}

# `from_path`

```elixir
@spec from_path(Path.t()) :: t()
```

Builds input from a filesystem path.

Does not read file contents. `:size` is filled from `File.stat/1` when the
path exists; otherwise `nil`.

## Arguments

* `path` — UTF-8 path string

## Examples

    iex> i = Whiff.Input.from_path("docs/notes.TXT")
    iex> {i.source, i.filename, i.extension}
    {:path, "notes.TXT", ".txt"}

# `present`

```elixir
@spec present(t()) :: map()
```

Safe presentation map for logs and diagnostics.

Never includes `:bytes`.

## Examples

    iex> input = Whiff.Input.from_binary(<<0, 1, 2>>, name: "x.bin", mime: "application/octet-stream")
    iex> Whiff.Input.present(input)
    %{source: :binary, path: nil, filename: "x.bin", extension: ".bin", mime: "application/octet-stream", size: 3}

---

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