Metadata-Version: 2.4
Name: safeshield
Version: 2.0.0
Summary: Safe Shield is a powerful, elegant, and highly extensible validation library for Python. Inspired by modern validation systems like Laravel, it provides a fluent syntax for validating complex data structures, including nested dictionaries and wildcard patterns.
Author-email: Wunsun Tarniho <wunsun58@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/WunsunTarniho/safe-shield
Project-URL: Repository, https://github.com/WunsunTarniho/safe-shield.git
Project-URL: Bug Tracker, https://github.com/WunsunTarniho/safe-shield/issues
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.10
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Security
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: Pillow==11.2.1
Requires-Dist: psycopg2-binary==2.9.9
Requires-Dist: python_dateutil==2.9.0.post0
Requires-Dist: Werkzeug==3.1.3
Requires-Dist: dnspython==2.4.2
Requires-Dist: idna==3.6

# Safe Shield 🛡️ (v2.0.0)

**Safe Shield** is a powerful, elegant, and highly extensible validation library for Python. Inspired by modern validation systems like Laravel, it provides a fluent syntax for validating complex data structures, including nested dictionaries and wildcard patterns.

⚠️ IMPORTANT: Safe Shield v1.x.x and v2.x.x have different APIs. Make sure you're reading the documentation that matches your installed version.

---

## 📖 Table of Contents

- [✨ Features](#-features)
- [📥 Installation](#-installation)
- [🚀 Quick Start](#-quick-start)
- [🛠️ 3 Ways to Define Rules](#️-3-ways-to-define-rules)
  - [1. Pipe String](#1-pipe-string-laravel-style)
  - [2. List of Strings & Tuples](#2-list-of-strings--tuples)
  - [3. Rule Instances](#3-rule-instances-object-oriented)
- [🧩 Custom Rules](#-custom-rules)
  - [1. Create the Rule](#1-create-the-rule)
  - [2. Use the Rule](#2-use-the-rule)
- [🌲 Nested Data & Wildcards](#-nested-data--wildcards)
  - [Dot Notation](#dot-notation)
  - [Wildcards](#wildcards-)
- [🎨 Error Messages & Customization](#-error-messages--customization)
  - [1. Custom Attribute Names](#1-custom-attribute-names)
  - [2. Custom Error Messages](#2-custom-error-messages)
  - [3. Placeholders](#3-placeholders)
- [🚀 Advanced Logic: any\_of & when](#-advanced-logic-any_of--when)
  - [1. Any of](#1-any_of-logical-or)
  - [2. When](#2-when-conditional-rules)
- [🔍 Complete Rule Reference](#-complete-rule-reference)
  - [📍 Basic & Requirement Rules](#-basic--requirement-rules)
  - [🔤 String Rules](#-string-rules)
  - [🔢 Numeric & Digits Rules](#-numeric--digits-rules)
  - [📅 Date & Time Rules](#-date--time-rules)
  - [📦 Array & Collection Rules](#-array--collection-rules)
  - [⚖️ Comparison Rules](#-comparison-rules)
  - [📁 File & Image Rules](#-file--image-rules)
  - [♟️ Conditional & Utility Rules](#-conditional--utility-rules)
- [🛡️ License](#️-license)

---

## ✨ Features

- 📝 **Fluent Syntax**: Define rules using simple strings or rich objects.
- 🌲 **Nested Validation**: Validate deeply nested data with ease using dot notation.
- 🃏 **Wildcard Support**: Apply rules across arrays or object collections with `*`.
- 🧩 **Modular & Extensible**: Easily create and register your own custom rules.
- 💬 **Customizable Messages**: Fine-grained control over error messages and attribute names.

---

## 📥 Installation

Install **Safe Shield** using pip:

```bash
pip install safeshield
```

---

## 🚀 Quick Start

```python
from validator import Validator

data = {
    "username": "john_doe",
    "email": "invalid-email",
    "profile": {
        "age": 17
    },
    "tags": ["python", "validation"]
}

rules = {
    "username": "required|string|min:3",
    "email": "required|email",
    "profile.age": "required|numeric|min:18",
    "tags": "required|array|min:1",
    "tags.*": "string|alpha"
}

v = Validator(data, rules)

if v.validate():
    print("Success!")
else:
    print(v.errors)
```

---

## 🛠️ 3 Ways to Define Rules

Safe Shield is extremely flexible in how you define validation rules. You can use whichever format fits your coding style. Below are full examples of how to implement each style in your code.

### 1. Pipe String (Laravel Style)
The most concise way. Rules are separated by `|` and parameters follow a `:`.
```python
from validator import Validator

data = {"email": "invalid-email", "age": 15}
rules = {
    "email": "required|email|max:255",
    "age": "required|numeric|min:18"
}

v = Validator(data, rules)

if v.validate():
    print("Validation Passed!")
else:
    print(v.errors) # {'email': ['The email must be a valid email.'], 'age': ['The age must be at least 18.']}
```

### 2. List of Strings & Tuples
Cleaner when dealing with complex parameters or when you want to avoid string parsing.
```python
from validator import Validator

data = {"email": "user@example.com", "age": 20}
rules = {
    "email": ["required", "email", "max:255"],
    "age": ["required", "numeric", ("min", 18)] # Tuples are great for params
}

v = Validator(data, rules)
if not v.validate():
    handle_your_errors(v.errors)
```

### 3. Rule Instances (Object Oriented)
The most powerful way. Allows for IDE autocompletion and passing complex objects or custom logic as parameters.
```python
from validator import Validator
from validator.rules import Required, Email, Min

data = {"email": "user@example.com", "age": 25}
rules = {
    "email": [Required(), Email()],
    "age": [Required(), Min(18)]
}

v = Validator(data, rules)
# Use it in your logic
validated_data = v.data if v.validate() else None
```

---

## 🧩 Custom Rules

Creating and using your own rules is straightforward.

### 1. Create the Rule
Extend the `Rule` class and implement `validate` and `message`.
```python
from validator.rules import Rule

class StrongPassword(Rule):
    def validate(self, field, value, params):
        # Your custom logic here
        return len(value) >= 10 and any(c.isdigit() for c in value)
    
    def message(self):
        return "The :attribute must be at least 10 characters and contain a number."
```

### 2. Use the Rule
There are two ways to use your custom rule:

#### Option A: Direct Instance (Recommended for one-offs)
Just pass it in the list of rules.
```python
rules = {
    "password": ["required", StrongPassword()]
}
```

#### Option B: Register & Use String (Global usage)
Register the rule with a name, then use that name anywhere.
```python
from validator.factory import RuleFactory

# Register (name, validate_func, message_func)
RuleFactory.register_rule(
    "strong_pass", 
    lambda f, v, p: len(v) > 10, 
    lambda f, p: "Password is too weak!"
)

rules = {
    "password": "required|strong_pass"
}
```

---

## 🌲 Nested Data & Wildcards

Safe Shield makes validating complex JSON-like structures simple using **Dot Notation** and **Wildcards**.

### Dot Notation
Validate nested keys regardless of depth.
```python
rules = {
    "user.profile.address.city": "required|string"
}
```

### Wildcards (`*`)
Apply rules to every element in a list or every key in a dictionary.
```python
data = {
    "items": [
        {"id": 1, "price": 10},
        {"id": 2, "price": 20}
    ]
}

rules = {
    "items.*.id": "required|integer",
    "items.*.price": "numeric|min:0"
}
```

---

## 🎨 Error Messages & Customization

### 1. Custom Attribute Names
Instead of showing "profile.age", show a friendly name like "Age".
```python
attributes = {
    "profile.age": "Age",
    "user.email": "Email Address"
}
v = Validator(data, rules, custom_attributes=attributes)
# Error will say: "The Age must be at least 18."
```

### 2. Custom Error Messages
You can customize messages globally or for specific field-rule combinations.
```python
messages = {
    "required": "Field :attribute is mandatory.",
    "email": "The :input value is not a valid email.",
    "profile.age.min": "You must be at least :min years old to join!"
}
v = Validator(data, rules, messages=messages)
```

### 3. Placeholders
Safe Shield provides several placeholders to make your messages dynamic:

| Placeholder | Description | Example Rule |
| :--- | :--- | :--- |
| `:attribute` | The name of the field (or its custom name). | All rules |
| `:input` | The actual value that failed validation. | All rules |
| `:value` | The primary parameter of the rule. | `min`, `max`, `size` |
| `:other` | The name of another field being compared to. | `same`, `required_if` |
| `:others` | A list of other fields. | `required_with`, `prohibits` |
| `:values` | A list of values provided in the rule params. | `in`, `starts_with`, `required_if` |

#### Example usage of `:other` and `:values`:
```python
messages = {
    "password.same": "The :attribute must match :other.",
    "status.in": "The :attribute must be one of these: :values."
}
```


---

## 🚀 Advanced Logic: `any_of` & `when`

For more complex validation scenarios, Safe Shield provides two powerful logic rules.

### 1. `any_of` (Logical OR)
Validates that a field passes at least one set of rules. Ideal for fields that can be multiple formats (e.g., a username can be an email OR a numeric ID).

- **How it works**: It takes multiple rule sets separated by commas.
- **Example Usage**:
```python
# Pass if it's a valid email OR a number with at least 10000
rules = {
    "identifier": "any_of:required|email,required|numeric|min:10000"
}

# Documentation via different styles:
# String: "any_of:rule1|rule2,rule3|rule4"
# List:   ("any_of", ["required", "email"], ["numeric"])
# Class:  AnyOf(["required", "email"], ["numeric"])
```

### 2. `when` (Conditional rules)
Dynamically applies rules to a field based on the value of **other** fields in the dataset.

- **How it works**: You define a "watch map". If a watched field matches a value, specific rules are injected.
- **Example Usage**:
```python
from validator import Validator
from validator.rules import When

data = {
    "user_type": "admin",
    "access_level": "" # This should be required if user_type is admin
}

rules = {
    "access_level": [
        When({
            "user_type": {
                "admin": "required|integer", # If user_type is 'admin'
                "guest": "nullable",         # If user_type is 'guest'
                "*": "string"               # Wildcard: any other value
            }
        })
    ]
}

v = Validator(data, rules)
if v.fails():
    print(v.errors()) # {'access_level': ['The access level must be an integer.']}
```

---

## 🔍 Complete Rule Reference

This section provides a detailed breakdown of every rule available in Safe Shield. Each rule is described in a narrative paragraph that covers its function, syntax (string and class), and implementation notes.

### 📍 Basic & Requirement Rules

#### 🔹 `required`
The `required` rule is the most fundamental requirement check. It ensures that the field is present in the input dictionary and is not considerd "empty". In this context, empty means the value cannot be `None`, an empty string (`""`), an empty list (`[]`), or an empty dictionary (`{}`). You can implement this by using the string `"required"` or the `Required()` class. It is the core rule for mandatory fields in any form.

#### 🔹 `nullable`
The `nullable` rule allows a field to contain a `None` (null) value without failing validation. Most importantly, if the value is `None`, the validator will skip all other rules assigned to that field, preventing errors like "must be a string" for a null value. It is defined as `"nullable"` or `Nullable()`. This is ideal for optional database fields that can remain empty.

#### 🔹 `sometimes`
The `sometimes` rule provides conditional execution based on field presence. Validation rules for the field will only be run if the key actually exists in the input data. If the key is missing, validation is skipped entirely. Use this via the `"sometimes"` string or `Sometimes()` class. This is perfect for "PATCH" API requests where you only send the fields you want to update.

#### 🔹 `bail`
The `bail` rule is a performance and user experience optimizer. Once the first validation rule for a field fails, `bail` instructs the validator to stop checking any subsequent rules for that same field. This prevents a single field from returning a long list of redundant error messages. It is defined as `"bail"` or `Bail()`.

#### 🔹 `accepted`
The `accepted` rule validates that the field's value is a "truthy" confirmation. It specifically checks if the value is `True`, `1`, `"yes"`, or `"on"`. This is most commonly used for mandatory "Terms of Service" checkboxes in registration forms. You can use the string `"accepted"` or the `Accepted()` class.

#### 🔹 `declined`
The `declined` rule is the logical opposite of `accepted`. It validates that the field's value is a "falsy" rejection. It checks if the value is `False`, `0`, `"no"`, or `"off"`. It is defined as `"declined"` or `Declined()`. Use this when you need a user to explicitly opt-out of a selection.

#### 🔹 `filled`
The `filled` rule ensures that if a field is present in the input, it must not be empty (not `None`, `""`, etc.). Unlike `required`, if the field is missing from the input entirely, it will still pass. It is essentially a "not empty if present" check. Define it as `"filled"` or `Filled()`.

#### 🔹 `present`
The `present` rule validates that the field key exists in the input data, regardless of its value. Even if the value is `None` or an empty string, it will pass as long as the key is there. Use this to ensure a specific data structure is maintained. Define it as `"present"` or `Present()`.

#### 🔹 `missing`
The `missing` rule ensures that the field is NOT part of the input data. If the key is found in the dictionary, validation fails. This is a security feature to prevent users from submitting fields that should only be handled internally. Define it as `"missing"` or `Missing()`.

#### 🔹 `prohibited`
The `prohibited` rule validates that the field is either missing from the input or contains an "empty" value (like `None` or `""`). It effectively forbids a field from having actual data. Define it as `"prohibited"` or `Prohibited()`.

#### 🔹 `exclude`
The `exclude` rule is a utility rule that always passes but has a side effect: it removes the field from the final dictionary returned by `v.validated()`. This is useful for data you need for validation (like a password confirmation) but don't want to include in a final database save. Define it as `"exclude"` or `Exclude()`.

---

### 🔤 String Rules

#### 🔹 `string`
The `string` rule validates that the input is a valid Python `str` type. It is the standard type-check rule for any text input. It is defined as `"string"` or `String()`.

#### 🔹 `alpha`
The `alpha` rule ensures the string contains only alphabetic characters (A-Z, a-z). It will fail if the string contains numbers, spaces, or any symbols. Use it for fields like "First Name" where only letters are allowed. Define it as `"alpha"` or `Alpha()`.

#### 🔹 `alpha_num`
The `alpha_num` rule validates that the string contains only alphanumeric characters (letters and numbers). Spaces and special symbols are prohibited. It is frequently used for IDs, usernames, or SKU codes. Define it as `"alpha_num"` or `AlphaNum()`.

#### 🔹 `alpha_dash`
The `alpha_dash` rule allows for alphanumeric characters, dashes (`-`), and underscores (`_`). This is the industry standard for validating "slugs" used in URLs. It is defined as `"alpha_dash"` or `AlphaDash()`.

#### 🔹 `email`
The `email` rule provides exhaustive validation for email addresses. By default, it uses standard RFC validation. However, it supports several advanced modes that can be combined (e.g., `"email:rfc,dns"`):
- **`rfc`**: Strict RFC-compliant validation (Default).
- **`strict`**: RFC validation without any common warnings or edge-case allowances.
- **`dns`**: Checks if the domain in the email address has valid MX records (requires `dnspython`).
- **`spoof`**: Detects homograph/spoofing attacks (requires `idna`).
- **`filter`**: Compatible with PHP's `filter_var` email validation logic.
- **`filter_unicode`**: PHP-style filter that allows Unicode characters in the local/domain parts.
You can define it as `"email"` or using the `Email()` class with specific params.

#### 🔹 `url`
The `url` rule validates that the input is a correctly formatted URL. It checks for the presence of a protocol (http/https), a valid domain structure, and optional paths or query parameters. Define it as `"url"` or `Url()`.

#### 🔹 `uuid`
The `uuid` rule ensures the string is a valid Universally Unique Identifier (UUID). It supports standard 36-character formats (e.g., `550e8400-e29b-41d4-a716-446655440000`). Define it as `"uuid"` or `Uuid()`.

#### 🔹 `ulid`
The `ulid` rule validates a Universally Unique Lexicographically Sortable Identifier. This is a 26-character sortable alternative to UUID. Define it as `"ulid"` or `Ulid()`.

#### 🔹 `ip`
The `ip` rule validates that the input is a valid IP address. By default, it allows both IPv4 and IPv6. You can restrict it using `"ip:v4"` or `"ip:v6"`. It uses Python's `ipaddress` module for robust checking. Define it as `"ip"` or `Ip()`.

#### 🔹 `hex`
The `hex` rule validates that the string is a valid hexadecimal color code. It supports both 3-digit and 6-digit formats (e.g., `#FFF` or `#FFFFFF`). The hash `#` prefix is optional. Define it as `"hex"` or `Hex()`.

#### 🔹 `json`
The `json` rule ensures the string is valid JSON-encoded data. It attempts to parse the string using Python's `json.loads()` and fails if a `JSONDecodeError` is raised. Define it as `"json"` or `Json()`.

#### 🔹 `regex`
The `regex` rule allows for powerful custom validation using Regular Expressions. It checks if the input matches a given pattern. Note that patterns should not be enclosed in delimiters (like `/.../`) when using string syntax. Define it as `"regex:pattern"` or `Regex("pattern")`.

#### 🔹 `not_regex`
The `not_regex` rule is the inverse of `regex`. It ensures the input does NOT match the specified pattern. This is useful for blacklisting certain keywords or characters. Define it as `"not_regex:pattern"` or `NotRegex("pattern")`.

#### 🔹 `starts_with`
The `starts_with` rule ensures the string begins with one of the provided values. It can take multiple parameters (e.g., `"starts_with:http://,https://"`). The check is case-sensitive. Define it as `"starts_with:val1,val2"` or `StartsWith("val1", "val2")`.

#### 🔹 `doesnt_start_with`
The `doesnt_start_with` rule ensures the string does NOT begin with any of the specified values. This is useful for preventing certain prefixes (e.g., `test_`). Define it as `"doesnt_start_with:val1"` or `DoesntStartWith("val1")`.

#### 🔹 `ends_with`
The `ends_with` rule validates that the string ends with one of the provided values (e.g., `"ends_with:.com,.net"`). It is case-sensitive. Define it as `"ends_with:val1,val2"` or `EndsWith("val1", "val2")`.

#### 🔹 `doesnt_end_with`
The `doesnt_end_with` rule ensures the string does NOT end with any of the specified suffixes. Define it as `"doesnt_end_with:val1"` or `DoesntEndWith("val1")`.

#### 🔹 `uppercase`
The `uppercase` rule ensures that every alphabetic character in the string is in its uppercase form. Non-alphabetic characters (numbers, symbols) are ignored. Define it as `"uppercase"` or `Uppercase()`.

#### 🔹 `lowercase`
The `lowercase` rule ensures that every alphabetic character in the string is in its lowercase form. This is commonly used for normalizing data like email addresses. Define it as `"lowercase"` or `Lowercase()`.

#### 🔹 `ascii`
The `ascii` rule validates that the string contains only 7-bit ASCII characters. It will fail if any multibyte or Unicode characters are detected. Define it as `"ascii"` or `Ascii()`.

#### 🔹 `confirmed`
The `confirmed` rule is a specialized security and UI rule. It validates that the current field matches another field named `{field}_confirmation`. For example, if the field is `password`, it will check if it matches `password_confirmation`. This is a quick way to handle "Confirm Password" logic without extra configuration. Define it as `"confirmed"` or `Confirmed()`.

---

---

### 🔢 Numeric & Digits Rules

#### 🔹 `numeric`
The `numeric` rule ensures that the input value is a valid number. This includes Python `int`, `float`, and even strings that represent numbers (e.g., `"123.45"`). It is the most flexible numeric check. Define it as `"numeric"` or `Numeric()`.

#### 🔹 `integer`
The `integer` rule validates that the input is a whole number. It accepts Python `int` types and numeric strings that do not contain a decimal point. Characters like `.`, `,`, or letters will cause validation to fail. Define it as `"integer"` or `Integer()`.

#### 🔹 `decimal`
The `decimal` rule ensures the input is a decimal/float number. It uses Python's `decimal.Decimal` for high-precision validation, making it perfect for financial data. It will fail for non-numeric strings or incorrectly formatted decimals. Define it as `"decimal"` or `Decimal()`.

#### 🔹 `digits`
The `digits` rule ensures the input is numeric and has an **exact** number of digits. For example, `"digits:5"` ensures the value is exactly 5 digits long (e.g., `"12345"`). It is often used for PIN codes or fixed-length IDs. Define it as `"digits:value"` or `Digits(value)`.

#### 🔹 `digits_between`
The `digits_between` rule validates that the field is numeric and its total digit count falls within a specified range $[min, max]$. This is useful for variable-length numeric identifiers like phone extension numbers. Define it as `"digits_between:min,max"` or `DigitsBetween(min, max)`.

#### 🔹 `min`
The `min` rule is powerful and **context-aware**:
- **Numbers**: It validates that the value is greater than or equal to the minimum.
- **Strings/Collections**: It validates that the length (number of characters or elements) is at least the minimum.
Define it as `"min:value"` or `Min(value)`.

#### 🔹 `max`
The `max` rule is the counterpart to `min` and is also **context-aware**:
- **Numbers**: It validates that the value is less than or equal to the maximum.
- **Strings/Collections**: It validates that the length does not exceed the maximum.
- **Files**: It can also be used to limit file size in bytes (though `size` or `mimes` are often preferred for clarity).
Define it as `"max:value"` or `Max(value)`.

#### 🔹 `between`
The `between` rule validates that a value or length falls within an inclusive range $[min, max]$. Like `min`/`max`, it works based on value for numbers and length for strings/arrays. Define it as `"between:min,max"` or `Between(min, max)`.

#### 🔹 `size`
The `size` rule ensures strict equality for a value or length.
- **Numbers**: The value must be exactly equal to the parameter.
- **Strings/Collections**: The length must be exactly equal to the parameter.
- **Files**: The file size must be exactly the specified number of bytes.
Define it as `"size:value"` or `Size(value)`.

#### 🔹 `gt` / `gte` / `lt` / `lte`
These rules provide explicit mathematical comparisons:
- **`gt`**: Greater Than (`>`).
- **`gte`**: Greater Than or Equal (`>=`).
- **`lt`**: Less Than (`<`).
- **`lte`**: Less Than or Equal (`<=`).
These rules specifically perform numeric comparisons using decimal precision. They are defined as `"gt:value"`, `"gte:value"`, etc.

#### 🔹 `max_digits` / `min_digits`
The `max_digits` and `min_digits` rules specifically limit the number of digits in a numeric value, regardless of decimals or signs. For example, `-12.34` has 4 digits. These rules are distinct from `min`/`max` as they count symbols rather than evaluating numerical worth. Define as `"max_digits:value"` or `MaxDigits(value)`.

#### 🔹 `multiple_of`
The `multiple_of` rule ensures that the input value is a multiple of a given step. For example, `"multiple_of:5"` would allow 5, 10, 15, etc. It works with both integers and floats. Define it as `"multiple_of:value"` or `MultipleOf(value)`.

---

### 📅 Date & Time Rules

#### 🔹 `date`
The `date` rule validates that the input is a valid date. It accepts Python `date` or `datetime` objects, as well as strings that can be processed by standard date parsers. Use it to ensure a field holds a real calendar date. Define it as `"date"` or `Date()`.

#### 🔹 `date_equals`
The `date_equals` rule ensures the input date matches a specific date. You can provide a fixed date string (e.g., `2023-12-25`) as the parameter. It compares only the date portion, ignoring time if present. Define it as `"date_equals:date"` or `DateEquals("date")`.

#### 🔹 `date_format`
The `date_format` rule enforces a strict pattern for date strings using standard Python `strftime` placeholders (e.g., `%Y-%m-%d`). Validation will fail if the input does not match the format exactly. Define it as `"date_format:format"` or `DateFormat("format")`.

#### 🔹 `after` / `after_or_equal`
These rules ensure that the input date occurs after (or on/after) a specific point in time. In addition to fixed dates, they support relative keywords:
- **`today`**: Validation against the current date.
- **`tomorrow`**: Validation against the next day.
- **`yesterday`**: Validation against the previous day.
Define as `"after:today"` or `After("today")`.

#### 🔹 `before` / `before_or_equal`
These rules ensure that the input date occurs before (or on/before) a specific comparison date. They also support relative keywords like `today`, `tomorrow`, and `yesterday`. This is ideal for validating birthdates (must be before today). Define as `"before:2030-01-01"` or `Before("2030-01-01")`.

#### 🔹 `timezone`
The `timezone` rule validates that the input string is a valid timezone identifier recognized by the system (e.g., `"UTC"`, `"Asia/Jakarta"`, `"America/New_York"`). It ensures compatibility with time-sensitive logic. Define it as `"timezone"` or `Timezone()`.

---

### 📦 Array & Collection Rules

#### 🔹 `array`
The `array` rule validates that the input is a Python dictionary (`dict`). This is typically used as a base check before validating nested fields within that dictionary. Use it to ensure your data structure matches your expectations. Define it as `"array"` or `Array()`.

#### 🔹 `list`
The `list` rule ensures that the input is a Python list/sequence (`list`, `tuple`, `set`). It confirms that you are dealing with a collection of items rather than a single value or a keyed dictionary. Define it as `"list"` or `List()`.

#### 🔹 `entries`
The `entries` rule validates the total number of items within an array or collection. For example, `"entries:3"` ensures the list has exactly 3 elements. This rule works for lists, dictionaries, and strings. Define it as `"entries:value"` or `Entries(value)`.

#### 🔹 `distinct`
The `distinct` rule validates that a collection contains only unique values. It will fail if any duplicate items are found within a list or tuple. This is essential for fields like "Selected Tags" where duplicates are not allowed. Define it as `"distinct"` or `Distinct()`.

#### 🔹 `in_array`
The `in_array` rule validates that the current field's value exists within another field that contains a list or array. For example, if `selected_id` must be one of the IDs in the `available_ids` list, use this. Define it as `"in_array:other_field"` or `InArray("other_field")`.

#### 🔹 `in_array_keys`
The `in_array_keys` rule validates that the input value matches one of the keys of a specified dictionary in the input data. Use this when you need to validate if a value is a valid index for a related dynamic object. Define it as `"in_array_keys:dict_field"` or `InArrayKeys("dict_field")`.

---

### ⚖️ Comparison Rules

#### 🔹 `same`
The `same` rule validates that the value of the current field is exactly equal to the value of another field in the input data. This is essential for cross-field consistency, such as "Confirm Password" or "Repeat Email" fields. Define it as `"same:other_field"` or `Same("other_field")`.

#### 🔹 `different`
The `different` rule ensures that the current field's value is not equal to another specified field's value. This is a common security practice to prevent users from using their username as their password. Define it as `"different:other_field"` or `Different("other_field")`.

#### 🔹 `in`
The `in` rule validates that the input value exists within a provided list of allowed values. It supports multiple parameters (e.g., `"in:admin,editor,viewer"`). The check is case-sensitive and handles both string and numeric types. Define it as `"in:val1,val2"` or `In("val1", "val2")`.

#### 🔹 `not_in`
The `not_in` rule is the inverse of `in`. It ensures the input value does NOT exist in the provided list of restricted values. Use this to blacklist specific inputs like reserved system names. Define as `"not_in:val1,val2"` or `NotIn("val1", "val2")`.

#### 🔹 `enum`
The `enum` rule is a specialized version of the inclusion check designed for Python's `Enum` classes. It ensures the value is a valid member of the specified enum. When used in class format, it provides fluent methods like `.only()` or `.exclude()` to dynamically restrict choices. Define as `"enum:EnumClassName"` or `Enum(EnumClass)`.

#### 🔹 `contains`
The `contains` rule validates that the input contains one or more specified sub-values. It is context-aware:
- **Strings**: It checks if the search values are substrings of the input.
- **Lists/Arrays**: It checks if the search values are present as elements in the list.
- **Dictionaries**: It checks if the values match any of the dictionary's keys.
Define it as `"contains:val1,val2"` or `Contains("val1", "val2")`.

#### 🔹 `exists`
The `exists` rule is a database-level validation that ensures the input value exists in a specific table and column. This is crucial for foreign key integrity (e.g., verifying a `user_id` actually exists in the `users` table). **Requires a Database Manager to be configured.** Define as `"exists:table,column"` or `Exists("table", "column")`.

#### 🔹 `unique`
The `unique` rule ensures that the input value does not already exist in a given database table and column. It is the standard rule for preventing duplicate usernames or emails. It supports an "ignore" parameter to skip checks for a specific record during updates. **Requires a Database Manager.** Define as `"unique:table,column,ignore:id_field"` or `Unique("table", "column")`.

---

### 📁 File & Image Rules

#### 🔹 `file`
The `file` rule validates that the input is a valid file upload object. It is compatible with major Python web frameworks (Flask, FastAPI, Django) and checks for standard file attributes like a filename and content length. Use this as a base check before applying specific constraints like size or type. Define it as `"file"` or `File()`.

#### 🔹 `image`
The `image` rule is a specialized file check that ensures the uploaded file is a common image format. It verifies the file's MIME type against a list of renderable formats including JPEG, PNG, GIF, BMP, SVG, and WebP. Define it as `"image"` or `Image()`.

#### 🔹 `mimes`
The `mimes` rule validates the file's extension against a list of allowed types (e.g., `"mimes:pdf,docx"`). It checks the file suffix to determine its type, providing a quick way to filter uploads by their intended format. Define it as `"mimes:ext1,ext2"` or `Mimes("ext1", "ext2")`.

#### 🔹 `mimetypes`
The `mimetypes` rule provides a more secure way to validate file types by checking the actual MIME content-type of the upload (e.g., `"mimetypes:application/pdf,image/jpeg"`). This is harder to spoof than a simple extension check. Define it as `"mimetypes:type1,type2"` or `Mimetypes("type1", "type2")`.

#### 🔹 `extensions`
The `extensions` rule specifically validates that the file has one of the provided extensions. While similar to `mimes`, it focuses purely on the filename's characters rather than the underlying media type. Define it as `"extensions:jpg,png"` or `Extensions("jpg", "png")`.

#### 🔹 `dimensions`
The `dimensions` rule provides exhaustive validation for image pixel sizes and aspect ratios. It is highly configurable and supports several specific parameters:
- **`width`**: The image must be exactly this many pixels wide.
- **`height`**: The image must be exactly this many pixels high.
- **`min_width`**: The width must be at least this value.
- **`max_width`**: The width cannot exceed this value.
- **`min_height`**: The height must be at least this value.
- **`max_height`**: The height cannot exceed this value.
- **`ratio`**: The aspect ratio of the image (e.g., `"3/2"` or `"1.5"` for landscape, `"1"` for square).
You can combine these parameters using the string syntax `"dimensions:width=1200,height=800"` or the `Dimensions()` class with named keyword arguments.

---

### ♟️ Conditional & Utility Rules

#### 🔹 `any_of`
The `any_of` rule implements a logical "OR" operation for validation. It takes multiple rule sets as parameters (separated by commas) and passes if the input satisfies at least one of those sets. For example, `"any_of:required|email,required|numeric"` allows a field to be either a valid email or a valid number. This is powerful for fields with polymorphic data types. Define it as `"any_of:rule_set1,rule_set2"` or `AnyOf(["rule_set1", "rule_set2"])`.

#### 🔹 `when`
The `when` rule is the most sophisticated rule in the library, allowing for complex, dynamic validation logic. It applies a specific set of rules to a field only if a certain condition is met. This condition can be a closure/function that evaluates the entire input dataset or a boolean check against another field. It is the primary tool for building reactive forms where fields depend on each other's values. Define it using the `When()` class for full flexibility.

#### 🔹 `prohibits`
The `prohibits` rule creates a relationship of mutual exclusion between fields. If the current field has a value (is not empty), all other fields specified in the parameters **must** be missing or empty in the input data. Use this for "either-or" scenarios, such as allowing either a `phone_number` or an `email_address`, but not both simultaneously. Define it as `"prohibits:field1,field2"` or `Prohibits("field1", "field2")`.

---

#### 🔹 `required_if`
The `required_if` rule makes a field mandatory only when another field matches a specific value. It is one of the most common conditional rules, used for scenarios like "if 'other' is selected in a dropdown, the 'description' field is required". Define it as `"required_if:other_field,value"` or `RequiredIf("other_field", "value")`.

#### 🔹 `required_unless`
The `required_unless` rule is the logical opposite of `required_if`. It makes a field mandatory **unless** another field matches a specific value. This is useful for fields that should always be provided except in one specific edge case. Define it as `"required_unless:other_field,value"` or `RequiredUnless("other_field", "value")`.

#### 🔹 `required_with`
The `required_with` rule makes a field mandatory if **any** of the other specified fields are present in the input data. Use this when a group of fields must be provided together if at least one of them is sent. Define it as `"required_with:field1,field2"` or `RequiredWith("field1", "field2")`.

#### 🔹 `required_with_all`
The `required_with_all` rule is stricter than `required_with`. It makes a field mandatory only if **all** of the other specified fields are present in the input data. This ensures that a complete set of related fields must include the current field. Define it as `"required_with_all:field1,field2"` or `RequiredWithAll("field1", "field2")`.

#### 🔹 `required_without`
The `required_without` rule makes a field mandatory if **any** of the other specified fields are missing from the input data. This is useful for ensuring that at least one of a set of alternatives is always provided (e.g., "either phone or email must be provided"). Define it as `"required_without:email"` or `RequiredWithout("email")`.

#### 🔹 `required_without_all`
The `required_without_all` rule makes a field mandatory if **all** of the other specified fields are missing. It acts as a final safety check to ensure that a required input is provided if all other alternatives were omitted. Define it as `"required_without_all:field1,field2"` or `RequiredWithoutAll("field1", "field2")`.

#### 🔹 `required_if_accepted`
The `required_if_accepted` rule makes a field mandatory only if another "agreement" field has been accepted (i.e., its value is `True`, `1`, `"yes"`, or `"on"`). This is common for legal forms where selecting a checkbox necessitates further information. Define it as `"required_if_accepted:terms"` or `RequiredIfAccepted("terms")`.

#### 🔹 `required_if_declined`
The `required_if_declined` rule makes a field mandatory only if another field has been declined (i.e., its value is `False`, `0`, `"no"`, or `"off"`). Use this to prompt for a reason when a user opts out of something. Define it as `"required_if_declined:mailing_list"` or `RequiredIfDeclined("mailing_list")`.

#### 🔹 `required_array_keys`
The `required_array_keys` rule is a structure-enforcement rule. It validates that the current input (which must be a dictionary) contains a specific set of keys. If any of the required keys are missing from the dictionary, validation fails. Define it as `"required_array_keys:id,name,email"` or `RequiredArrayKeys("id", "name")`.

#### 🔹 `prohibited_if`
The `prohibited_if` rule forbids a field from having any value if another specified field matches a certain value. This is useful for disabling certain options based on a user's role or status. Define it as `"prohibited_if:role,guest"` or `ProhibitedIf("role", "guest")`.

#### 🔹 `prohibited_unless`
The `prohibited_unless` rule forbids a field from having a value **unless** another field matches a specific value. This is a strict constraint for fields that are only allowed in very specific conditions. Define it as `"prohibited_unless:state,debug"` or `ProhibitedUnless("state", "debug")`.

#### 🔹 `prohibited_if_accepted`
The `prohibited_if_accepted` rule forbids a field if another field has been accepted. For example, if a user selects "I do not want a receipt", you might prohibit the `billing_address` field to minimize data collection. Define it as `"prohibited_if_accepted:no_receipt"`.

#### 🔹 `prohibited_if_declined`
The `prohibited_if_declined` rule forbids a field if another field has been declined. This is the inverse of the accepted variant and helps enforce logical consistency in conditional forms. Define it as `"prohibited_if_declined:opt_in"`.

#### 🔹 `present_if`
The `present_if` rule requires that a field key **must exist** in the input dictionary if another field matches a specific value. Note that the value of the field can be empty (like `None` or `""`); the rule only enforces that the key itself is present. Define it as `"present_if:type,complex"`.

#### 🔹 `present_unless`
The `present_unless` rule requires that a field key must exist **unless** another field matches a specific value. It ensures structural consistency in your input data for almost all cases except the specified exception. Define it as `"present_unless:status,draft"`.

#### 🔹 `present_with`
The `present_with` rule requires the current field to be present if **any** governing fields are present in the input. This is useful for grouping metadata fields that must exist if an optional header field is sent. Define it as `"present_with:meta_header"`.

#### 🔹 `present_with_all`
The `present_with_all` rule requires the field to be present only if **all** of the specified other fields are also present. This enforces a complete record structure when a full set of related data is submitted. Define it as `"present_with_all:f1,f2"`.

#### 🔹 `missing_if`
The `missing_if` rule enforces that a field **must not exist** in the input if another field matches a specific value. This is a powerful dynamic security constraint to prevent certain dataจาก appearing in specific contexts. Define it as `"missing_if:state,final"`.

#### 🔹 `missing_unless`
The `missing_unless` rule forbids a field from being present **unless** another field matches a specific value. It is a very strict structure constraint for fields that are only permitted in unique scenarios. Define it as `"missing_unless:auth,admin"`.

#### 🔹 `missing_with`
The `missing_with` rule ensures the current field is absent if **any** of the other specified fields are present. Use this to prevent conflicting or redundant fields from being submitted together. Define it as `"missing_with:alternative_id"`.

#### 🔹 `missing_with_all`
The `missing_with_all` rule ensures the current field is absent if **all** of the other specified fields are present. This is the strictest form of absence enforcement for related field groups. Define it as `"missing_with_all:f1,f2"`.

#### 🔹 `exclude_if`
The `exclude_if` rule dynamically removes the field from the final validated data if another field matches a certain value. This allows you to "clean" the output data based on other input parameters before passing it to your database or service. Define it as `"exclude_if:save_meta,false"`.

#### 🔹 `exclude_unless`
The `exclude_unless` rule removes the field from the final output **unless** another field matches a specific value. This ensures only necessary data remains in the final dictionary. Define it as `"exclude_unless:keep_temp,true"`.

#### 🔹 `exclude_with`
The `exclude_with` rule removes the field from the output if **any** of the specified other fields are present. This helps in de-duplicating data that might have come from multiple sources in the input. Define it as `"exclude_with:primary_id"`.

#### 🔹 `exclude_without`
The `exclude_without` rule removes the field from the output if **any** of the specified other fields are **missing**. This is useful for cleaning up optional supplementary data that doesn't have its required primary counterparty. Define it as `"exclude_without:parent_id"`.

---

## 🛡️ License
Safe Shield is open-sourced software licensed under the MIT license.
