Skip to content

Requirement Validators

Xellu edited this page Jun 14, 2026 · 3 revisions

Validators define rules for incoming data. They are used in HTTP request requirements and shell command arguments to validate and reject data before it reaches your handler code.

from napi.Require import (
    AnyOf, AnyTypeOf, ExactMatch, RegExMatch, File, ListOf
)
# OR napi.http.Require
# Source: nautica.models.Requirements

Built-in Validators

AnyOf(*options)

Value must be one of the provided options:

AnyOf("list", "dict")         # accepts "list" or "dict"
AnyOf("admin", "user", "mod") # accepts any of the three roles

AnyTypeOf(*types)

Value must match any of the provided types:

AnyTypeOf(str, int)   # accepts strings or integers
AnyTypeOf(str, None)  # accepts strings or None

ExactMatch(match)

Value must equal the provided value exactly:

ExactMatch("v3")      # accepts only "v3"
ExactMatch(42)        # accepts only 42

RegExMatch(pattern)

Value must match the provided regex pattern:

RegExMatch(r"^[a-zA-Z0-9_]{3,20}$")  # alphanumeric, 3-20 chars
RegExMatch(r"^\d{4}-\d{2}-\d{2}$")   # date format YYYY-MM-DD

File(max_size, mime)

File upload validation, only usable in the files field of @HTTP.Require():

File(max_size=File.MB(2), mime=["image/png", "image/jpeg"])
Parameter Type Description
max_size int Maximum file size in bytes
mime list[str] Allowed MIME types

Size helpers:

File.KB(128)  # 128 kilobytes
File.MB(2)    # 2 megabytes
File.GB(1)    # 1 gigabyte

ListOf(obj, min_length, max_length)

All items in a list has to match the type of object.

ListOf(str) # All items have to be a string
ListOf(int, max_length = 10) # Only integers, 10 items max
ListOf({"name": str, "age": int}, min_length = 5, max_length = 10) # Only dicts with 'name:str' and 'age:int'; 5-10 items
ListOf(AnyTypeOf(str, int)) # This also works

Using Validators

In HTTP Routes

Pass validators as values in @HTTP.Require() instead of plain types:

from napi.http import HTTP, Context, Reply, Require

@HTTP.GET()
@HTTP.Require(
    query = {"format": Require.AnyOf("list", "dict")},
    body = {"username": Require.RegExMatch(r"^[a-zA-Z0-9_]{3,20}$")},
    files = {"avatar": Require.File(max_size=Require.File.MB(2), mime=["image/png"])}
)
async def example(ctx: Context):
    ...

If validation fails, Nautica automatically returns a 422 Unprocessable Content with details about what was missing or invalid. Meaning your handler is never reached.

In Shell Commands

Pass validators as argument types in CommandRequirements:

from nautica.services.builtins.shell.decorator import RegisterCommand, CommandRequirements
from nautica.models.Requirements import AnyOf

@RegisterCommand(
    "setmode",
    "Sets the service mode",
    args = CommandRequirements(
        args = {"mode": AnyOf("debug", "production")}
    )
)
def setmode(mode: str):
    return f"Mode set to {mode}"
> setmode debug       # valid
> setmode invalid     # Argument <mode> does not match anyOf(debug, production)

Custom Validators

You can define your own validators by extending Requirement:

from nautica.models.Requirements import Requirement

class MyValidator(Requirement):
    def isValid(self, content) -> bool: # The validation logic
        return content == "hello"
 
    def toComponent(self) -> dict: # For OpenAPI Docs generation
         return {
            "type": "string",
            "enum": ["hello"]
        }

    def __str__(self):
        return "mustBeHello"
Method Required Description
isValid(content) Yes Return True if valid, False otherwise
toComponent() Recommended Return dict, with a valid OpenAPI schema
__str__() Recommended Shown in error messages when validation fails

Custom validators work anywhere a built-in validator would:

@HTTP.Require(query = {"greeting": MyValidator()})

Extending Built-in Validators

You can extend existing validators to customize their behavior. A useful pattern is extending AnyOf with dynamic options, values that are only known at runtime:

from nautica import Services
from nautica.models.Requirements import AnyOf, Requirement

class RunningServices(AnyOf):
    def __init__(self):
        Requirement.__init__(self)  # skip AnyOf's __init__

    @property
    def options(self):
        return [s._getName() for s in Services.instances]

    def __str__(self):
        return "service"

Requirement.__init__() is called directly instead of super().__init__() to skip AnyOf's init, which would require options upfront — since our options are dynamic and only available at runtime.

Using it in a shell command:

@RegisterCommand(
    "restart",
    "Restarts a service",
    args = CommandRequirements(
        args = {"service": RunningServices()}
    )
)
def restart(service: str):
    return f"Restarting {service}..."
> restart HTTPServer   # valid
> restart Unknown      # invalid, not a running service

Or in an HTTP route:

@HTTP.GET()
@HTTP.Require(
    query = {"service": RunningServices()}
)
async def get_service_info(ctx: Context):
    name = ctx.query["service"]
    ...

Reference

Validator Usage Works in
AnyOf(*options) Value must be one of the options HTTP, Shell
AnyTypeOf(*types) Value must match one of the types HTTP, Shell
ExactMatch(match) Value must equal exactly HTTP, Shell
RegExMatch(pattern) Value must match regex HTTP, Shell
File(max_size, mime) File upload validation HTTP only
ListOf(obj, min_length, max_length) Items must match validator or type HTTP, Shell
Custom Requirement Define your own rules HTTP, Shell

Clone this wiki locally