Skip to main content

lib/npm/validator.ex

defmodule NPM.Validator do
  @moduledoc """
  Validate `package.json` fields.
  """

  @doc """
  Validate a package name according to npm naming rules.

  Returns `:ok` or `{:error, reason}`.
  """
  @spec validate_name(String.t()) :: :ok | {:error, String.t()}
  def validate_name(name) do
    cond do
      name == "" ->
        {:error, "name cannot be empty"}

      byte_size(name) > 214 ->
        {:error, "name cannot exceed 214 characters"}

      String.starts_with?(name, ".") ->
        {:error, "name cannot start with a period"}

      String.starts_with?(name, "_") ->
        {:error, "name cannot start with an underscore"}

      name != String.downcase(name) and not String.starts_with?(name, "@") ->
        {:error, "name must be lowercase"}

      String.contains?(name, " ") ->
        {:error, "name cannot contain spaces"}

      true ->
        :ok
    end
  end

  @doc """
  Validate a version range string.

  Returns `:ok` or `{:error, reason}`.
  """
  @spec validate_range(String.t()) :: :ok | {:error, String.t()}
  def validate_range(""), do: {:error, "version range cannot be empty"}
  def validate_range("*"), do: :ok
  def validate_range("latest"), do: :ok

  def validate_range(range) do
    case NPMSemver.to_hex_constraint(range) do
      {:ok, _} -> :ok
      :error -> {:error, "invalid version range: #{range}"}
    end
  end
end