{"id":21569228,"url":"https://github.com/symlex/input-validation","last_synced_at":"2025-04-10T14:06:01.227Z","repository":{"id":57012618,"uuid":"46562140","full_name":"symlex/input-validation","owner":"symlex","description":"A secure whitelist validation layer for PHP ('accept known good')","archived":false,"fork":false,"pushed_at":"2024-07-07T12:05:28.000Z","size":150,"stargazers_count":7,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T12:47:43.701Z","etag":null,"topics":["form","form-validation","php","rest","validation","whitelist-validation"],"latest_commit_sha":null,"homepage":"https://docs.symlex.org","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/symlex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-20T13:00:19.000Z","updated_at":"2024-07-07T12:03:58.000Z","dependencies_parsed_at":"2022-08-21T13:40:47.898Z","dependency_job_id":null,"html_url":"https://github.com/symlex/input-validation","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symlex%2Finput-validation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symlex%2Finput-validation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symlex%2Finput-validation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symlex%2Finput-validation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/symlex","download_url":"https://codeload.github.com/symlex/input-validation/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248231838,"owners_count":21069412,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["form","form-validation","php","rest","validation","whitelist-validation"],"created_at":"2024-11-24T11:07:33.065Z","updated_at":"2025-04-10T14:06:01.203Z","avatar_url":"https://github.com/symlex.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Easy \u0026 Secure Whitelist Validation for PHP\n\n[![Latest Stable Version](https://poser.pugx.org/symlex/input-validation/v/stable.svg)](https://packagist.org/packages/symlex/input-validation)\n[![Total Downloads](https://poser.pugx.org/symlex/input-validation/downloads.svg)](https://packagist.org/packages/symlex/input-validation)\n[![License](https://poser.pugx.org/symlex/input-validation/license.svg)](https://packagist.org/packages/symlex/input-validation)\n[![Test Coverage](https://codecov.io/gh/symlex/input-validation/branch/master/graph/badge.svg)](https://codecov.io/gh/symlex/input-validation)\n[![Build Status](https://travis-ci.org/symlex/input-validation.png?branch=master)](https://travis-ci.org/symlex/input-validation)\n[![Documentation](https://readthedocs.org/projects/symlex-docs/badge/?version=latest\u0026style=flat)](https://docs.symlex.org/en/latest/input-validation)\n\n**This library provides whitelist validation (\"accept known good\") that is perfectly suited for building secure REST services.** It uses programming language independent validation rules (plain array) that can be reused for additional client-side validation (JavaScript) or passed to template rendering engines such as Twig. By design, it is compatible with any framework and input source (HTML, REST, RPC, ...).\n\nA major advantage of this data source agnostic approach is that developers can do bottom-up development using unit tests to find bugs early and work on validation rules without an existing HTML frontend or storage backend. Use case specific input value validation is also more secure than general model validation, which often relies on a blacklist (\"reject known bad\").\n\nBesides basic validation rules such as type or length, more advanced features are supported as well - for example dependent fields, internationalization and multi-page forms. Validated values can be fetched individually, as flat array, by tag or by page.\n\nThe usage is simple: Form classes can inherit their definitions from each other. If needed, validation behavior can be changed using standard object-oriented methodologies. You don't need to hold a PhD in design patterns to understand how it works.\n\n## Client-side, Form and Model Validation ##\n\nThe following visualization highlights the differences between client-side, form (input value) and model validation. Model validation generally operates on trusted data (internal system state) and should be repeatable at any point in time while input validation explicitly operates once on data that comes from untrusted sources (depending on the use case and user privileges). This separation makes it possible to build reusable models, controllers and forms that can be coupled through dependency injection (see REST controller example).\n\nThink of form validation as whitelist validation (\"accept known good\") and model validation as blacklist validation (\"reject known bad\"). Whitelist validation is more secure while blacklist validation prevents your model layer from being overly constrained to very specific use cases.\n\nInvalid model data should always cause an exception to be thrown (otherwise the application can continue running without noticing the mistake) while invalid input values coming from external sources are not unexpected, but rather common (unless you got users that never make mistakes). Validation within a specific model may not be possible at all, if a set of input values must be validated together (because they depend on each other) but individual values are then stored in different models - at least it can create additional dependencies between models that would not be there otherwise up to the point that all models depend on each other. In short: The application may still work as expected, but the code is a mess.\n\nFrom a theoretical standpoint, any complex system has more internal state than it exposes to the outside, thus it is never sufficient to use model validation only - except the model provides two sets of methods: some that are used internally and some that can be exposed to arbitrary input data from any source. Aside from side-effects such as limited user feedback (exception messages) and bloated model code, this approach may easily lead to serious security flaws. Malicious input data is a much higher threat to multi-user Web applications than to classical single-user desktop applications. Simple blacklist model validation may be fully sufficient for desktop applications, which are in full control of the user interface (view layer).\n\nClient-side (JavaScript or HTML) form validation is always just a convenience feature and not reliable. However, with this library you can (at least partly) reuse existing server-side form validation rules to perform client-side validation, since they can be easily converted to JSON (for JavaScript) or be passed to template rendering engines such as Twig or Smarty (for HTML). Reusing model layer validation rules in a similar fashion is at least difficult, if not impossible.\n\nSee also [Where to include business rule validation (OWASP)](https://www.owasp.org/index.php/Data_Validation#Where_to_include_business_rule_validation).\n\n![Differences between client-side, input value (form) and model validation](https://www.lucidchart.com/publicSegments/view/5461f867-ae1c-44a4-b565-6f780a00cf27/image.png)\n\n## REST Request Validation Example ##\n\nThis example shows how to validate user input in a REST controller context. Note, how easy it is to avoid the deeply nested structures you often find in validation code. User model and form are injected as dependencies. \n\n```php\n\u003c?php\n\nclass UserController\n{\n    protected $user;\n    protected $form;\n\n    public function __construct(UserModel $user, UserForm $form)\n    {\n        $this-\u003euser = $user;\n        $this-\u003eform = $form;\n    }\n    \n    // Update User\n    public function putAction(int $id, Request $request): array \n    {\n        // Find entity (throws exception, if not found)\n        $this-\u003euser-\u003efind($id); \n        \n        // Form initialization with current values\n        $this-\u003eform-\u003esetDefinedValues($this-\u003euser-\u003egetValues()); \n        \n        // Set input values\n        $this-\u003eform-\u003esetDefinedWritableValues($request-\u003erequest-\u003eall()); \n        \n        // Validation\n        $this-\u003eform-\u003evalidate(); \n\n        if($this-\u003eform-\u003ehasErrors()) {\n            throw new FormInvalidException($this-\u003eform-\u003egetFirstError());\n        }\n        \n        // Update values in database\n        $this-\u003euser-\u003eupdate($this-\u003eform-\u003egetValues()); \n\n        // Return updated values\n        return $this-\u003euser-\u003egetValues(); \n    }\n    \n    // Return form fields incl current values for User\n    public function optionsAction(int $id): array \n    {\n        // Find entity (throws exception, if not found)\n        $this-\u003euser-\u003efind($id); \n        \n        // Form initialization with current values\n        $this-\u003eform-\u003esetDefinedValues($this-\u003euser-\u003egetValues()); \n        \n        // Returns form as JSON compatible array incl all values\n        return $this-\u003eform-\u003egetAsArray(); \n    }\n}\n```\n\nSee also [Doctrine ActiveRecord - Object-oriented CRUD for Doctrine DBAL](https://github.com/symlex/doctrine-active-record)\n\n## Configuration ##\n\nA detailed overview of field properties can be found below. `$_('label')` is used for optional translation of field captions or other strings - a number of different translation file formats such as YAML are supported for that (see [Symfony Components](http://symfony.com/doc/current/components/translation.html) documentation)\n\n```php\nuse InputValidation\\Form;\n\nclass UserForm extends Form\n{\n    protected function init(array $params = array())\n    {\n        $definition = [\n            'username' =\u003e [\n                'type' =\u003e 'string',\n                'caption' =\u003e $this-\u003e_('username'),\n                'required' =\u003e true,\n                'min' =\u003e 3,\n                'max' =\u003e 15\n            ],\n            'email' =\u003e [\n                'type' =\u003e 'email',\n                'caption' =\u003e $this-\u003e_('email_address'),\n                'required' =\u003e true\n            ],\n            'gender' =\u003e [\n                'type' =\u003e 'string',\n                'caption' =\u003e $this-\u003e_('gender'),\n                'required' =\u003e false,\n                'options' =\u003e [\n                    'm' =\u003e 'Male',\n                    'f' =\u003e 'Female',\n                    'o' =\u003e 'Other'\n                ],\n                'optional' =\u003e true\n            ],\n            'birthday' =\u003e [\n                'type' =\u003e 'date',\n                'caption' =\u003e $this-\u003e_('birthday'),\n                'required' =\u003e false\n            ],\n            'password' =\u003e [\n                'type' =\u003e 'string',\n                'caption' =\u003e $this-\u003e_('password'),\n                'required' =\u003e true,\n                'min' =\u003e 5,\n                'max' =\u003e 30\n            ],\n            'password_again' =\u003e [\n                'type' =\u003e 'string',\n                'caption' =\u003e $this-\u003e_('password_again'),\n                'required' =\u003e true,\n                'matches' =\u003e 'password'\n            ],\n            'continent' =\u003e [\n                'type' =\u003e 'string',\n                'caption' =\u003e $this-\u003e_('region'),\n                'required' =\u003e true,\n                'options' =\u003e [\n                    'north_america' =\u003e 'North America',\n                    'south_america' =\u003e 'South Amertica',\n                    'europe' =\u003e 'Europe,\n                    'asia' =\u003e 'Asia',\n                    'australia' =\u003e 'Australia'\n                ]\n            ]\n        ];\n\n        $this-\u003esetDefinition($definition);\n    }\n}\n```\n\n## Properties ##\n\nProperty               | Description\n---------------------- | ---------------------------------------------------------------------------------------------------\ncaption                | Field title (used for form rendering and in validation messages)\ntype                   | Data type: int, numeric, scalar, list, bool, string, email, ip, url, date, datetime, time and switch\ntype_params            | Optional parameters for data type validation\noptions                | Array of possible values for the field (for select lists or radio button groups)\nmin                    | Minimum value for numbers/dates, length for strings or number of elements for lists\nmax                    | Maximum value for numbers/dates, length for strings or number of elements for lists\nrequired               | Field cannot be empty (if false, setDefinedValues() and setDefinedWritableValues() still throw an exception, if it does not exist at all)\noptional               | setDefinedValues() and setDefinedWritableValues() don't throw an exception, if the field is missing in the input values (usefull for checkboxes or certain JavaScript frameworks, that do not submit any data for empty form elements e.g. AngularJS)\nreadonly               | User is not allowed to change the field (not writable)\nhidden                 | User can not see the field (no impact on the validation)\ndefault                | Default value\nregex                  | Regular expression to match against\nmatches                | Field value must match another form field (e.g. for password or email validation). Property can be prefixed with \"!\" to state that the fields must be different.\ndepends                | Field is required, if the given form field is not empty\ndepends_value          | Field is required, if the field defined in \"depends\" has this value\ndepends_value_empty    | Field is required, if the field defined in \"depends\" is empty\ndepends_first_option   | Field is required, if the field defined in \"depends\" has the first value (see \"options\")\ndepends_last_option    | Field is required, if the field defined in \"depends\" has the last value (see \"options\")\npage                   | Page number for multi-page forms\ntags                   | Optional list of tags (can be used to extract values by tag, see getValuesByTag())\n\n## Create a Form Instance ##\n\nYou can create new form instances manually...\n\n```php\nuse InputValidation\\Form;\nuse InputValidation\\Form\\Validator;\nuse InputValidation\\Form\\Options\\YamlOptions;\nuse Symfony\\Component\\Translation\\Translator;\nuse Symfony\\Component\\Translation\\MessageSelector;\nuse Symfony\\Component\\Translation\\Loader\\YamlFileLoader;\nuse Symfony\\Component\\Translation\\Loader\\ArrayLoader;\n\n$translator = new Translator('en', new MessageSelector);\n$translator-\u003eaddLoader('yaml', new YamlFileLoader);\n$translator-\u003eaddLoader('array', new ArrayLoader);\n\n$validator = new Validator();\n\n$options = new YamlOptions($translator);\n\n$form = new Form($translator, $validator, $options);\n```\n\n... or using the convenient `InputValidation\\Form\\Factory`:\n\n```php\n$formFactory = new InputValidation\\Form\\Factory($translator, $validator, $options);\n$formFactory-\u003esetFactoryNamespace('App\\Form');\n$formFactory-\u003esetFactoryPostfix('Form');\n$formFactory-\u003ecreate('User'); // Returns instance of App\\Form\\UserForm\n```\n\n## API ##\n\n**__construct(Translator $translator, Form\\Validator $validator, Form\\OptionsInterface $options, array $params = array())**\n\nThe constructor requires instances of Symfony\\Component\\Translation\\TranslatorInterface, InputValidation\\Form\\Validator, InputValidation\\Form\\OptionsInterface and an optional set of arbitrary parameters, which are passed to **init(array $params = array())** (protected method for initializing the form).\n\n**setOptions(Form\\OptionsInterface $options)**\n\nSets an optional class instance to automatically fill option lists (see \"options\" form field property). OptionsInterface only requires a method get($listName) that returns an array of options.\n\n**getOptions(): Form\\OptionsInterface**\n\nReturns the options instance \n\n**options(string $listName): array**\n\nReturns a list of options e.g. countries:\n\n```php\n'country' =\u003e array(\n    'type' =\u003e 'string',\n    'caption' =\u003e 'Country',\n    'default' =\u003e 'DE',\n    'options' =\u003e $this-\u003eform-\u003eoptions('countries')\n)\n```\n\n**optionsWithDefault(string $listName, string $defaultLabel = ''): array**\n\nReturns a list of options with default label for no selection\n\n```php\n'country' =\u003e array(\n    'type' =\u003e 'string',\n    'caption' =\u003e 'Country',\n    'required' =\u003e true,\n    'options' =\u003e $this-\u003eform-\u003eoptionsWithDefault('countries')\n)\n```\n     \n**getTranslator(): Translator**\n\nReturns the Translator instance (see __construct)\n\n**setTranslator(Translator $translator)**\n\nSets the Translator instance (see __construct)\n\n**getValidator(): Validator**\n\nReturns the Validator instance (see __construct)\n\n**setValidator(Validator $validator)**\n\nSets the Validator instance (see __construct)\n\n**getLocale()**\n\nReturns the current locale e.g. en, de or fr\n\n**setLocale($locale)**\n\nSets the current locale e.g. en, de or fr\n\n**setDefinition(array $definition)**\n\nSets the form field definition array (see example and form field properties)\n\n**getDefinition($key = null, $propertyName = null): mixed**\n\nReturns the form field definition(s). If $key is null, definitions for all fields are returned. If $propertyName is null and $key is not null, only the definition of the given field key are returned. If both arguments are not null, only the definition of the given form field property is returned (for example, getDefinition('firstname', 'type') might return 'string'). A FormException is thrown otherwise.\n\n**getFieldDefinition(string $name): array**\n\nReturns form field definition as array (wrapper for getDefinition)\n\n**getFieldProperty(string $name, string $property): mixed**\n\nReturns a field property from the form definition (wrapper for getDefinition)\n\n**addDefinition($key, array $definition)**\n\nAdds a single form field definition (see form field properties)\n\n**changeDefinition($key, array $changes)**\n\nChanges a single form field definition (see form field properties)\n\n**setGroups(array $groups)**\n\nSets form field groups (optional feature, if you want to reuse your form definition to reder the form as HTML).\n\nExample:\n```php\n$form-\u003esetGroups(\n    array(\n        'name' =\u003e array('firstname', 'lastname'),\n        'address' =\u003e array('street', 'housenr', 'zip', 'city')\n    )\n);\n```\n\n**getFieldAsArray(string $key): array**\n\nReturns field definition and value as JSON compatible array:\n\n```php\narray (\n  'name' =\u003e 'country',\n  'caption' =\u003e 'Country',\n  'default' =\u003e 'DE',\n  'type' =\u003e 'string',\n  'options' =\u003e array (\n    array (\n      'option' =\u003e 'US',\n      'label' =\u003e 'United States',\n    ),\n    array (\n      'option' =\u003e 'GB',\n      'label' =\u003e 'United Kingdom',\n    ),\n    array (\n      'option' =\u003e 'DE',\n      'label' =\u003e 'Germany',\n    ),\n  ),\n  'value' =\u003e 'DE',\n  'uid' =\u003e 'id58a401f5a54e6',\n),\n```\n\n**getAsArray(): array**\n\nReturns the complete form (definition and all values) as JSON compatible array, which can be used to render the form in templates:\n\n```php\narray (\n  'company' =\u003e array (\n    'name' =\u003e 'company',\n    'caption' =\u003e 'Company',\n    'type' =\u003e 'string',\n    'value' =\u003e 'IBM',\n    'uid' =\u003e 'id58a401f5a54d6',\n  ),\n  'country' =\u003e array (\n    'name' =\u003e 'country',\n    'caption' =\u003e 'Country',\n    'default' =\u003e 'DE',\n    'type' =\u003e 'string',\n    'options' =\u003e array (\n      array (\n        'option' =\u003e 'US',\n        'label' =\u003e 'United States',\n      ),\n      array (\n        'option' =\u003e 'GB',\n        'label' =\u003e 'United Kingdom',\n      ),\n      array (\n        'option' =\u003e 'DE',\n        'label' =\u003e 'Germany',\n      ),\n    ),\n    'value' =\u003e 'DE',\n    'uid' =\u003e 'id58a401f5a54e6',\n  ),\n),\n```\n\n**getAsGroupedArray(): array**\n\nReturns grouped form field definitions and values (you must use setGroups() first):\n\n```php\narray(\n  'person' =\u003e array (\n    'group_name' =\u003e 'person',\n    'group_caption' =\u003e 'Person',\n    'fields' =\u003e array(\n      'person' =\u003e array (\n        'name' =\u003e 'firstname',\n        'caption' =\u003e 'First Name',\n        'type' =\u003e 'string',\n        'readonly' =\u003e true,\n        'value' =\u003e NULL,\n        'uid' =\u003e 'id58a401f5a5267',\n      ),\n      'lastname' =\u003e array (\n        'name' =\u003e 'lastname',\n        'caption' =\u003e 'Last Name',\n        'type' =\u003e 'string',\n        'readonly' =\u003e false,\n        'value' =\u003e 'Mander',\n        'uid' =\u003e 'id58a401f5a5279',\n      ),\n    ),\n  ),\n  'location' =\u003e array (\n    'group_name' =\u003e 'location',\n    'group_caption' =\u003e 'Location',\n    'fields' =\u003e array (\n      'company' =\u003e array (\n        'name' =\u003e 'company',\n        'caption' =\u003e 'Company',\n        'type' =\u003e 'string',\n        'value' =\u003e 'IBM',\n        'uid' =\u003e 'id58a401f5a54d6',\n      ),\n      'country' =\u003e array (\n        'name' =\u003e 'country',\n        'caption' =\u003e 'Country',\n        'type' =\u003e 'string',\n        'default' =\u003e 'DE',\n        'options' =\u003e array (\n          array (\n            'option' =\u003e 'US',\n            'label' =\u003e 'United States',\n          ),\n          array (\n            'option' =\u003e 'GB',\n            'label' =\u003e 'United Kingdom',\n          ),\n          array (\n            'option' =\u003e 'DE',\n            'label' =\u003e 'Germany',\n          ),\n        ),\n        'value' =\u003e 'DE',\n        'uid' =\u003e 'id58a401f5a54e6',\n      ),\n    ),\n  ),\n),\n```\n\n**setAllValues(array $values)**\n\nSets all form values (does not check, if the fields exist or if the fields are writable by the user). Throws an exception, if you try to set values for undefined fields.\n\n**setDefinedValues(array $values)**\n\nIterates through the form definition and sets the values for fields, that are present in the form definition.\n\n**setWritableValues(array $values)**\n\nIterates through the passed value array and sets the values for fields, that are writable by the user.\n\n**setDefinedWritableValues(array $values)**\n\nSets the values for fields, that are present in the form definition and that are writable by the user (recommended method for most use cases).\n\n**setWritableValuesOnPage(array $values, $page)**\n\nSets the values for fields on the given page, that are present in the form definition and that are writable by the user (recommended method for most use cases, if the form contains multiple pages).\n\n**getValuesByPage()**\n\nReturns the form values for all elements grouped by page.\n\n**getValuesByTag($tag)**\n\nReturns the form values for all elements by tag (see \"tags\" form field property)\n\n**getValues()**\n\nReturns all form field values\n\n**getWritableValues()**\n\nReturns all user writable form field values\n\n**translate($token, array $params = array())**\n\nUses the Translator adapter to translate the given string/token (accepts optional parameters for the translation string).\n\n**_($token, array $params = array())**\n\nAlias for translate()\n\n**addError($key, $token, array $params = array())**\n\nAdds a validation error (uses translate() for the error message internally)\n\n**validate()**\n\nValidates all form field values. You can use getErrors(), getErrorsByPage(), isValid() and hasErrors() to get the validation results.\n\n**hasErrors(): bool**\n\nReturns true, if the form has errors\n\n**isValid(): bool**\n\neturns true, if the form is valid (has no errors)\n\n**getErrors(): array**\n\nReturns all errors and throws an exception, if the validation was not performed yet (you must call validate() before calling getErrors()).\n\n**getFirstError(): string**\n\nReturns the first error as string\n\n**getErrorsAsText(): string**\n\nReturns all errors as indented text (for command line applications)\n\n**getErrorsByPage(): array**\n\nReturns all errors grouped by page and throws an exception, if the validation was not performed yet (you must call validate() before calling getErrorsByPage()).\n\n**clearErrors()**\n\nResets the validation and clears all errors\n\n**getHash(): string**\n\nReturns hash that uniquely identifies the form (for caching comprehensive forms)\n\n## Composer ##\n\nTo use this library in your project, simply run `composer require symlex/input-validation` or\nadd \"symlex/input-validation\" to your [composer.json](https://getcomposer.org/doc/04-schema.md) file and run `composer update`:\n\n```json\n{\n    \"require\": {\n        \"php\": \"\u003e=7.1\",\n        \"symlex/input-validation\": \"^4.0\"\n    }\n}\n```\n\n## About ##\n\nInputValidation is maintained by [Michael Mayer](https://blog.liquidbytes.net/about/).\nFeel free to send an e-mail to [hello@symlex.org](mailto:hello@symlex.org) if you have any questions, \nneed [commercial support](https://blog.liquidbytes.net/contact/) or just want to say hello. \nWe welcome contributions of any kind. If you have a bug or an idea, read our \n[guide](CONTRIBUTING.md) before opening an issue.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymlex%2Finput-validation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsymlex%2Finput-validation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymlex%2Finput-validation/lists"}