# `MqttX.Topic`
[🔗](https://github.com/cignosystems/mqttx/blob/v0.9.0/lib/mqttx/topic.ex#L1)

MQTT Topic validation, normalization, and wildcard matching.

## Topic Format

Topics are `/`-separated strings. Wildcards are:
- `+` - Single-level wildcard (matches exactly one level)
- `#` - Multi-level wildcard (matches zero or more levels, must be last)

## Shared Subscriptions (MQTT 5.0)

Shared subscriptions use the format `$share/group_name/topic_filter`.
Messages are distributed among subscribers in a group using round-robin.

## Examples

    # Validation
    iex> MqttX.Topic.validate("sensors/temperature")
    {:ok, ["sensors", "temperature"]}

    iex> MqttX.Topic.validate("sensors/+/humidity")
    {:ok, ["sensors", :single_level, "humidity"]}

    iex> MqttX.Topic.validate("sensors/#")
    {:ok, ["sensors", :multi_level]}

    # Matching
    iex> MqttX.Topic.matches?(["sensors", :single_level, "temp"], ["sensors", "room1", "temp"])
    true

    iex> MqttX.Topic.matches?(["sensors", :multi_level], ["sensors", "room1", "temp"])
    true

    # Shared subscriptions
    iex> MqttX.Topic.shared?("$share/group1/sensors/#")
    true

    iex> MqttX.Topic.parse_shared("$share/group1/sensors/#")
    {:shared, "group1", "sensors/#"}

# `normalized_topic`

```elixir
@type normalized_topic() :: [binary() | wildcard()]
```

# `wildcard`

```elixir
@type wildcard() :: :single_level | :multi_level
```

# `flatten`

```elixir
@spec flatten(normalized_topic()) :: binary()
```

Flatten a normalized topic back to a binary string.

# `matches?`

```elixir
@spec matches?(normalized_topic(), normalized_topic()) :: boolean()
```

Match a topic filter against a concrete topic.

The filter can contain wildcards, the topic should not.

## Examples

    iex> MqttX.Topic.matches?(["sensors", :single_level, "temp"], ["sensors", "room1", "temp"])
    true

    iex> MqttX.Topic.matches?(["sensors", :multi_level], ["sensors", "room1", "temp"])
    true

    iex> MqttX.Topic.matches?(["sensors", "room1"], ["sensors", "room2"])
    false

# `normalize`

```elixir
@spec normalize(binary() | list()) :: normalized_topic()
```

Normalize a topic to a list.

Wildcards are converted to atoms `:single_level` (+) and `:multi_level` (#).

# `parse_shared`

```elixir
@spec parse_shared(binary()) :: {:shared, binary(), binary()} | {:normal, binary()}
```

Parse a shared subscription filter.

Returns `{:shared, group_name, topic_filter}` for shared subscriptions,
or `{:normal, topic_filter}` for regular subscriptions.

## Examples

    iex> MqttX.Topic.parse_shared("$share/group1/sensors/#")
    {:shared, "group1", "sensors/#"}

    iex> MqttX.Topic.parse_shared("sensors/#")
    {:normal, "sensors/#"}

# `shared?`

```elixir
@spec shared?(binary()) :: boolean()
```

Check if a topic filter is a shared subscription.

Shared subscriptions use the format `$share/group_name/topic_filter`.

## Examples

    iex> MqttX.Topic.shared?("$share/group1/sensors/#")
    true

    iex> MqttX.Topic.shared?("sensors/#")
    false

# `valid?`

```elixir
@spec valid?(binary() | list()) :: boolean()
```

Check if a topic is valid.

# `validate`

```elixir
@spec validate(binary() | list()) ::
  {:ok, normalized_topic()} | {:error, :invalid_topic}
```

Validate and normalize a topic.

Returns `{:ok, normalized_topic}` or `{:error, :invalid_topic}`.

# `validate_publish`

```elixir
@spec validate_publish(binary() | list()) ::
  {:ok, normalized_topic()} | {:error, :invalid_topic}
```

Validate a topic for publishing (no wildcards allowed).

Returns `{:ok, normalized_topic}` or `{:error, :invalid_topic}`.

# `wildcard?`

```elixir
@spec wildcard?(normalized_topic()) :: boolean()
```

Check if a topic contains wildcards.

---

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