{"id":47337825,"url":"https://github.com/stevenosse/form_shield","last_synced_at":"2026-03-17T22:08:23.158Z","repository":{"id":285992730,"uuid":"960023585","full_name":"stevenosse/form_shield","owner":"stevenosse","description":"A declarative, rule-based form validation library for Flutter apps, offering customizable rules and messages, seamless integration with Flutter forms, type safety, and chainable validation.","archived":false,"fork":false,"pushed_at":"2025-12-09T10:53:32.000Z","size":241,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-02T13:58:38.522Z","etag":null,"topics":["fluter","form-validator"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/form_shield","language":"Dart","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/stevenosse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-04-03T18:26:41.000Z","updated_at":"2025-12-09T10:53:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"9249f9b2-625b-4afb-9685-ad9c862b2590","html_url":"https://github.com/stevenosse/form_shield","commit_stats":null,"previous_names":["stevenosse/form_shield"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/stevenosse/form_shield","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevenosse%2Fform_shield","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevenosse%2Fform_shield/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevenosse%2Fform_shield/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevenosse%2Fform_shield/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stevenosse","download_url":"https://codeload.github.com/stevenosse/form_shield/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevenosse%2Fform_shield/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30633240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-17T17:32:55.572Z","status":"ssl_error","status_checked_at":"2026-03-17T17:32:38.732Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["fluter","form-validator"],"created_at":"2026-03-17T22:08:22.358Z","updated_at":"2026-03-17T22:08:23.102Z","avatar_url":"https://github.com/stevenosse.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Form Shield\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/stevenosse/form_shield/refs/heads/main/logo.svg\" width=\"200\" alt=\"Form Shield Logo\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://pub.dev/packages/form_shield\"\u003e\u003cimg src=\"https://img.shields.io/pub/v/form_shield.svg\" alt=\"pub version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"license\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/stevenosse/form_shield\"\u003e\u003cimg src=\"https://codecov.io/gh/stevenosse/form_shield/branch/main/graph/badge.svg\" alt=\"codecov\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\nA declarative, rule-based form validation library for Flutter apps, offering customizable rules and messages, seamless integration with Flutter forms, type safety, and chainable validation.\n\nIt provides a simple yet powerful way to define and apply validation logic to your form fields.\n\n## Features\n\n✨ **Declarative validation:** Define validation rules in a clear, readable way. \u003cbr /\u003e\n🎨 **Customizable:** Easily tailor rules and error messages to your needs.\u003cbr /\u003e\n🤝 **Flutter integration:** Works seamlessly with Flutter's `Form` and `TextFormField` widgets. \u003cbr /\u003e\n🔒 **Type-safe:** Leverages Dart's type system for safer validation logic. \u003cbr /\u003e\n🔗 **Chainable rules:** Combine multiple validation rules effortlessly. \u003cbr /\u003e\n📚 **Comprehensive built-in rules:** Includes common validation scenarios out-of-the-box (required, email, password, length, numeric range, phone, etc.). \u003cbr /\u003e\n🛠️ **Extensible:** Create your own custom validation rules by extending the base class. \u003cbr /\u003e\n🔄 **Separate sync/async APIs:** Clearly separated APIs for synchronous and asynchronous validation needs. \u003cbr /\u003e\n🧩 **Composable validators:** Combine multiple validators with the `CompositeValidator`. \u003cbr /\u003e\n\n## Table of Contents\n- [Installation](#getting-started)\n  - [Installation](#installation)\n- [Usage](#usage)\n  - [Basic usage](#basic-usage)\n  - [Customizing error messages](#customizing-error-messages)\n  - [Using multiple validation rules](#using-multiple-validation-rules)\n  - [Custom validation rules](#custom-validation-rules)\n  - [Dynamic custom validation](#dynamic-custom-validation)\n  - [Validating numbers](#validating-numbers)\n  - [Phone number validation](#phone-number-validation)\n  - [Password validation with options](#password-validation-with-options)\n  - [Password confirmation](#password-confirmation)\n- [Localization (i18n)](#localization-i18n)\n- [Available validation rules](#available-validation-rules)\n- [Creating your own validation rules](#creating-your-own-validation-rules)\n- [Validation vrchitecture](#validation-architecture)\n  - [Synchronous validation](#synchronous-validation)\n  - [Asynchronous validation](#asynchronous-validation)\n  - [Compose validators](#composite-validation)\n- [Asynchronous validation rules](#asynchronous-validation-rules)\n  - [Username availability checker](#example-username-availability-checker)\n  - [Using async validation in forms](#using-async-validation-in-forms)\n  - [Debouncing async validation](#debouncing-async-validation)\n  - [Manually triggering async validation](#manually-triggering-async-validation)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Getting started\n\n### Installation\n\nAdd `form_shield` to your `pubspec.yaml` dependencies:\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  form_shield: ^0.5.0\n```\n\nThen, run `flutter pub get`.\n\n## Usage\n\n### Basic usage\n\nImport the package:\n```dart\nimport 'package:form_shield/form_shield.dart';\n```\n\nWrap your `TextFormField` (or other form fields) within a `Form` widget and assign a `GlobalKey\u003cFormState\u003e`. Use the shorthand `validator([...])` helper to attach rules to the `validator` property of your fields:\n\\- Note: The direct constructor `Validator\u003cT\u003e([...])` is deprecated. Please use `validator([...])`. The type-specific static factories (`Validator.forString/forNumber/forBoolean/forDate`) have been removed.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:form_shield/form_shield.dart';\n\nclass MyForm extends StatelessWidget {\n  final _formKey = GlobalKey\u003cFormState\u003e();\n  \n  @override\n  Widget build(BuildContext context) {\n    return Form(\n      key: _formKey,\n      child: Column(\n        children: [\n          TextFormField(\n            decoration: InputDecoration(labelText: 'Email'),\n            validator: validator([\n              RequiredRule(),\n              EmailRule(),\n            ]),\n          ),\n          TextFormField(\n            decoration: InputDecoration(labelText: 'Password'),\n            obscureText: true,\n            validator: validator([\n              RequiredRule(),\n              PasswordRule(),\n            ]),\n          ),\n          TextFormField(\n            decoration: InputDecoration(labelText: 'Date (YYYY-MM-DD)'),\n            validator: validator([\n              // Validates a date string using ISO format\n              DateRule(minDate: DateTime(1900, 1, 1)),\n            ]),\n          ),\n          ElevatedButton(\n            onPressed: () {\n              if (_formKey.currentState!.validate()) {\n                // Form is valid, proceed\n              }\n            },\n            child: Text('Submit'),\n          ),\n        ],\n      ),\n    );\n  }\n}\n```\n\n### Customizing error messages\n\n```dart\nvalidator\u003cString\u003e([\n  RequiredRule(errorMessage: 'Please enter your email address'),\n  EmailRule(errorMessage: 'Please enter a valid email address'),\n])\n```\n\n### Using multiple validation rules\n\n```dart\nvalidator\u003cString\u003e([\n  RequiredRule(),\n  MinLengthRule(8, errorMessage: 'Username must be at least 8 characters'),\n  MaxLengthRule(20, errorMessage: 'Username cannot exceed 20 characters'),\n])\n```\n\n### Validating numbers\n\n```dart\nvalidator\u003cnum\u003e([\n  MinValueRule(18, errorMessage: 'You must be at least 18 years old'),\n  MaxValueRule(120, errorMessage: 'Please enter a valid age'),\n])\n```\n\n### Phone number validation\n\n```dart\n// General phone validation\nvalidator\u003cString\u003e([\n  RequiredRule(),\n  PhoneRule(),\n])\n\n// Country-specific phone validation\nvalidator\u003cString\u003e([\n  RequiredRule(),\n  CountryPhoneRule(countryCode: 'CM'),\n])\n```\n\n### Password validation with options\n\n```dart\nvalidator\u003cString\u003e([\n  RequiredRule(),\n  PasswordRule(\n    options: PasswordOptions(\n      minLength: 10,\n      requireUppercase: true,\n      requireLowercase: true,\n      requireDigit: true,\n      requireSpecialChar: true,\n    ),\n    errorMessage: 'Password does not meet security requirements',\n  ),\n])\n```\n\n### Password validation with custom error messages\n\n```dart\nvalidator\u003cString\u003e([\n  RequiredRule(),\n  PasswordRule(\n    options: PasswordOptions(\n      minLength: 12,\n      requireUppercase: true,\n      requireLowercase: true,\n      requireDigit: true,\n      requireSpecialChar: true,\n      // Custom error messages for each validation type\n      minLengthMessage: 'Password must be at least 12 characters long',\n      uppercaseMessage: 'Password must include an uppercase letter (A-Z)',\n      lowercaseMessage: 'Password must include a lowercase letter (a-z)',\n      digitMessage: 'Password must include at least one number (0-9)',\n      specialCharMessage: 'Password must include a special character (!@#$%^\u0026*)',\n    ),\n  ),\n])\n```\n\n### Password confirmation\n\n```dart\nfinal passwordController = TextEditingController();\n\n// Password field\nTextFormField(\n  controller: passwordController,\n  validator: validator\u003cString\u003e([\n    RequiredRule(),\n    PasswordRule(),\n  ]),\n)\n\n// Confirm password field\nTextFormField(\n  validator: validator\u003cString\u003e([\n    RequiredRule(),\n    PasswordMatchRule(\n      passwordGetter: () =\u003e passwordController.text,\n      errorMessage: 'Passwords do not match',\n    ),\n  ]),\n)\n```\n\n## Localization (i18n)\n\nForm Shield includes built-in i18n support with English and French translations. Error messages are automatically localized based on your app's locale.\n\n### Setup\n\nAdd the localization delegate to your `MaterialApp`:\n\n```dart\nimport 'package:form_shield/form_shield.dart';\nimport 'package:flutter_localizations/flutter_localizations.dart';\n\nMaterialApp(\n  localizationsDelegates: [\n    FormShieldI18n.delegate,  // Form Shield's localization delegate\n    GlobalMaterialLocalizations.delegate,\n    GlobalWidgetsLocalizations.delegate,\n    GlobalCupertinoLocalizations.delegate,\n  ],\n  supportedLocales: FormShieldI18n.delegate.supportedLocales, // [en, fr]\n  // ...\n)\n```\n\n### How It Works\n\nOnce the delegate is configured, all validation error messages will automatically use the appropriate language based on the user's locale. No changes to your validation code are required.\n\n```dart\n// Error messages are automatically localized\nTextFormField(\n  validator: validator([\n    RequiredRule(),  // Shows \"This field is required\" (en) or \"Ce champ est requis\" (fr)\n    EmailRule(),     // Shows \"Please enter a valid email address\" (en) or \"Veuillez entrer une adresse e-mail valide\" (fr)\n  ]),\n)\n```\n\n### Custom Error Messages\n\nYou can still override with custom messages when needed:\n\n```dart\nRequiredRule(errorMessage: 'My custom message')  // Always uses this message\n```\n\n### Fallback Behavior\n\nIf the localization delegate is not configured, Form Shield falls back to English messages. This ensures backward compatibility.\n\n## Available validation rules\n\n- `RequiredRule` - Validates that a value is not null or empty\n- `EmailRule` - Validates that a string is a valid email address\n- `PasswordRule` - Validates that a string meets password requirements\n- `PasswordMatchRule` - Validates that a string matches another string\n- `LengthRule` - Validates that a string's length is within specified bounds\n- `MinLengthRule` - Validates that a string's length is at least a specified minimum\n- `MaxLengthRule` - Validates that a string's length is at most a specified maximum\n- `ValueRule` - Validates that a numeric value is within specified bounds\n- `MinValueRule` - Validates that a numeric value is at least a specified minimum\n- `MaxValueRule` - Validates that a numeric value is at most a specified maximum\n- `PhoneRule` - Validates that a string is a valid phone number\n- `CountryPhoneRule` - Validates that a string is a valid phone number for a specific country\n- `UrlRule` - Validates that a string is a valid URL\n- `IPAddressRule` - Validates that a string is a valid IPv4 or IPv6 address\n- `CreditCardRule` - Validates that a string is a valid credit card number\n- `DateRule` - Validates that a date string is within specified bounds\n- `DateRangeRule` - Validates that an end date is after a start date (string inputs)\n\n## Creating your own validation rules\n\nYou can create your own validation rules by extending the `ValidationRule` class:\n\n```dart\nclass NoSpacesRule extends ValidationRule\u003cString\u003e {\n  const NoSpacesRule({\n    String errorMessage = 'No spaces allowed',\n  }) : super(errorMessage: errorMessage);\n\n  @override\n  ValidationResult validate(String? value) {\n    if (value == null || value.isEmpty) {\n      return const ValidationResult.success();\n    }\n\n    if (value.contains(' ')) {\n      return ValidationResult.error(errorMessage);\n    }\n\n    return const ValidationResult.success();\n  }\n}\n```\n\nThen use it like any other validation rule:\n\n```dart\nvalidator\u003cString\u003e([\n  RequiredRule(),\n  NoSpacesRule(),\n])\n```\n\n## Validation architecture\n\nForm Shield offers three distinct validator classes to handle different validation scenarios:\n\n### Synchronous validation\n\n`Validator` handles synchronous validation with immediate results:\n\n```dart\n// Create a validator with synchronous rules\nfinal validator = validator\u003cString\u003e([\n  RequiredRule(),\n  EmailRule(),\n]);\n\n// Use it directly as a FormField validator\nTextFormField(\n  validator: validator,\n)\n```\n\n### Asynchronous validation\n\n`AsyncValidator` is specifically for asynchronous validation needs:\n\n```dart\n// Create an async validator with async rules\nfinal asyncValidator = asyncValidator\u003cString\u003e([\n  UsernameAvailabilityRule(\n    checkAvailability: (username) async {\n      // API call or database check\n      return await userRepository.isUsernameAvailable(username);\n    },\n  ),\n], debounceDuration: Duration(milliseconds: 500));\n\n// Don't forget to dispose\n@override\nvoid dispose() {\n  asyncValidator.dispose();\n  super.dispose();\n}\n```\n\n### Composite validation\n\n`CompositeValidator` combines both synchronous and asynchronous validators:\n\n```dart\n// Create sync and async validators\nfinal syncValidator = validator\u003cString\u003e([\n  RequiredRule(),\n  MinLengthRule(3),\n  MaxLengthRule(20),\n]);\n\nfinal asyncValidator = asyncValidator\u003cString\u003e([\n  UsernameAvailabilityRule(\n    checkAvailability: _checkUsernameAvailability,\n  ),\n]);\n\n// Compose them together\nfinal compositeValidator = compositeValidator\u003cString\u003e(\n  [syncValidator],\n  [asyncValidator],\n);\n\n// Use in your form\nTextFormField(\n  validator: compositeValidator,\n  // ...\n)\n\n// Clean up resources\n@override\nvoid dispose() {\n  compositeValidator.dispose();\n  super.dispose();\n}\n```\n\n## Asynchronous validation rules\n\nForm Shield supports asynchronous validation for scenarios where validation requires network requests or other async operations (like checking username availability or email uniqueness).\n\nYou can create async validation rules by extending the specialized `AsyncValidationRule` class:\n\n#### Example: Username availability checker\n\n```dart\nclass UsernameAvailabilityRule extends AsyncValidationRule\u003cString\u003e {\n  final Future\u003cbool\u003e Function(String username) _checkAvailability;\n\n  const UsernameAvailabilityRule({\n    required Future\u003cbool\u003e Function(String username) checkAvailability,\n    super.errorMessage = 'This username is already taken',\n  }) : _checkAvailability = checkAvailability;\n\n  @override\n  ValidationResult validate(String? value) {\n    // Basic sync validation for null/empty check\n    if (value == null || value.isEmpty) {\n      return ValidationResult.error('Username cannot be empty');\n    }\n    return const ValidationResult.success();\n  }\n\n  @override\n  Future\u003cValidationResult\u003e validateAsync(String? value) async {\n    // Run sync validation first\n    final syncResult = validate(value);\n    if (!syncResult.isValid) {\n      return syncResult;\n    }\n\n    try {\n      // Perform the async validation\n      final isAvailable = await _checkAvailability(value!);\n      if (isAvailable) {\n        return const ValidationResult.success();\n      } else {\n        return ValidationResult.error(errorMessage);\n      }\n    } catch (e) {\n      return ValidationResult.error('Error checking username availability: $e');\n    }\n  }\n}\n```\n\n#### Using async validation in forms\n\nWhen using async validation, use the `AsyncValidator` or `CompositeValidator` class:\n\n```dart\nclass _MyFormState extends State\u003cMyForm\u003e {\n  final _formKey = GlobalKey\u003cFormState\u003e();\n  final _usernameController = TextEditingController();\n  late final _asyncValidator;\n  late final _compositeValidator;\n\n  @override\n  void initState() {\n    super.initState();\n\n    _asyncValidator = asyncValidator\u003cString\u003e([\n      UsernameAvailabilityRule(\n        checkAvailability: _checkUsernameAvailability,\n      ),\n    ], debounceDuration: Duration(milliseconds: 500));\n    \n    _compositeValidator = compositeValidator\u003cString\u003e(\n      [\n        validator\u003cString\u003e([\n          RequiredRule(),\n          LengthRule(minLength: 3, maxLength: 20),\n        ]),\n      ],\n      [_asyncValidator],\n    );\n  }\n\n  @override\n  void dispose() {\n    _usernameController.dispose();\n    _compositeValidator.dispose(); // This will handle disposing the async validator\n    super.dispose();\n  }\n\n  Future\u003cbool\u003e _checkUsernameAvailability(String username) async {\n    // Simulate API call with delay\n    await Future.delayed(const Duration(seconds: 1));\n    final takenUsernames = ['admin', 'user', 'test'];\n    return !takenUsernames.contains(username.toLowerCase());\n  }\n\n  void _submitForm() {\n    if (_formKey.currentState!.validate() \u0026\u0026\n        !_compositeValidator.isValidating \u0026\u0026\n        _compositeValidator.isValid) {\n      // All validations passed, proceed with form submission\n    }\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Form(\n      key: _formKey,\n      child: Column(\n        children: [\n          TextFormField(\n            controller: _usernameController,\n            decoration: InputDecoration(labelText: 'Username'),\n            validator: _compositeValidator,\n          ),\n          // Show async validation state\n          ListenableBuilder(\n            listenable: _asyncValidator.asyncState,\n            builder: (context, _) {\n              if (_compositeValidator.isValidating) {\n                return Text('Checking username availability...');\n              } else if (!_compositeValidator.isValid \u0026\u0026 _asyncValidator.errorMessage != null) {\n                return Text(\n                  _asyncValidator.errorMessage!,\n                  style: TextStyle(color: Colors.red),\n                );\n              } else if (_compositeValidator.isValid) {\n                return Text(\n                  'Username is available',\n                  style: TextStyle(color: Colors.green),\n                );\n              }\n              return SizedBox.shrink();\n            },\n          ),\n          ElevatedButton(\n            onPressed: _submitForm,\n            child: Text('Submit'),\n          ),\n        ],\n      ),\n    );\n  }\n}\n```\n\n#### Debouncing async validation\n\nAsyncValidator includes built-in debouncing to prevent excessive API calls during typing. You can customize the debounce duration:\n\n```dart\nasyncValidator\u003cString\u003e([\n  UsernameAvailabilityRule(checkAvailability: _checkUsername),\n], debounceDuration: Duration(milliseconds: 800)) // Custom debounce time\n```\n\n#### Manually triggering async validation\n\nYou can manually trigger async validation using the `validateAsync` method:\n\n```dart\nFuture\u003cvoid\u003e _checkUsername() async {\n  final isValid = await _asyncValidator.validateAsync(\n    _usernameController.text,\n    debounceDuration: Duration.zero, // Optional: skip debouncing\n  );\n  \n  if (isValid) {\n    // Username is valid and available\n  }\n}\n```\n\n### FAQ\n#### How can i show live async validation error message?\nYou can use the `ListenableBuilder` widget to listen to the async validation state and show the error message when it becomes available. Here's an example:\n\n```dart\n```dart\nListenableBuilder(\n  listenable: _asyncValidator.asyncState,\n  builder: (context, _) {\n    if (_compositeValidator.isValidating) {\n      return Text('Checking username availability...');\n    } else if (!_compositeValidator.isValid \u0026\u0026 _asyncValidator.errorMessage != null) {\n      return Text(\n        _asyncValidator.errorMessage!,\n        style: TextStyle(color: Colors.red),\n      );\n    } else if (_compositeValidator.isValid) {\n      return Text(\n        'Username is available',\n        style: TextStyle(color: Colors.green),\n      );\n    }\n    return SizedBox.shrink();\n  },\n)\n```\nThis will show the error message when the async validation fails, and a success message when it passes.\n\nFor live validation feedback as the user types, make sure to set `autovalidateMode` on your Form:\n\n```dart\nForm(\n  key: _formKey,\n  autovalidateMode: AutovalidateMode.always, // Enable live validation\n  child: Column(\n    // Form fields...\n  ),\n)\n```\n\nThis ensures validation runs automatically whenever input changes, providing immediate feedback.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit issues, pull requests, or suggest improvements.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevenosse%2Fform_shield","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstevenosse%2Fform_shield","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevenosse%2Fform_shield/lists"}