# `PhoenixKit.Modules.Shop.Options.MetadataValidator`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.71/lib/modules/shop/options/metadata_validator.ex#L1)

Validates and normalizes product metadata for options and pricing.

This module handles:
- Format normalization (object -> string for price modifiers)
- Consistency validation between _option_values and _price_modifiers
- Cleanup of orphaned modifiers for removed values

## Price Modifier Formats

The canonical format is a simple string representing the price delta:

    %{"_price_modifiers" => %{
      "size" => %{"M" => "5.00", "L" => "10.00"},
      "color" => %{"Gold" => "8.00"}
    }}

Legacy object format is also supported for backward compatibility:

    %{"_price_modifiers" => %{
      "size" => %{"M" => %{"type" => "fixed", "value" => "5.00"}}
    }}

Both formats are normalized to string format when saving.

# `clean_orphaned_modifiers`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.71/lib/modules/shop/options/metadata_validator.ex#L119)

Removes orphaned modifiers for values not in _option_values.

This cleans up price modifiers when option values are removed.

## Examples

    metadata = %{
      "_option_values" => %{"size" => ["M", "L"]},
      "_price_modifiers" => %{"size" => %{"S" => "0", "M" => "5.00", "L" => "10.00"}}
    }

    MetadataValidator.clean_orphaned_modifiers(metadata)
    # => %{
    #   "_option_values" => %{"size" => ["M", "L"]},
    #   "_price_modifiers" => %{"size" => %{"M" => "5.00", "L" => "10.00"}}
    # }

# `normalize_price_modifiers`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.71/lib/modules/shop/options/metadata_validator.ex#L191)

Normalizes all price modifiers to string format.

Converts object format to string format:
- `%{"type" => "fixed", "value" => "10.00"}` -> `"10.00"`
- `%{"value" => "10.00"}` -> `"10.00"`
- `%{"final_price" => "30.00"}` with base_price 20 -> `"10.00"`

Already-string values are passed through unchanged.

## Examples

    metadata = %{
      "_price_modifiers" => %{
        "size" => %{
          "M" => %{"type" => "fixed", "value" => "5.00"},
          "L" => "10.00"
        }
      }
    }

    MetadataValidator.normalize_price_modifiers(metadata)
    # => %{
    #   "_price_modifiers" => %{
    #     "size" => %{"M" => "5.00", "L" => "10.00"}
    #   }
    # }

# `normalize_product_attrs`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.71/lib/modules/shop/options/metadata_validator.ex#L252)

Normalizes a complete set of product attributes before saving.

This function:
1. Normalizes price modifiers to string format
2. Cleans orphaned modifiers
3. Removes empty _option_values and _price_modifiers maps

## Examples

    attrs = %{
      "title" => "My Product",
      "metadata" => %{
        "_option_values" => %{"size" => ["M", "L"]},
        "_price_modifiers" => %{
          "size" => %{
            "M" => %{"type" => "fixed", "value" => "5.00"},
            "S" => "orphaned"
          }
        }
      }
    }

    MetadataValidator.normalize_product_attrs(attrs)
    # Normalizes modifiers and removes orphaned "S" entry

# `validate`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.71/lib/modules/shop/options/metadata_validator.ex#L43)

Validates metadata structure against option schema.

Returns `:ok` or `{:error, errors}` where errors is a list of error tuples.

## Examples

    schema = [%{"key" => "size", "type" => "select", "options" => ["S", "M", "L"]}]

    MetadataValidator.validate(%{"size" => "M"}, schema)
    # => :ok

    MetadataValidator.validate(%{"size" => "XL"}, schema)
    # => {:error, [{"size", "must be one of: S, M, L"}]}

# `validate_consistency`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.71/lib/modules/shop/options/metadata_validator.ex#L63)

Validates consistency between _option_values and _price_modifiers.

Ensures that:
- All keys in _price_modifiers have corresponding entries in _option_values (or are schema options)
- All values in _price_modifiers exist in their respective option values

Returns a list of error tuples (empty if valid).

---

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