# Common validators for {{ project_name }}
#
# Reusable validation predicates and type contracts.
# Import with: let validators = import "validators/common.ncl" in

{
  # Port number validation (1-65535)
  ValidPort = fun label =>
    std.contract.from_predicate (fun port =>
      std.is_number port &&
      port >= 1 &&
      port <= 65535
    ) {
      label = label,
      message = "must be a valid port number (1-65535)",
    },

  # Non-empty string validation
  NonEmptyString = fun label =>
    std.contract.from_predicate (fun s =>
      std.is_string s &&
      std.string.length s > 0
    ) {
      label = label,
      message = "must be a non-empty string",
    },

  # Valid bind address format (IP:PORT)
  ValidBindAddress = fun label =>
    std.contract.from_predicate (fun addr =>
      std.is_string addr &&
      std.string.contains ":" addr &&
      let parts = std.string.split ":" addr in
      std.array.length parts == 2
    ) {
      label = label,
      message = "must be a valid bind address (IP:PORT format)",
    },

  # Valid URL format
  ValidUrl = fun label =>
    std.contract.from_predicate (fun url =>
      std.is_string url &&
      (std.string.is_match "^https?://" url)
    ) {
      label = label,
      message = "must be a valid HTTP/HTTPS URL",
    },

  # Positive number validation
  PositiveNumber = fun label =>
    std.contract.from_predicate (fun n =>
      std.is_number n && n > 0
    ) {
      label = label,
      message = "must be a positive number",
    },

  # Non-negative number validation
  NonNegativeNumber = fun label =>
    std.contract.from_predicate (fun n =>
      std.is_number n && n >= 0
    ) {
      label = label,
      message = "must be a non-negative number",
    },

  # Range validation
  Range = fun min => fun max => fun label =>
    std.contract.from_predicate (fun n =>
      std.is_number n &&
      n >= min &&
      n <= max
    ) {
      label = label,
      message = "must be between %{std.to_string min} and %{std.to_string max}",
    },

  # String pattern matching (regex)
  MatchesPattern = fun pattern => fun label =>
    std.contract.from_predicate (fun s =>
      std.is_string s &&
      std.string.is_match pattern s
    ) {
      label = label,
      message = "must match pattern: %{pattern}",
    },

  # Enum validation (one of allowed values)
  OneOf = fun allowed => fun label =>
    std.contract.from_predicate (fun value =>
      std.array.any (fun v => v == value) allowed
    ) {
      label = label,
      message = "must be one of: %{std.serialize 'Json allowed}",
    },

  # Array length validation
  ArrayLength = fun min => fun max => fun label =>
    std.contract.from_predicate (fun arr =>
      std.is_array arr &&
      let len = std.array.length arr in
      len >= min && len <= max
    ) {
      label = label,
      message = "array length must be between %{std.to_string min} and %{std.to_string max}",
    },
}
