# GuardedStruct

```elixir
Mix.install([
  {:guarded_struct, "~> 0.1.0"},
  {:html_sanitize_ex, "~> 1.4.3"},

  # Optional dependencies, It’s recommended to create your own
  # custom validation and sanitization functions instead of
  # enabling these libraries.
  {:email_checker, "~> 0.2.4"},
  {:ex_url, "~> 2.0"},
  {:ex_phone_number, "~> 0.4.3"},
])
```

## What's new in 0.1.0

`v0.1.0` rewrites the macro core on Spark. **Existing 0.0.x code keeps working — every example below runs unchanged.** New features are demonstrated near the bottom of this notebook:

| Feature | Section |
|---|---|
| Pattern-keyed maps (regex `field` names, closes #11) | _Pattern-keyed maps_ |
| `virtual_field` (closes #5) | _Virtual fields_ |
| `GuardedStruct.Validate` standalone API (closes #2) | _Standalone validation_ |
| Erlang Record support (closes #6) | _Erlang Records_ |
| Custom validators / sanitizers via Spark DSL | _Custom derive ops_ |
| Splode error wrapping | _Splode errors_ |
| Ash extension | _Ash integration_ |

### Conventions you'll see throughout

* **Error shape is canonical.** Every `{:error, …}` from `builder/1`,
  `__guarded_change__/1`, `Validate.*`, and the Ash `Change` is **always**
  `{:error, [error_map, ...]}`. Each error map has the same shape:
  `%{field: atom, action: atom, message: String, [errors: [...]]}`.
  Multi-field errors like `:required_fields` and `:authorized_fields` emit
  **one error per affected field** (no more `%{fields: [...]}` map).

* **Sanitizer arg order is pipe-friendly.** Direct calls take
  `(value, op)` — e.g. `"  Alice  " |> SanitizerDerive.sanitize(:trim)`.
  Same convention applies to `Extension.dispatch_sanitize/2`, generated
  `__sanitize__/2` callbacks on extension modules, and any user-supplied
  `:sanitize_derive` callback module.

* **LLM agents.** The repo ships agent context at the root
  ([`usage-rules.md`](https://github.com/mishka-group/guarded_struct/blob/master/usage-rules.md)
  + topic files in `usage-rules/`) and skills.sh-compatible
  [`.claude/skills/*/SKILL.md`](https://github.com/mishka-group/guarded_struct/tree/master/.claude/skills).
  Pull them into a project via
  `mix usage_rules.sync` (with [`:usage_rules`](https://github.com/ash-project/usage_rules)
  as a dev dep) or copy the skills directly.

See [`CHANGELOG.md`](https://github.com/mishka-group/guarded_struct/blob/master/CHANGELOG.md) for the full list of changes.

## About

The creation of this macro will allow you to build `Structs` that provide you with a number of important options, including the following:

1. **Validation**
2. **Sanitizing**
3. **Constructor**
4. **It provides the capacity to operate in a nested style simultaneously.**

Suppose you are going to collect a number of pieces of information from the user, and before doing anything else, you are going to sanitize them.
After that, you are going to validate each piece of data, and if there are no issues, you will either display it in a proper output or save it somewhere else.
All of the characteristics that are associated with this macro revolve around cleaning and validating the data.

The features that we list below are individually based on a particular strategy and requirement, but thankfully, they may be combined and mixed in any way that you see fit.

It bestows to you a significant amount of authority in this sphere.
After the initial version of this macro was obtained from the source of the `typed_struct` library, many sections of it were rewritten, or new concepts were taken from libraries in Rust and Scala and added to this library in the form of Elixir base.

The initial version of this macro can be found in the `typed_struct` library. Its base is a syntax that is very easy to comprehend, especially for non-technical product managers, and highly straightforward.

Before explaining the copyright, I must point out that the primary library, which is `typed_struct`, is no longer supported for a long time, so please pay attention to the following copyright.

[![Run in Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fmishka-group%2Fguarded_struct%2Fblob%2Fmaster%2Fguidance%2Fguarded-struct.livemd)

## Copyright

The code in this module is based on the `typed_struct` library (https://github.com/ejpcmac/typed_struct),
which is licensed under the MIT License.

Modifications and additions have been made to enhance its capabilities as part of the current project.

**MIT License**

Adding new Copyright (c) [2023] [Shahryar Tavakkoli at [Mishka Group](https://github.com/mishka-group)]

**Note:** If the license changes during the support of this project, this file will always remain on MIT

## Table of Contents

* [Defines a guarded struct](#defines-a-guarded-struct)
* [Defining a struct layer without additional options](#defining-a-struct-layer-without-additional-options)
* [Define a struct with settings related to essential keys or opaque type](#define-a-struct-with-settings-related-to-essential-keys-or-opaque-type)
* [Defining the struct by calling the validation module or calling from the module that contains the struct](#defining-the-struct-by-calling-the-validation-module-or-calling-from-the-module-that-contains-the-struct)
* [Define the struct by calling the main_validator for full access on the output](#define-the-struct-by-calling-the-main_validator-for-full-access-on-the-output)
* [Define struct with derive](#define-struct-with-derive)
* [Extending derive section](#extending-derive-section)
* [Struct definition with validator and derive simultaneously](#struct-definition-with-validator-and-derive-simultaneously)
* [Define a nested and complex struct](#define-a-nested-and-complex-struct)
* [Error and data output sample](#data-and-error-output-sample)
* [Set config to show error inside defexception](#set-config-to-show-error-inside-defexception)
* [Error defexception modules](#set-config-to-show-error-inside-defexception)
* [authorized_fields option to limit user input](#authorized_fields-option-to-limit-user-input)
* [Call external struct/structs module](#call-external-struct/structs-module)
* [List of structs](#list-of-structs)
* [Struct information function](#struct-information-function)
* [Transmitting whole output of builder function to its children](#transmitting-whole-output-of-builder-function-to-its-children)
* [Auto core key](#auto-core-key)
* [On core key](#on-core-key)
* [From core key](#from-core-key)
* [Domain core key](#domain-core-key)
* [Domain core key with equal and either support](#domain-core-key-with-equal-and-either-support)
* [Domain core key with Custom function support](#domain-core-key-with-custom-function-support)
* [Conditional fields](#conditional-fields)
* [List Conditional fields](#list-conditional-fields)

## Defines a guarded struct

The beginning of the block consists of the introduction of a `Struct` with the `guardedstruct` macro, which is solely responsible for recording a series of information in order to create a struct, as well as all of the fields with the `field` macro, and if you need to create another struct within this struct (in actuality, a module child within another module), you must use the `sub_field` macro.

**Note:** there is no restriction on the number of times you can call the `sub_field` macro or the field macro within the context of the `sub_field` macro.

**Note:** Because `Stract` does not prioritize the display of keys depending on your requirements, you do not need to follow the priority of the fields and call them in order to utilize the app.
Implement the program's logic, regardless of what it might be.

**Note:** Because of different limitations, if you want to write a test, you must first place the module in which you built the struct outside of the test macro. Once the struct has been built, you may then test it by calling it within the test macro itself. The examples it provides can also be found in the testing done by this library itself.

**Note:** this library is only supported on versions of `Elixir 1.15` and higher, as well as `OTP 26`, and that the manufacturer does not offer bug patches for problems that occur in older software versions.

**Note:** All of this library's dependencies are optional; nonetheless, if you require their use in your program, you will need to include them. We provide further explanation on the topic in the area you're looking for.

> Before continuing with the discussion about the library section and also offering practical examples in this field, it is important to understand that when you construct a struct in a module, after compilation in the runtime of the program, each module includes the following functional functions:

1. The `builder()` function is actually an action function, and it requires you to provide it with information in the form of a `map`.

2. The `enforce_keys()` function: this method returns the necessary keys of the first layer of the struct. However, if you want to display all of the keys of the nested struct, you will need to enter the `:all` input, which is not yet implemented in this version.

3. The `keys()` function has the same requirements as the `enforce_keys()` function, with the exception that it returns all of the keys, including the ones that aren't necessary.

---

**And also, any data that enters the `builder` function must go through the following path:**

1. If the `map` currently uses the `string` data type, it will be converted to the `atom` data type.

2. Eliminates the keys from the `struct` that are not present in the list

3. Determines whether or not all of the essential keys have been transmitted.

4. If you write your own custom validation, each field's validations will be checked.

> It is important to notice that regardless of the circumstances, this macro also inspects the module itself. If there is a `validator` function but none of the functions are set, it calls the validator function directly from the module itself into the field itself.

1. The output of the complete `struct` is entered into the mother validation, and the programmer is given the opportunity to write for the final output in this validation. This validation also provides the possibility of writing for the output of the struct.

> This macro will call the struct's `main_validator` directly from the module it has been placed in if, in this section, the `main_validator`  is not set in the struct but is found in the module that contains the struct.

1. If there were no problems in the previous phases (it is important to note that options 4 and 5 are not required), it will proceed to the next level of the program, which is the validation and custom Sanitizer stage.

2. To begin, the Sanitizer will alter the data so that it corresponds to what you have called in each field, and it will not return any errors. Even if the Sanitizer programmer is not utilized in the required type as a result of an accidental oversight, the data will still be passed to the following stage.

3. At this point, it will return an error or data for each field, depending on the validations that you called.

4. At the end of the process, you will receive a tuple that will either have problems in it or the final data with an ok status.

> It is important to keep in mind that if your `struct` is nested, all of the internal errors of these structs are also included in the list of problems. Additionally, the data will be sent to you when the status is positive, but only if you have called the parent of this struct.

> Note that each nested struct can be used on its own and possesses all of the capabilities that have been discussed thus far. For instance, if you have module `A` and you utilized the `sub_field` that is named `auth` in it, you may now use it separately from the `A.Auth` Use. Use.

## Defining a struct layer without additional options

```elixir
defmodule NoramalStruct do
  use GuardedStruct

  guardedstruct do
    field(:field_one, String.t())
    field(:field_two, integer(), enforce: true)
    field(:field_three, boolean(), enforce: true)
    field(:field_four, atom(), default: :hey)
  end
end
```

```elixir
NoramalStruct.builder(%{field_one: "Mishka", field_two: 1, field_three: true})
```

## Define a struct with settings related to essential keys or opaque type

##### Options

* `enforce` - if set to true, sets `enforce: true` to all fields by default.
  This can be overridden by setting `enforce: false` or a default value on
  individual fields.
* `opaque` - if set to true, creates an opaque type for the struct.
* `module` - if set, creates the struct in a submodule named `module`.

```elixir
defmodule EnforceStruct do
  use GuardedStruct

  guardedstruct enforce: true do
    field(:enforced_by_default, term())
    field(:not_enforced, term(), enforce: false)
    field(:with_default, integer(), default: 1)
    field(:with_false_default, boolean(), default: false)
    field(:with_nil_default, term(), default: nil)
  end
end
```

```elixir
# Status ok
EnforceStruct.builder(%{enforced_by_default: "test"})
```

```elixir
# Status error
EnforceStruct.builder(%{})
```

```elixir
# OR opaque
defmodule OpaqueStruct do
  use GuardedStruct

  guardedstruct opaque: true do
    field(:opaque_by_default, term())
    field(:not_enforced, term(), enforce: false)
    field(:with_default, integer(), default: 1)
    field(:with_false_default, boolean(), default: false)
    field(:with_nil_default, term(), default: nil)
  end
end
```

```elixir
OpaqueStruct.builder(%{})
```

```elixir
defmodule NormalEnforceAndDefaultStruct do
  use GuardedStruct

  guardedstruct do
    field(:enforced, term(), enforce: true)
    field(:with_default, integer(), default: 1)
    field(:with_false_default, boolean(), default: false)
    field(:with_nil_default, term(), default: nil)
  end
end
```

```elixir
NormalEnforceAndDefaultStruct.builder(%{})
```

```elixir
NormalEnforceAndDefaultStruct.builder(%{enforced: true})
```

```elixir
# OR create sub module

defmodule NormalSubModuleAsGuardedEstructMacroStruct do
  use GuardedStruct

  guardedstruct module: Struct do
    field(:field, term())
  end
end
```

```elixir
NormalSubModuleAsGuardedEstructMacroStruct.Struct.builder(%{field: "name"})
```

## Defining the struct by calling the validation module or calling from the module that contains the struct

##### Options

* `validator` - if set as tuple like this {ModuleName, :function_name} for each field,
  in fact you have a `builder` function that check the validation.

```elixir
# First, it looks at whether a validator has been set for each field,
# otherwise it looks inside the module.
defmodule NormalValidatorStruct do
  use GuardedStruct
  # You can call `validator` from another module
  # You can use field(:name, String.t(), validator: {__MODULE__, :validator})
  # Even you can not call validator, because you put the reserved name inside module
  guardedstruct do
    field(:name, String.t(), validator: {NormalValidatorStruct, :validator})
    field(:title, String.t())
  end

  def validator(:name, value) do
    if is_binary(value), do: {:ok, :name, value}, else: {:error, :name, "Not right!"}
  end

  # You can not use it, but it is mentioned here for test clarity
  def validator(name, value) do
    {:ok, name, value}
  end
end
```

```elixir
NormalValidatorStruct.builder(%{name: "Mishka"})
```

```elixir
NormalValidatorStruct.builder(%{name: :mishka})
```

> **Note**: There are other ways to call the `validator` function. Using a Tuple is the first method. It is shown in the code excerpt above. The module address appears in the first entry, and the function name in Atomic form appears in the second.
> Note that you can use `__MODULE__` if you define it in this module.
> But there's a simpler method. The module itself has a reserved word for `validator`. It only has to be valued as a field; the macro will verify that the input is accurate.

## Define the struct by calling the main_validator for full access on the output

##### Options

* `main_validator` - if set as tuple like this `{ModuleName, :function_name}`,
  for guardedstruct, in fact you have a global validation.

```elixir
# First, it looks at whether a main_validator has been set for each field,
# otherwise it looks inside the module.
defmodule NormalMainValidatorStruct do
  use GuardedStruct
  # You can call `main_validator` from another module
  # Even you can not call validator, because you put the reserved name inside module
  guardedstruct main_validator: {__MODULE__, :main_validator} do
    field(:name, String.t())
    field(:title, String.t())
  end

  # if `guardedstruct` has no `main_validator` which is configed
  def main_validator(value) do
    if Map.get(value, :title) == "mishka" do
      {:ok, value}
    else
      {:error, [%{message: "Not right!", field: :title, action: :validator}]}
    end
  end
end
```

```elixir
NormalMainValidatorStruct.builder(%{title: "mishka"})
```

```elixir
NormalMainValidatorStruct.builder(%{title: :mishka})
```

As the code sample above illustrates. `main_validator` is a bit rudimentary in terms of error and data presentation, and it can only be used once. since it gives you the ability to generate a unique code. Similar to `validator`, this option can be called by a macro within the same module or from another module.

## Define struct with derive

> derive is divided into two parts: `validate` and `sanitize`, which is priority with `sanitize`

**It should be noted that in the following tables you can see that in order to use some derives, you need to add its dependency on your project.**

#### Sanitize

| How to use                  | Dependencies              | Description                                                         |
| --------------------------- | ------------------------- | ------------------------------------------------------------------- |
| `"sanitize(trim)"`          | NO                        | Trim your string                                                    |
| `"sanitize(upcase)"`        | NO                        | Upcase your string                                                  |
| `"sanitize(downcase)"`      | NO                        | Downcase your string                                                |
| `"sanitize(capitalize)"`    | NO                        | Capitalize your string                                              |
| `"sanitize(basic_html)"`    | `:html_sanitize_ex`       | Sanitize your string base on `basic_html`                           |
| `"sanitize(html5)"`         | `:html_sanitize_ex`       | Sanitize your string base on `html5`                                |
| `"sanitize(markdown_html)"` | `:html_sanitize_ex`       | Sanitize your string base on `markdown_html`                        |
| `"sanitize(strip_tags)"`    | `:html_sanitize_ex`       | Sanitize your string base on `strip_tags`                           |
| `"sanitize(tag)"`           | `:html_sanitize_ex`       | Sanitize your string base on `html_sanitize_ex` selection           |
| `"sanitize(string_float)"`  | `:html_sanitize_ex` or NO | Sanitize your string base on `html_sanitize_ex` and `Float.parse/1` |
| `"sanitize(string_integer)"`  | `:html_sanitize_ex` or NO | Sanitize your string base on `html_sanitize_ex` and `Integer.parse/1` |

#### Validate

| How to use                                                       | Dependencies    | Description                                                                                         |
| ---------------------------------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------- |
| `"validate(string)"`                                             | NO              | Validate if the data is string                                                                      |
| `"validate(integer)"`                                            | NO              | Validate if the data is integer                                                                     |
| `"validate(list)"`                                               | NO              | Validate if the data is list                                                                        |
| `"validate(atom)"`                                               | NO              | Validate if the data is atom                                                                        |
| `"validate(bitstring)"`                                          | NO              | Validate if the data is bitstring                                                                   |
| `"validate(boolean)"`                                            | NO              | Validate if the data is boolean                                                                     |
| `"validate(exception)"`                                          | NO              | Validate if the data is exception                                                                   |
| `"validate(float)"`                                              | NO              | Validate if the data is float                                                                       |
| `"validate(function)"`                                           | NO              | Validate if the data is function                                                                    |
| `"validate(map)"`                                                | NO              | Validate if the data is map                                                                         |
| `"validate(nil_value)"`                                          | NO              | Validate if the data is nil value                                                                   |
| `"validate(not_nil_value)"`                                      | NO              | Validate if the data is not nil value                                                               |
| `"validate(number)"`                                             | NO              | Validate if the data is number                                                                      |
| `"validate(pid)"`                                                | NO              | Validate if the data is Elixir pid                                                                  |
| `"validate(port)"`                                               | NO              | Validate if the data is Elixir port                                                                 |
| `"validate(reference)"`                                          | NO              | Validate if the data is Elixir reference                                                            |
| `"validate(struct)"`                                             | NO              | Validate if the data is struct                                                                      |
| `"validate(tuple)"`                                              | NO              | Validate if the data is tuple                                                                       |
| `"validate(not_empty)"`                                          | NO              | Validate if the data is not empty - binary, map, list                                               |
| `"validate(max_len=10)"`                                         | NO              | Validate if the data is more than 10 - Range, integer, binary                                       |
| `"validate(min_len=10)"`                                         | NO              | Validate if the data is less than 10 - Range, integer, binary                                       |
| `"validate(url)"`                                                | NO              | Validate if the data is url                                                                         |
| `"validate(geo_url)"`                                            | `ex_url`        | Validate if the data is geo url                                                                     |
| `"validate(tell)"`                                               | `ex_url`        | Validate if the data is tell                                                                        |
| `"validate(tell=98)"`                                            | `ex_url`        | Validate if the data is tell with country code                                                      |
| `"validate(email)"`                                              | `email_checker` | Validate if the data is email                                                                       |
| `"validate(location)"`                                           | `ex_url`        | Validate if the data is location                                                                    |
| `"validate(string_boolean)"`                                     | NO              | Validate if the data is string boolean                                                              |
| `"validate(datetime)"`                                           | NO              | Validate if the data is datetime                                                                    |
| `"validate(range)"`                                              | NO              | Validate if the data is datetime                                                                    |
| `"validate(date)"`                                               | NO              | Validate if the data is datetime                                                                    |
| `"validate(regex='^[a-zA-Z]+@mishka\.group$')"`                  | NO              | Validate if the data is match with regex                                                            |
| `"validate(ipv4)"`                                               | NO              | Validate if the data is ipv4                                                                        |
| `"validate(not_empty_string)"`                                   | NO              | Validate if the data is not empty string                                                            |
| `"validate(uuid)"`                                               | NO              | Validate if the data is uuid                                                                        |
| `"validate(enum=String[admin::user::banned])"`                   | NO              | Validate if the data is one of the enum value, which is String                                      |
| `"validate(enum=Atom[admin::user::banned])"`                     | NO              | Validate if the data is one of the enum value, which is Atom                                        |
| `"validate(enum=Integer[1::2::3])"`                              | NO              | Validate if the data is one of the enum value, which is Integer                                     |
| `"validate(enum=Float[1.5::2.0::4.5])"`                          | NO              | Validate if the data is one of the enum value, which is Float                                       |
| `"validate(enum=Map[%{status: 1}::%{status: 2}::%{status: 3}])"` | NO              | Validate if the data is one of the enum value, which is Map                                         |
| `"validate(enum=Tuple[{:admin, 1}::{:user, 2}::{:banned, 3}])"`  | NO              | Validate if the data is one of the enum value, which is Tuple                                       |
| `"validate(equal=some_thing)"`                                   | NO              | Validate if the data is equal with validation value, which is any type                              |
| `"validate(either=[string, enum=Integer[1::2::3]])"`             | NO              | Validate if the data is valid with each derive validation                                           |
| `"validate(custom=[Enum, all?])"`                                | NO              | Validate if the you custom function returns trueو **Please read section 20**                       |
| `"validate(some_string_float)"`                                  | NO              | Validate if the string data is float (Somewhat by removing the string)                              |
| `"validate(string_float)"`                                       | NO              | Validate if the string data is float (Strict mode)                                                  |
| `"validate(string_integer)"`                                     | NO              | Validate if the string data is integer (Strict mode)                                                |
| `"validate(some_string_integer)"`                                | NO              | Validate if the string data is integer (Somewhat by removing the string)                            |
| `"validate(not_flatten_empty)"`                                  | NO              | Validate the list if it is empty by summing and flattening the entire list                          |
| `"validate(not_flatten_empty_item)"`                             | NO              | Validate the list if it is empty by summing and flattening the entire list and first level children |
| `"validate(queue)"`                             | NO              | Validate the data is Erlang queue or not |
| `"validate(username)"`                             | NO              | Validate the input has username format or not |
| `"validate(full_name)"`                             | NO              | Validate the input has full_name format or not |

```elixir
defmodule NormalDeriveStruct do
  use GuardedStruct

  guardedstruct do
    field(:id, integer(), derive: "sanitize(trim) validate(integer, max_len=20, min_len=5)")
    field(:title, String.t(), derive: "sanitize(trim, upcase) validate(not_empty_string)")

    field(:name, String.t(),
      derive: "sanitize(trim, capitalize) validate(string, not_empty, max_len=20)"
    )
  end
end
```

```elixir
NormalDeriveStruct.builder(%{title: "mishka"})
```

```elixir
NormalDeriveStruct.builder(%{title: :mishka})
```

## Extending derive section

##### Options

* `validate_derive` - It can be just one module or a list of modules
* `sanitize_derive` - It can be just one module or a list of modules

- First set Application env:

```elixir
Application.put_env(:guarded_struct, :validate_derive, [TestValidate, TestValidate2])
Application.put_env(:guarded_struct, :sanitize_derive, [TestSanitize, TestSanitize2])

# OR
Application.put_env(:guarded_struct, :validate_derive, TestValidate)
Application.put_env(:guarded_struct, :sanitize_derive, TestSanitize)
```

```elixir
defmodule TestValidateModule do
  def validate(:testv1, input, field) do
    if is_binary(input),
      do: input,
      else: {:error, field, :testv1, "The #{field} field must not be empty"}
  end
end

defmodule TestValidateModule2 do
  def validate(:testv2, input, field) do
    if is_binary(input),
      do: input,
      else: {:error, field, :testv1, "The #{field} field must not be empty"}
  end
end

defmodule TestSanitizeModuleOne do
  def sanitize(input, :capitalize_v1) do
    if is_binary(input), do: String.capitalize(input) <> "::_v1", else: input
  end
end

defmodule TestSanitizeModuleTwo do
  def sanitize(input, :capitalize_v2) do
    if is_binary(input), do: String.capitalize(input) <> "::_v2", else: input
  end
end
```

```elixir
defmodule NormalExtendedModuleDeriveStruct do
  use GuardedStruct

  guardedstruct validate_derive: TestValidateModule, sanitize_derive: TestSanitizeModuleOne do
    field(:id, integer(), derive: "sanitize(trim) validate(not_exist)")
    field(:title, String.t(), derive: "sanitize(trim) validate(string)")
    field(:name, String.t(), derive: "sanitize(capitalize_v1) validate(string)")
  end
end
```

```elixir
NormalExtendedModuleDeriveStruct.builder(%{name: "Mishka"})
```

```elixir
# OR you can extend with list of modules
defmodule NormalExtendedListModuleDeriveStruct do
  use GuardedStruct

  guardedstruct validate_derive: [TestValidateModule, TestValidateModule2],
                sanitize_derive: [TestSanitizeModuleTwo, TestSanitizeModuleOne] do
    field(:id, integer(), derive: "validate(ineteger)")
    field(:name, String.t(), derive: "sanitize(capitalize_v2) validate(string)")
    field(:title, String.t(), derive: "sanitize(trim) validate(string)")
  end
end
```

```elixir
NormalExtendedListModuleDeriveStruct.builder(%{name: "Mishka"})
```

## Struct definition with validator and derive simultaneously

```elixir
# In this code, name field has not custom validator module and function
# Then it see the caller module for it
defmodule NormalSimultaneouslyDeriveAndValidatorStruct do
  use GuardedStruct

  guardedstruct do
    field(:name, String.t(),
      enforce: true,
      derive: "sanitize(trim, upcase) validate(not_empty)"
    )

    field(:title, String.t(), derive: "sanitize(trim, capitalize) validate(not_empty)")
  end

  def validator(:name, value) do
    if is_binary(value), do: {:ok, :name, "Mishka   "}, else: {:error, :name, "No, never"}
  end

  def validator(name, value) do
    {:ok, name, value}
  end
end
```

```elixir
NormalSimultaneouslyDeriveAndValidatorStruct.builder(%{name: "Mishka", title: "mishka"})
```

```elixir
# OR with custom validator

defmodule GuardedStructTest.AnotherModule do
  def validator(:name, value) do
    if is_binary(value), do: {:ok, :name, value}, else: {:error, :name, "No, never"}
  end
end

defmodule NormalSimultaneouslyDeriveWithCustomValidatorStruct do
  alias GuardedStructTest.AnotherModule
  use GuardedStruct

  guardedstruct do
    field(:name, String.t(),
      enforce: true,
      derive: "sanitize(trim, capitalize) validate(not_empty)",
      validator: {AnotherModule, :validator}
    )

    field(:title, String.t(), derive: "sanitize(trim, capitalize) validate(not_empty)")
  end

  # You can not use it, but it is mentioned here for test clarity
  def validator(name, value) do
    {:ok, name, value}
  end
end
```

```elixir
NormalSimultaneouslyDeriveWithCustomValidatorStruct.builder(%{name: "mishka"})
```

```elixir
NormalSimultaneouslyDeriveWithCustomValidatorStruct.builder(%{name: :mishka})
```

## Define a nested and complex struct

```elixir
defmodule NestedStruct do
  use GuardedStruct

  guardedstruct do
    field(:name, String.t(),
      derive:
        "sanitize(strip_tags, trim, capitalize) validate(string, not_empty, max_len=20, min_len=3)"
    )

    field(:family, String.t(),
      derive:
        "sanitize(basic_html, trim, capitalize) validate(string, not_empty, max_len=20, min_len=3)"
    )

    field(:age, integer(), enforce: true, derive: "validate(integer, max_len=110, min_len=18)")

    sub_field(:auth, struct(), enforce: true) do
      field(:server, String.t(), derive: "validate(regex='^[a-zA-Z]+@mishka\.group$')")

      field(:identity_provider, String.t(),
        derive: "sanitize(strip_tags, trim, lowercase) validate(not_empty)"
      )

      sub_field(:role, struct(), enforce: true) do
        field(:name, String.t(),
          derive: "sanitize(strip_tags, trim, lowercase) validate(enum=Atom[admin::user::banned])"
        )

        field(:action, String.t(), derive: "validate(string_boolean)")

        field(:status, String.t(),
          derive: "validate(enum=Map[%{status: 1}::%{status: 2}::%{status: 3}])"
        )
      end

      field(:last_activity, String.t(), derive: "sanitize(strip_tags, trim) validate(datetime)")
    end

    sub_field(:profile, struct()) do
      field(:site, String.t(), derive: "validate(url)")

      field(:nickname, String.t(), validator: {NestedStruct, :validator})
    end

    field(:username, String.t(),
      enforce: true,
      derive: "sanitize(tag=strip_tags) validate(not_empty, max_len=20, min_len=3)"
    )
  end

  def validator(:nickname, value) do
    if is_binary(value),
      do: {:ok, :nickname, value},
      else: {:error, :nickname, "Invalid nickname"}
  end

  def validator(field, value) do
    {:ok, field, value}
  end
end
```

```elixir
NestedStruct.builder(%{
  username: " <p>Mishka   </p>",
  auth: %{
    server: "users@mishka.tools",
    identity_provider: "google",
    role: %{
      name: :user,
      action: "true",
      status: %{status: 2}
    },
    last_activity: "2023-08-20 16:54:07.841434Z"
  },
  age: 18,
  family: "group",
  name: "mishka",
  profile: %{
    site: "https://elixir-lang.org",
    nickname: "mishka"
  }
})
```

```elixir
NestedStruct.builder(%{
  username: "mishka",
  auth: %{
    server: "users@mishka.tools",
    identity_provider: "google",
    role: %{
      name: :admin,
      action: "test",
      status: %{status: 2}
    },
    last_activity: "20213-08-20 16:54:07.841434Z"
  },
  age: 18,
  family: "group",
  name: "mishka",
  profile: %{
    site: "https://elixir-lang.org",
    nickname: :test
  }
})
```

```elixir
NestedStruct.keys() |> IO.inspect()
NestedStruct.keys(:profile) |> IO.inspect()
NestedStruct.__information__() |> IO.inspect()
NestedStruct.enforce_keys() |> IO.inspect()
NestedStruct.enforce_keys(:profile) |> IO.inspect()
```

## Data and Error output sample

<!-- livebook:{"force_markdown":true} -->

```elixir
# Elixir :ok data
{:ok,
  %__MODULE__.TestNestedStruct{
    username: "Mishka",
    profile: %__MODULE__.TestNestedStruct.Profile{
      nickname: "mishka",
      site: "https://elixir-lang.org"
    },
    auth: %__MODULE__.TestNestedStruct.Auth{
      last_activity: "2023-08-20 16:54:07.841434Z",
      role: %__MODULE__.TestNestedStruct.Auth.Role{
        action: "true",
        name: :user,
        status: %{status: 2}
      },
      identity_provider: "google",
      server: "users@mishka.tools"
    },
    age: 18,
    family: "Group",
    name: "Mishka"
}}
```

<!-- livebook:{"break_markdown":true} -->

<!-- livebook:{"force_markdown":true} -->

```elixir
# Elixir :error
{:error,
 [
   %{field: :profile, errors: [%{message: "Invalid nickname", field: :nickname}]},
   %{
     field: :auth,
     errors: [
       %{
         message: "Invalid DateTime format in the last_activity field",
         field: :last_activity,
         action: :datetime
       },
       %{
         field: :role,
         errors: [
           %{
             message: "Invalid boolean format in the action field",
             field: :action,
             action: :string_boolean
           }
         ]
       }
     ]
   }
 ]}
```

## Set config to show error inside defexception

You may want to display the received errors in Elixir's `defexception`. you just need to enable the `error: true` for `guardedstruct` macro or `sub_field`.

**Note**: When you enable the `error` option. This macro will generate for you a module that is part of the parent module subset, and within that module, it will generate a `defexception` struct.

##### Error `defexception` modules

<!-- livebook:{"force_markdown":true} -->

```elixir
TestCallNestedStructWithError.Error
TestCallNestedStructWithError.Auth.Error
TestCallNestedStructWithError.Auth.Path.Error
```

##### Sample code

```elixir
defmodule TestCallNestedStructWithError do
  use GuardedStruct

  guardedstruct error: true do
    field(:name, String.t(), derive: "validate(string)")

    sub_field(:auth, struct(), error: true) do
      field(:action, String.t(), derive: "validate(not_empty)")

      sub_field(:path, struct(), error: true) do
        field(:name, String.t())
      end
    end
  end
end
```

And you should call it like this, the second entry should be `true` or `false` to show error `defexception`

```elixir
TestCallNestedStructWithError.builder(%{name: 1}, true)
```

## authorized_fields option to limit user input

If this option is not used, the program will automatically drop fields that are not defined; however, if this option is set, it will return an error to the user if they transmit a field that is not in the list of specified fields. If this option is not used, the program will automatically drop fields that are not defined.

**Please take note** that the `required_fields` and this section are not the same thing, and that the validation of the mandatory fields will take place after this section.

**Security note**: The assumption of the correctness of this function is that you must enter the name of each field correctly (as a map key), for example, Phoenix should consider a layer of care for the names of the fields, otherwise, a security problem may arise.

```elixir
defmodule TestAuthorizeKeys do
  use GuardedStruct

  guardedstruct authorized_fields: true do
    field(:name, String.t(), derive: "validate(string)")

    sub_field(:auth, struct(), authorized_fields: true) do
      field(:action, String.t(), derive: "validate(not_empty)")

      sub_field(:path, struct()) do
        field(:name, String.t())
      end
    end
  end
end
```

```elixir
TestAuthorizeKeys.builder(%{name: "Shahryar", test: "test"})
```

```elixir
TestAuthorizeKeys.builder(%{name: "Shahryar", auth: %{action: "admin", test: "test"}})
```

## Call external struct/structs module

This option can be helpful for you if you wish to construct your own modules in various files and then make those modules reusable in the future. Simply implement the macro in another module, and then call that module from the `field` macro. The `struct` and `structs` options are the ones in which the module can be placed. The first one will provide you with an indication that you will be given a map, and the second one will provide you with a list of maps.

```elixir
defmodule ExternalAuthModuleStruct do
  use GuardedStruct

  guardedstruct do
    field(:action, String.t(), derive: "validate(not_empty)")
  end
end

defmodule ExternalAuthCallModuleStruct do
  use GuardedStruct

  guardedstruct do
    field(:name, String.t(), derive: "validate(string)")
    field(:auth_path, struct(), struct: ExternalAuthModuleStruct)
    field(:auth_path1, struct(), structs: ExternalAuthModuleStruct)
  end
end
```

```elixir
ExternalAuthCallModuleStruct.builder(%{
  name: "Mishka",
  auth_path: %{action: "add"},
  auth_path1: [%{action: "add"}]
})
```

## List of structs

As was discussed in the earlier available choices. In the `field` macro that is used to call **another module**, as well as in the `sub_field` macro, you have the ability to retrieve a list of structs rather than a single struct.

```elixir
defmodule ListSubFieldAndExternalAuthCallModuleStruct do
  use GuardedStruct

  guardedstruct do
    field(:name, String.t(), derive: "validate(not_empty)")
    field(:auth_path, struct(), structs: ExternalAuthModuleStruct)

    sub_field(:profile, list(struct()), structs: true) do
      field(:github, String.t(), enforce: true, derive: "validate(url)")
      field(:nickname, String.t(), derive: "validate(not_empty)")
    end
  end
end
```

```elixir
ListSubFieldAndExternalAuthCallModuleStruct.builder(%{
  name: "mishka",
  auth_path: [
    %{action: "*:admin", path: %{role: "1"}},
    %{action: "*:user", path: %{role: "3"}}
  ]
})
```

```elixir
ListSubFieldAndExternalAuthCallModuleStruct.builder(%{
  name: "mishka",
  auth_path: [
    %{action: "*:admin", path: %{role: "1"}},
    %{action: "*:user", path: %{role: "3", rel: %{social: "github"}}}
  ],
  profile: [%{github: "https://github.com/mishka-group"}]
})
```

## Struct information function

You will need to include a function known as `__information__()` in each and every module that you develop for your very own `structs`. This function will store a variety of information, such as keys, callers, and so on.

**Note:** There is a possibility that further information will be added to this function; please check its output after each update.

**Note:** If you call another Struct module within the `field` macro, you should not use the `caller` key within this function. This is due to the fact that the constructor information is only available during **compile** time, and not run time.

<!-- livebook:{"force_markdown":true} -->

```elixir
TestStruct.__information__()
```

## Transmitting whole output of builder function to its children

Because new keys have been added, such as `auto`, `on`, and `from` which will be explained in more detail below. The `builder` function is available in the following two different styles.

> If you don't provide the `:root` key, you can just specify the child key, but if you do, you have to send the entire map as an `attar`. This is something to keep in mind.

<!-- livebook:{"force_markdown":true} -->

```elixir
def builder(attrs, error)

def builder({key, attrs} = input, error)
    when is_tuple(input) and is_map(attrs) and is_list(key) do
      ...
end
```

## Auto core key

Even if the user transmits the information and it is already in the input, such as with the ID field, the sequence of fields still has to be formed automatically. You can accomplish what you want to with the help of the `auto` option.

> As you can see in the code below, we have several types of `auto` option calls

---

> When the core keys are called, the entire primary map is sent to each child.

```elixir
defmodule AutoCoreKeyStruct do
  use GuardedStruct

  guardedstruct do
    field(:username, String.t(), derive: "validate(not_empty)")
    field(:user_id, String.t(), auto: {Ecto.UUID, :generate})
    field(:parent_id, String.t(), auto: {Ecto.UUID, :generate})

    sub_field(:profile, struct()) do
      field(:id, String.t(), auto: {Ecto.UUID, :generate})
      field(:nickname, String.t(), derive: "validate(not_empty)")

      sub_field(:social, struct()) do
        field(:id, String.t(), auto: {AutoCoreKeyStruct, :create_uuid, "test-path"})
        field(:skype, String.t(), derive: "validate(string)")
        field(:username, String.t(), from: "root::username")
      end
    end

    sub_field(:items, struct(), structs: true) do
      field(:id, String.t(), auto: {Ecto.UUID, :generate})
      field(:something, String.t(), derive: "validate(string)", from: "root::username")
    end
  end

  def create_uuid(default) do
    MishkaDeveloperTools.Helper.UUID.generate() <> "-#{default}"
  end
end
```

> **Note**: When changing a record in the database, for example, you might need to make sure that a particular piece of data does not get overwritten by an automatic piece of data if one already exists. To find a solution to this issue, you will need to invoke the `builder` function in the following manner.

```elixir
AutoCoreKeyStruct.builder(%{username: "mishka", user_id: "test_not_to_be_replaced"})
```

```elixir
AutoCoreKeyStruct.builder(
  {:root, %{username: "mishka", user_id: "test_not_to_be_replaced"}, :add}
)
```

```elixir
AutoCoreKeyStruct.builder(
  {:root, %{username: "mishka", user_id: "test_should_be_replaced"}, :edit}
)
```

The desired key can be derived from the information that was supplied by the user, and it is stored in the first entry of the `Tuple`. If it is `:root` or `[:root]`, it indicates that the entire data set is being referred to, and if it is a special key that must be valued as a list, it indicates that the `builder` will begin its operation from that particular key. It is important to notice that the key has to be `sub_field` if the path is chosen to be displayed.

## On core key

With the aid of this option, you can make the presence of a field dependent on the presence of another field and, if there is no error, produce an error message.

If you pay attention to the routing method, the routing will start from the sent map itself if `:root` is specified, but if it is not used, the routing will start from the received map in the child if it is not used.

> When the core keys are called, the entire primary map is sent to each child.

<!-- livebook:{"break_markdown":true} -->

##### Note:

> By default, `on` core key is called when the value of the calling field is sent; To force the field to be non-empty, you must use enforce.

```elixir
defmodule OnCoreKeyStruct do
  use GuardedStruct

  guardedstruct do
    field(:name, String.t(), derive: "validate(string)")

    sub_field(:profile, struct()) do
      field(:id, String.t(), auto: {Ecto.UUID, :generate})
      field(:nickname, String.t(), on: "root::name", derive: "validate(string)")
      field(:github, String.t(), derive: "validate(string)")

      sub_field(:identity, struct()) do
        field(:provider, String.t(), on: "root::profile::github", derive: "validate(string)")
        field(:id, String.t(), auto: {Ecto.UUID, :generate})
        field(:rel, String.t(), on: "sub_identity::auth_path::action")

        sub_field(:sub_identity, struct()) do
          field(:id, String.t(), auto: {Ecto.UUID, :generate})
          field(:auth_path, struct(), struct: ExternalAuthModuleStruct)
        end
      end
    end

    sub_field(:last_activity, list(struct()), structs: true) do
      field(:action, String.t(), enforce: true, derive: "validate(string)", on: "root::name")
    end
  end
end
```

```elixir
OnCoreKeyStruct.builder(%{
  name: "mishka",
  profile: %{
    nickname: "Mishka",
    github: "test",
    identity: %{
      provider: "git",
      sub_identity: %{id: "test", auth_path: %{action: "admin/edit"}}
    }
  }
})
```

```elixir
OnCoreKeyStruct.builder(%{
  name: "mishka",
  profile: %{
    nickname: "Mishka",
    identity: %{provider: "git"}
  }
})
```

## From core key

You can select this alternative if you require any data that was delivered in another key to be incorporated into the key that you are looking for. If the key is present, the data associated with it will be copied; however, if the key is not there, the data in and of itself will be retained.

If you pay attention to the routing method, the routing will start from the sent map itself if `:root` is specified, but if it is not used, the routing will start from the received map in the child if it is not used.

---

> When the core keys are called, the entire primary map is sent to each child.

> Note: It is possible that you will need to check that the field you wish to duplicate exists, and in order to do so, you can use either the `on` key or the `enforce` option.

> Note: You can use this feature from inside the `conditional_field` list to outside the data, but you cannot point to the inside of the list from outside the list.

```elixir
defmodule FromCoreKeyStruct do
  use GuardedStruct

  guardedstruct do
    field(:username, String.t(), derive: "validate(not_empty)")
    field(:user_id, String.t(), auto: {Ecto.UUID, :generate})
    field(:parent_id, String.t(), auto: {Ecto.UUID, :generate})

    sub_field(:profile, struct()) do
      field(:id, String.t(), auto: {Ecto.UUID, :generate})
      field(:nickname, String.t(), derive: "validate(not_empty)")

      sub_field(:social, struct()) do
        field(:id, String.t(), auto: {FromCoreKeyStruct, :create_uuid, "test-path"})
        field(:skype, String.t(), derive: "validate(string)")
        field(:username, String.t(), from: "root::username")
      end
    end

    sub_field(:items, struct(), structs: true) do
      field(:id, String.t(), auto: {Ecto.UUID, :generate})
      field(:something, String.t(), derive: "validate(string)", from: "root::username")
    end
  end

  def create_uuid(default) do
    MishkaDeveloperTools.Helper.UUID.generate() <> "-#{default}"
  end
end
```

```elixir
FromCoreKeyStruct.builder(%{
  username: "mishka",
  user_id: "test_to_be_replaced",
  profile: %{nickname: "Mishka", social: %{skype: "mishka_skype", username: "none_to_test"}}
})
```

## Domain core key

When dealing with a structure that is heavily nested, it is occasionally necessary to establish the permitted range of values for a set of parameters based on the input provided by a parent.
Note that similar to earlier parts, we do not transfer the entirety of either the `Struct` or the `Map` to this feature in this particular section. Always keep in mind the top-down structure, often known as the parent-to-child relationship.

```elixir
defmodule AllowedParentDomain do
  use GuardedStruct

  guardedstruct authorized_fields: true do
    field(:username, String.t(),
      domain: "!auth.action=String[admin, user]::?auth.social=Atom[banned]",
      derive: "validate(string)"
    )

    field(:type_social, String.t(),
      domain: "?auth.type=Map[%{name: \"mishka\"}, %{name: \"mishka2\"}]",
      derive: "validate(string)"
    )

    sub_field(:auth, struct(), authorized_fields: true) do
      field(:action, String.t(), derive: "validate(not_empty)")
      field(:social, atom(), derive: "validate(atom)")
      field(:type, map(), derive: "validate(map)")
    end
  end
end
```

```elixir
AllowedParentDomain.builder(%{username: "mishka", auth: %{action: "admin1"}})
```

```elixir
AllowedParentDomain.builder(%{
  username: "mishka",
  auth: %{action: "admin", social: :banned}
})
```

**Please see the `domain` core key, for example:**

<!-- livebook:{"force_markdown":true} -->

```elixir
domain: "!auth.action=String[admin, user]::?auth.social=Atom[banned]"
```

**In this part:**

* If `username` key is sent you must have `auth.action` path which is string `admin` or string `user`
* If `username` key is sent you you can have `auth.social` path which is just atom `:banned`
* So the `auth.social` can be nil and inside user input impossible nil

**Note**: Within this section of the core keys, we are making use of the `:enum` Derive. You are free to make advantage of any and all of the amenities that this Derive provides.

---

**Note:**:

It is important to think about the fact that the `domain` core key does not consider any update of  the `auto` core key and instead examines the data that was initially entered in the `builder`.
The information that was entered is not altered in any way by this function; it is merely validating it.

## Domain core key with equal and either support

This component supplies all of the facilities that are necessary to be able to utilize the two keys labeled `equal` and `either`, but because of a little interference, its style is different from the original style of each of these keys, and you are required to adhere to these guidelines. Play can be found in this section.

##### Example for `equal`

<!-- livebook:{"force_markdown":true} -->

```elixir
"?auth.equal=Equal[Atom>>name]"
```

##### Example for `either`

<!-- livebook:{"force_markdown":true} -->

```elixir
domain: "?auth.either=Either[string, enum>>Integer[1>>2>>3]]"
```

**Note**: As you can see, the `>>` indicator has been utilized in this area, despite the fact that it was not included in the first version of these validations.

## Domain core key with Custom function support

Imagine that you have a function that determines for you whether or not the data that has been sent is valid.

**Note**: the function is required to have an input.

**Note**: the function must return either true or false.

**Note**: When writing code for the module, do not utilize aliases; instead, write the module's complete path.

```elixir
defmodule AllowedParentCustomDomain do
  use GuardedStruct
  @module_path "AllowedParentCustomDomain"

  guardedstruct authorized_fields: true do
    field(:username, String.t(),
      domain: "!auth.action=Custom[#{@module_path}, is_stuff?]",
      derive: "validate(string)"
    )

    sub_field(:auth, struct(), authorized_fields: true) do
      field(:action, String.t(), derive: "validate(not_empty)")
    end
  end

  def is_stuff?(data) when data == "ok", do: true
  def is_stuff?(_data), do: false
end
```

```elixir
AllowedParentCustomDomain.builder(%{
  username: "mishka",
  auth: %{action: "ok"}
})
```

```elixir
AllowedParentCustomDomain.builder(%{
  username: "mishka",
  auth: %{action: "error"}
})
```

**Note**: if you want to use `custom` inside `derive` validation, you should do like this:

```elixir
defmodule CustomValidationDerive do
  use GuardedStruct

  guardedstruct authorized_fields: true do
    field(:status, String.t(), derive: "validate(custom=[#{__MODULE__}, is_stuff?])")
  end

  def is_stuff?(data) when data == "ok", do: true
  def is_stuff?(_data), do: false
end
```

**Note**: You can see when you use it inside a derive, the GuardedStruct calculates the you module `alias`.

```elixir
CustomValidationDerive.builder(%{status: "ok"})
```

```elixir
CustomValidationDerive.builder(%{status: "error"})
```

## Conditional fields

One of the unique capabilities of this macro is the ability to define conditions and differentiate between the various kinds of `fields`. Assume that you want the `social` field to be able to take both a value `string` and a `map` where `address` and `provider` are included in the `map`.
It is important to notice that the `conditional_field` contained within this macro have the capability of supporting `sub_field`. You can look at some illustrations down below.

Note: Please read this if you want to document any conditional fields for your API. For instance, your front team ought to be aware of which area of the output is for. You have the option of adding the `hint` keyword in accordance with the aforementioned code. And the clue is in your practice here.

**Output of hint**: `__hint__`

```elixir
defmodule ConditionalFieldValidatorTestValidators do
  def is_string_data(field, value) do
    if is_binary(value), do: {:ok, field, value}, else: {:error, field, "It is not string"}
  end

  def is_map_data(field, value) do
    if is_map(value), do: {:ok, field, value}, else: {:error, field, "It is not map"}
  end

  def is_list_data(field, value) do
    if is_list(value), do: {:ok, field, value}, else: {:error, field, "It is not list"}
  end

  def is_flat_list_data(field, value) do
    if is_list(value),
      do: {:ok, field, List.flatten(value)},
      else: {:error, field, "It is not list"}
  end

  def is_int_data(field, value) do
    if is_integer(value), do: {:ok, field, value}, else: {:error, field, "It is not integer"}
  end
end
```

```elixir
defmodule ConditionalFieldComplexTest do
  use GuardedStruct
  alias ConditionalFieldValidatorTestValidators, as: VAL

  guardedstruct do
    field(:provider, String.t())

    sub_field(:profile, struct()) do
      field(:name, String.t(), enforce: true)
      field(:family, String.t(), enforce: true)

      conditional_field(:address, any()) do
        field(:address, String.t(), hint: "address1", validator: {VAL, :is_string_data})

        sub_field(:address, struct(), hint: "address2", validator: {VAL, :is_map_data}) do
          field(:location, String.t(), enforce: true)
          field(:text_location, String.t(), enforce: true)
        end

        sub_field(:address, struct(), hint: "address3", validator: {VAL, :is_map_data}) do
          field(:location, String.t(), enforce: true, derive: "validate(string, location)")
          field(:text_location, String.t(), enforce: true)
          field(:email, String.t(), enforce: true)
        end
      end
    end

    conditional_field(:product, any()) do
      field(:product, String.t(), hint: "product1", validator: {VAL, :is_string_data})

      sub_field(:product, struct(), hint: "product2", validator: {VAL, :is_map_data}) do
        field(:name, String.t(), enforce: true)
        field(:price, integer(), enforce: true)

        sub_field(:information, struct()) do
          field(:creator, String.t(), enforce: true)
          field(:company, String.t(), enforce: true)

          conditional_field(:inventory, integer() | struct(), enforce: true) do
            field(:inventory, integer(),
              hint: "inventory1",
              validator: {VAL, :is_int_data},
              derive: "validate(integer, max_len=33)"
            )

            sub_field(:inventory, struct(), hint: "inventory2", validator: {VAL, :is_map_data}) do
              field(:count, integer(), enforce: true)
              field(:expiration, integer(), enforce: true)
            end
          end
        end
      end
    end
  end
end
```

```elixir
ConditionalFieldComplexTest.builder(%{
  provider: "Mishka",
  profile: %{
    name: "Shahryar",
    family: "Tavakkoli",
    address: %{
      location: "geo:48.198634,-16.371648,3.4;crs=wgs84;u=40.0",
      text_location: "Nowhere",
      email: "shahryar@mishka.tools"
    }
  },
  product: %{
    name: "MishkaDeveloperTools",
    price: 0,
    information: %{
      creator: "Shahryar Tavakkoli",
      company: "mishka group",
      inventory: %{
        count: 3_000_000,
        expiration: 33
      }
    }
  }
})
```

## List Conditional fields

The `conditional_fields` is one of the most important aspects of this macro, which is available to the programmer in all of its many variants. Typically, you have the ability to send a map through the `builder`. If the map is compliant with one of the requirements, your output will be returned. Additionally, you have the ability to transmit the value of one of the keys related to the map in the form of a list.
Now, with this option, you are able to transmit the complete entry as a list. In addition, you are able to send one of the items on this list as another list, and nesting functionality has been made available to you.

<!-- livebook:{"force_markdown":true} -->

```elixir
conditional_field(:activities, any(), structs: true) do
  field(:activities, struct(), struct: ExtrenalConditional, validator: {VAL, :is_map_data}, hint: "activities1")

  field(:activities, struct(), structs: ExtrenalConditional, validator: {VAL, :is_list_data}, hint: "activities2")

  field(:activities, String.t(), hint: "activities3", validator: {VAL, :is_string_data})
end
```

As you can see in the code above, you only need to give the macro the `structs: true` option

##### Note:

> Using a list `conditional_field` in a nested list can create a logical bug for you if the list is not flattened, **Please test your builder before releasing to production**.

## New features in 0.1.0

The sections below are new in `v0.1.0`. They show features added on top of the existing API; everything above this point continues to work unchanged.

## Pattern-keyed maps

A `field` whose name is a regex declares a free-form map shape. The struct's `builder/1` returns a plain validated map. Useful for translations, headers, sharded data — anything where keys are runtime-unknown but values share a uniform shape.

```elixir
defmodule Shard do
  use GuardedStruct
  guardedstruct do
    field :node, String.t(), enforce: true, derives: "sanitize(trim) validate(ipv4)"
  end
end

defmodule ShardsMap do
  use GuardedStruct
  guardedstruct do
    field ~r/^shard_\d+$/, struct(), struct: Shard, derives: "validate(map, not_empty)"
  end
end

ShardsMap.builder(%{
  "shard_1" => %{node: "10.0.0.1"},
  "shard_2" => %{node: "10.0.0.2"}
})
# => {:ok, %{
#      "shard_1" => %Shard{node: "10.0.0.1"},
#      "shard_2" => %Shard{node: "10.0.0.2"}
#    }}
```

Keys stay as strings (atom-table-exhaustion safe by default). Mixing atom-keyed and regex-keyed `field`s in the same `guardedstruct` raises a compile-time error.

## Virtual fields

A `virtual_field` is validated through the full pipeline but excluded from the generated `defstruct`. Use it for input fields needed only by `main_validator/1`:

```elixir
defmodule Signup do
  use GuardedStruct

  guardedstruct do
    field :email, String.t(), enforce: true, derives: "validate(email_r)"
    field :password, String.t(), enforce: true, derives: "validate(string, min_len=8)"
    virtual_field :password_confirm, String.t(), derives: "validate(string)"
  end

  def main_validator(attrs) do
    if attrs[:password] == attrs[:password_confirm] do
      {:ok, attrs}
    else
      {:error,
       [%{field: :password_confirm, action: :match, message: "passwords don't match"}]}
    end
  end
end

Signup.builder(%{
  email: "alice@x.com",
  password: "longpassword",
  password_confirm: "longpassword"
})
# => {:ok, %Signup{email: "alice@x.com", password: "longpassword"}}
#    Note: password_confirm is NOT on the struct — it was used by main_validator and dropped.
```

## Dynamic fields

A `dynamic_field` is a free-form map field with **passthrough semantics** — whatever map you submit (string keys, atom keys, mixed, nested) round-trips byte-identical to `builder/1`'s output. No string-to-atom conversion of inner keys, ever. Defaults to `%{}`, `type :: map()`, `derives: "validate(map)"`.

```elixir
defmodule WithMetadata do
  use GuardedStruct
  guardedstruct do
    field :name, String.t(), enforce: true
    dynamic_field :metadata
  end
end

WithMetadata.builder(%{name: "Alice", metadata: %{"role" => "admin", "tier" => 2}})
# => {:ok, %WithMetadata{name: "Alice", metadata: %{"role" => "admin", "tier" => 2}}}
#    String keys stay as strings — no String.to_atom of user input.

WithMetadata.builder(%{name: "Alice", metadata: %{:role => "admin", "tier" => 2}})
# => {:ok, %WithMetadata{metadata: %{role: "admin", "tier" => 2}}}
#    Mixed keys round-trip exactly. This is atom-attack-safe by default.
```

Why it matters: parsing `%{"<attacker-controlled>" => ...}` from JSON would otherwise create new atoms in the BEAM atom table (which is bounded). `dynamic_field` lets you accept arbitrary user maps without that risk.

## `@derives` decorator

Alternative to inline `derives:` — keeps field declarations short when the derive string gets long. The `@derives` attribute applies to the very NEXT field declaration (one-shot, like `@doc`).

```elixir
defmodule Article do
  use GuardedStruct

  guardedstruct do
    @derives "sanitize(trim) validate(string, not_empty, max_len=200)"
    field :title, String.t(), enforce: true

    @derives "sanitize(trim, downcase) validate(string, max_len=80)"
    field :slug, String.t(), enforce: true

    @derives "validate(integer, min_len=0)"
    field :views, integer(), default: 0
  end
end
```

Aliases: `@derive_rules` (same behavior) — both work, `@derives` is canonical. If both `@derives` (decorator) and `derives:` (inline option) are present on the same field, the inline option wins.

## JSON encoding

Set `json: true` on the section to auto-derive a JSON encoder. Uses `Jason.Encoder` if `:jason` is in your deps; falls back to the built-in `JSON.Encoder` on Elixir 1.18+; no-op if neither is available.

```elixir
defmodule Order do
  use GuardedStruct

  guardedstruct json: true do
    field :id, String.t(), enforce: true
    field :total, integer(), enforce: true
  end
end

{:ok, order} = Order.builder(%{id: "abc", total: 99})
Jason.encode!(order)
# => ~s({"id":"abc","total":99})

# On Elixir 1.18+ without Jason in deps:
# JSON.encode!(order)
```

The encoder cascades to sub_field submodules — nested structs serialize recursively.

## Introspection — `GuardedStruct.Info`

Every generated module gets a rich set of introspection helpers via `GuardedStruct.Info`.

```elixir
defmodule User do
  use GuardedStruct

  guardedstruct enforce: true do
    field :name, String.t()
    field :email, String.t(), derives: "validate(email_r)"
    virtual_field :password_confirm, String.t()
    sub_field :address, struct() do
      field :city, String.t()
    end
  end
end

# One call — full dump
GuardedStruct.Info.describe(User)
# => %{
#      module: User,
#      keys: [:name, :email, :address],
#      enforce_keys: [:name, :email, :address],
#      fields: [
#        %{name: :name, kind: :field, type: "String.t()", enforce?: true, ...},
#        %{name: :email, kind: :field, derive: "validate(email_r)", ...},
#        %{name: :address, kind: :sub_field, sub_module: User.Address, ...},
#        %{name: :password_confirm, kind: :virtual_field, enforce?: false, ...}
#      ],
#      options: %{enforce: true, json: false, ...}
#    }

# Field-level lookups
GuardedStruct.Info.field_kind(User, :email)              #=> :field
GuardedStruct.Info.enforce?(User, :email)                #=> true
GuardedStruct.Info.virtual?(User, :password_confirm)     #=> true
GuardedStruct.Info.field_derives(User, :email)           #=> "validate(email_r)"
GuardedStruct.Info.sub_module(User, :address)            #=> User.Address

# Collections by kind
GuardedStruct.Info.sub_fields(User)                      #=> [:address]
GuardedStruct.Info.virtual_fields(User)                  #=> [:password_confirm]
```

## Audit-log diffing — `GuardedStruct.Diff`

```elixir
{:ok, v1} = User.builder(%{name: "Alice", email: "alice@x.com"})
{:ok, v2} = User.builder(%{name: "Alicia", email: "alice@x.com"})

GuardedStruct.Diff.diff(v1, v2)
# => %{name: {:changed, "Alice", "Alicia"}}

GuardedStruct.Diff.apply(v1, %{name: {:changed, "Alice", "Alicia"}})
# => %User{name: "Alicia", email: "alice@x.com", ...}

GuardedStruct.Diff.equal?(v1, v2)
# => false
```

Diffs are nested-struct-aware — only changed fields appear in the result map.

## `example/0` helper

Every generated module has an `example/0` function that returns a struct populated with declared defaults (and type-based placeholders for fields without defaults). Useful for REPL inspection, docs, fixture generation.

```elixir
defmodule Order do
  use GuardedStruct
  guardedstruct do
    field :id, String.t(), default: "order-1"
    field :total, integer(), default: 0
    field :currency, String.t(), default: "USD"
  end
end

Order.example()
# => %Order{id: "order-1", total: 0, currency: "USD"}
```

## Telemetry

Every top-level `builder/1` call emits three events:

| Event | Payload | Metadata |
|---|---|---|
| `[:guarded_struct, :builder, :start]` | `%{system_time}` | `%{module}` |
| `[:guarded_struct, :builder, :stop]` | `%{duration}` | `%{module, result: :ok | :error, error_count}` |
| `[:guarded_struct, :builder, :exception]` | `%{duration}` | `%{module, kind, reason, stacktrace}` |

Wire a handler in your application startup:

```elixir
:telemetry.attach(
  "log-builds",
  [:guarded_struct, :builder, :stop],
  fn _e, %{duration: d}, %{module: m, result: r}, _ ->
    Logger.info("#{inspect(m)} #{r} in #{System.convert_time_unit(d, :native, :microsecond)}µs")
  end,
  nil
)
```

Only top-level builds emit — nested sub_field builds inherit; you see exactly one event per public `builder/1` call.

## Standalone validation

`GuardedStruct.Validate` exposes the schema without going through `builder/1`. Three tiers:

```elixir
# Tier 1 — ad-hoc op-string against a value, no module needed
GuardedStruct.Validate.run("validate(string, max_len=80, email_r)", "alice@example.com")
# => {:ok, "alice@example.com"}

# Tier 2 — single named field of a module
GuardedStruct.Validate.field(Signup, :email, "alice@x.com")
# => {:ok, "alice@x.com"}

# Tier 2 — with cross-field deps via context
GuardedStruct.Validate.field(MyStruct, :owner_id, "u-123",
  context: %{user_id: "u-123"}
)

# Tier 2 — isolated mode skips on:/domain: deps entirely
GuardedStruct.Validate.field(MyStruct, :owner_id, "u-123", mode: :isolated)

# Tier 3 — partial subset of fields (form-as-you-type, PATCH endpoints)
GuardedStruct.Validate.partial(Signup, %{email: "alice@x.com", password: "longpassword"})
# => {:ok, %{email: "alice@x.com", password: "longpassword"}}
#    No enforce_keys check — missing fields silently skipped.
```

## Erlang Records

```elixir
require Record
Record.defrecord(:user_record, name: nil, age: nil)

defmodule WithRecord do
  use GuardedStruct
  guardedstruct do
    field(:user, :tuple, derive: "validate(record=user_record)")
  end
end

rec = user_record(name: "Alice", age: 30)
WithRecord.builder(%{user: rec})
# => {:ok, %WithRecord{user: {:user_record, "Alice", 30}}}
```

The `record=tag` form checks that the input is a tagged tuple with the given tag. The bare `validate(record)` accepts any tagged tuple.

## Custom derive ops

Beyond the 50+ built-in validators and 11 sanitizers, you can register your own via a small Spark-native DSL. Declarations live inside a `derives do ... end` block:

```elixir
defmodule MyApp.Derives do
  use GuardedStruct.Derive.Extension

  derives do
    validator :slug, fn input ->
      is_binary(input) and Regex.match?(~r/^[a-z0-9-]+$/, input)
    end

    sanitizer :slugify, fn input when is_binary(input) ->
      input
      |> String.downcase()
      |> String.replace(~r/[^a-z0-9-]+/u, "-")
    end
  end
end

# Register globally — config/config.exs
# config :guarded_struct, derive_extensions: [MyApp.Derives]

# OR register per-module — overrides global, with `:config` sentinel for merge
defmodule Post do
  use GuardedStruct, derive_extensions: [MyApp.Derives]

  guardedstruct do
    field :slug, String.t(), derives: "sanitize(slugify) validate(slug)"
  end
end
```

Per-module resolution rules:
- `[A, B]` — these only; global is ignored
- `[:config, A]` — global ++ [A] (global wins on op-name collisions)
- `[A, :config]` — [A] ++ global (A wins on collisions)
- `[A, :config, B]` — [A] ++ global ++ [B]

A compile-time warning fires if a custom op-name shadows a built-in registered in the internal Derive registry (the custom would be dead code since built-in clauses match first).

## Splode errors

`builder/1` returns the legacy tuple shape `{:error, [%{field, action, message}]}` by default. Wrap with Splode for `traverse_errors/2`, `to_class/1`, JSON serialisation:

```elixir
case Person.builder(input) do
  {:ok, _} = ok ->
    ok

  {:error, errs} ->
    {:error, GuardedStruct.Errors.from_tuple(errs)}
end
```

## Ash integration

Use the same DSL inside an `Ash.Resource`:

```elixir
defmodule MyApp.Resources.User do
  use Ash.Resource, extensions: [GuardedStruct.AshResource]

  guardedstruct do
    field(:name, :string, enforce: true, derives: "validate(string, max_len=80)")
    field(:email, :string, enforce: true, derives: "validate(email_r)")
  end

  changes do
    change GuardedStruct.AshResource.Change
  end

  # ... your normal Ash actions, attributes, policies, etc.
end

MyApp.Resources.User.__guarded_change__(%{name: "Alice", email: "alice@x.com"})
# => {:ok, %{name: "Alice", email: "alice@x.com"}}
```

The pipeline lives under the `__guarded_*__` namespace so it doesn't clash with Ash's own callbacks. The `Change` module bridges `__guarded_change__/1` into the changeset pipeline — runs sanitize + validate on every `:create` and `:update`.

### Auto-wire (Option B)

Set `auto_wire true` and skip the `changes do ... end` block — a Spark transformer injects the change for you via `Ash.Resource.Builder.add_change/3`:

```elixir
defmodule MyApp.Resources.User do
  use Ash.Resource, extensions: [GuardedStruct.AshResource]

  guardedstruct do
    auto_wire true
    field :email, :string, derives: "sanitize(trim, downcase) validate(email_r)"
  end

  # No `changes do ... end` block needed.
end
```

### Bulk operations

The bridge implements `batch_change/3`, so `Ash.bulk_create/3` and `Ash.bulk_update/3` work end-to-end — both `strategy: :atomic` (default) and `strategy: :stream` produce identical sanitized results.

### Atomic mode

`GuardedStruct.AshResource.Change` is atomic-safe by default. No flag to set, no `require_atomic? false` on update / destroy actions.

```elixir
defmodule MyApp.Resources.User do
  use Ash.Resource, extensions: [GuardedStruct.AshResource]

  guardedstruct do
    auto_wire true

    field :email,     :string,  derives: "sanitize(trim, downcase) validate(email_r, max_len=320)"
    field :username,  :string,  derives: "validate(string, min_len=3, max_len=20)"
    field :age,       :integer, derives: "validate(integer, min_len=0, max_len=150)"
    field :role,      :string,  derives: "validate(enum=String[admin::user::guest])"
    field :tenant_id, :string,  derives: "validate(uuid)"
  end

  actions do
    defaults [:read, :destroy]
    create :create, accept: [:email, :username, :age, :role, :tenant_id]

    update :update do
      accept [:email, :username, :age, :role]
    end
  end

  attributes do
    uuid_primary_key :id
    attribute :email,     :string, allow_nil?: false, public?: true
    attribute :username,  :string, allow_nil?: false, public?: true
    attribute :age,       :integer, public?: true
    attribute :role,      :string,  public?: true
    attribute :tenant_id, :string,  public?: true
  end
end
```

**How `Change.atomic/3` works.** It reads `changeset.attributes` and `changeset.atomics`, detects whether any atomic value is an `Ash.Expr`, and:

* if every value is a plain literal → runs the full pipeline (sanitize → validate → derive → `auto:` → main_validator) in Elixir and returns `{:atomic, sanitized_map}` for Ash to substitute into the single-statement UPDATE,
* if any value is an `Ash.Expr` (e.g. `Ash.Changeset.atomic_update(record, :counter, expr(counter + 1))`) → returns `{:not_atomic, reason}` and Ash falls back to the imperative path.

Sanitize ops (`trim`, `downcase`, `strip_tags`, `slugify`, …) all work — they run in Elixir before the atomic SQL fires. So do `auto:` MFAs, custom `Derive.Extension` ops, `main_validator/1`, and cross-field options like `on:` / `from:` / `domain:`.
