{"id":20757263,"url":"https://github.com/vapor-community/forms","last_synced_at":"2025-04-29T10:36:47.821Z","repository":{"id":104073186,"uuid":"72986771","full_name":"vapor-community/forms","owner":"vapor-community","description":"Brings simple, dynamic and re-usable web form handling to Vapor.","archived":false,"fork":false,"pushed_at":"2017-09-29T18:58:12.000Z","size":78,"stargazers_count":57,"open_issues_count":3,"forks_count":10,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-30T12:11:23.403Z","etag":null,"topics":["form-builder","form-validation","vapor","vapor-provider"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vapor-community.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2016-11-06T11:40:17.000Z","updated_at":"2025-02-27T22:31:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"f31bd474-77e1-4760-a52d-01604256dd5d","html_url":"https://github.com/vapor-community/forms","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vapor-community%2Fforms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vapor-community%2Fforms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vapor-community%2Fforms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vapor-community%2Fforms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vapor-community","download_url":"https://codeload.github.com/vapor-community/forms/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251484757,"owners_count":21596805,"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-builder","form-validation","vapor","vapor-provider"],"created_at":"2024-11-17T09:41:13.968Z","updated_at":"2025-04-29T10:36:47.811Z","avatar_url":"https://github.com/vapor-community.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Forms\n\n![Swift](http://img.shields.io/badge/swift-3.1-brightgreen.svg)\n![Vapor](http://img.shields.io/badge/vapor-2.0-brightgreen.svg)\n![Travis](https://travis-ci.org/vapor-community/forms.svg?branch=master)\n[![CircleCI](https://circleci.com/gh/vapor-community/forms.svg?style=svg)](https://circleci.com/gh/vapor-community/forms)\n\n---\n\nBrings simple, dynamic and re-usable web form handling to\n[Vapor](https://github.com/vapor/vapor).\n\nThis library is being used in production and should be safe, but as an early\nrelease the API is subject to change.\n\nDon't forget to add to your `providers` if you want to use built-in Leaf tags:\n\n```swift\nimport Vapor\nimport Forms\n\nextension Config {\n    ...\n\n    private func setupProviders() throws {\n        try addProvider(Forms.Provider.self)\n        ...\n    }\n}\n\n```\n\n## Features\n\nCreate a `Fieldset` on the fly:\n\n```swift\nlet fieldset = Fieldset([\n  \"firstName\": StringField(label: \"First Name\"),\n  \"lastName\": StringField(label: \"Last Name\"),\n])\n```\n\nand add validation:\n\n```swift\nlet fieldset = Fieldset([\n  \"firstName\": StringField(),\n  \"lastName\": StringField(),\n  \"email\": StringField(String.EmailValidator()),\n], requiring: [\"email\"])\n```\n\nYou can add multiple validators, too:\n\n```swift\nlet fieldset = Fieldset([\n  \"firstName\": StringField(label: \"First Name\",\n    String.MinimumLengthValidator(characters: 3),\n    String.MaximumLengthValidator(characters: 255),\n  ),\n  \"lastName\": StringField(label: \"Last Name\",\n    String.MinimumLengthValidator(characters: 3),\n    String.MaximumLengthValidator(characters: 255),\n  ),\n  \"email\": StringField(String.EmailValidator()),\n], requiring: [\"email\"])\n```\n\nAnd even add whole-fieldset validation after individual field validators have run:\n\n```swift\nstatic let loginFieldset = Fieldset([\n  \"username\": StringField(label: \"Username\"),\n  \"password\": StringField(label: \"Password\"),\n], requiring: [\"username\", \"password\"]) { fieldset in\n  let loginResult = validateCredentials(username: fieldset.values[\"username\"]!.string!, password: fieldset.values[\"password\"]!.string!)\n  if !loginResult {\n    fieldset.errors[\"password\"].append(FieldError.validationFailed(message: \"Username and password not valid\"))\n  }\n}\n```\n\nValidate from a `request`:\n\n```swift\nfieldset.validate(request.data)\n```\n\nor even from a simple object:\n\n```swift\nfieldset.validate([\n  \"firstName\": \"Peter\",\n  \"lastName\": \"Pan\",\n])\n```\n\nValidation results:\n\n```swift\nswitch fieldset.validate(request.data) {\ncase .success(let validatedData):\n  // validatedData is guaranteed to contain correct field names and values.\n  let user = User(\n    firstName: validatedData[\"firstName\"]!.string!,\n    lastName: validatedData[\"lastName\"]!.string!,\n  )\ncase .failure:\n  // Use the field names and failed validation messages in `fieldset.errors`,\n  // and the passed-in values in `fieldset.values` to re-render your form.\n  // If a single field fails multiple validators, you'll receive\n  // an error string for each rather than just failing at the first\n  // validator.\n}\n```\n\nGain strongly-typed results by wrapping the `Fieldset` in a re-usable `Form`.\n\n```swift\nstruct UserForm: Form {\n  let firstName: String\n  let lastName: String\n  let email: String\n\n  static let fieldset = Fieldset([\n    \"firstName\": StringField(),\n    \"lastName\": StringField(),\n    \"email\": StringField(String.EmailValidator()),\n  ], requiring: [\"firstName\", \"lastName\", \"email\"])\n\n  init(validatedData: [String: Node]) throws {\n    // validatedData is guaranteed to contain correct field names and values.\n    firstName = validatedData[\"firstName\"]!.string!\n    lastName = validatedData[\"lastName\"]!.string!\n    email = validatedData[\"email\"]!.string!\n  }\n}\n```\n\nNow you can easily and type-safely access the results of your form validation.\n\n```swift\ndrop.get { request in\n  do {\n    let form = try UserForm(validating: request.data)\n    // Return to your view, or use the properties to save a Model instance.\n    return \"Hello \\(form.firstName) \\(form.lastName)\"\n  } catch FormError.validationFailed(let fieldset) {\n    // Use the leaf tags on the fieldset to re-render your form.\n    return try drop.view.make(\"index\", [\n      \"fieldset\": fieldset,\n    ])\n  }\n}\n```\n\nRendering a form with validation error messages:\n\n```html\n\u003clabel\u003eName\u003c/label\u003e\n\u003cinput type='text' name='name' value='#valueForField(fieldset, \"name\")'\u003e\n#ifFieldHasErrors(fieldset, \"name\") { \u003cul class=\"errorlist\"\u003e }\n#loopErrorsForField(fieldset, \"name\", \"message\") { \u003cli\u003e#(message)\u003c/li\u003e }\n#ifFieldHasErrors(fieldset, \"name\") { \u003c/ul\u003e }\n```\n\nRendering a `select`:\n\n```html\n\u003cselect name='colour'\u003e\n  \u003coption hidden\u003e\u003c/option\u003e\n  \u003coption #valueForField(fieldset, \"colour\") { #equal(self, \"red\") { selected } }\u003ered\u003c/option\u003e\n  \u003coption #valueForField(fieldset, \"colour\") { #equal(self, \"blue\") { selected } }\u003eblue\u003c/option\u003e\n  \u003coption #valueForField(fieldset, \"colour\") { #equal(self, \"green\") { selected } }\u003egreen\u003c/option\u003e\n\u003c/select\u003e\n```\n\n## Documentation\n\nSee the extensive tests file for full usage while in early development.\nBuilt-in validators are in the `Validators` directory.\nProper documentation to come.\n\n## Known issues\n\nSo far, everything works as it says on the tin.\n\nThere are some unfortunate design aspects, though, which the author hopes to\nstraighten out.\n\nOne of Swift's greatest assets is strong typing, but this library largely\nbypasses all those benefits. This is due to limitations in both Swift's\nintrospection mechanism, and the author's general intelligence. The `Form`\nprotocol is an attempt to resolve this lack; *in theory*, when the end-user\nfills out their `fields` property and `init` method correctly there should\nbe no problems, but it would be nice for the compiler to catch any typos\nbefore the app runs. Using an `enum` for field names would be a good idea.\n\nThe majority of the library uses `...ValidationResult` enums to return useful\ninformation about the success or failure of validation. However, the `Form`\nprotocol also `throws` because the mapping of validated data to instance\nproperty is implemented by the end-user and errors may arise.\n\nVapor's `Node` is heavily used, as is `Content`. Unfortunately, the built-in\n[validation](https://vapor.github.io/documentation/guide/validation.html)\nis (despite the author's best efforts) almost completely unused. Future work\nmay be able to converge the two validation mechanisms enough that this library\ndoesn't need to supply its own.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvapor-community%2Fforms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvapor-community%2Fforms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvapor-community%2Fforms/lists"}