{"id":13801308,"url":"https://github.com/splink/veto","last_synced_at":"2026-01-16T17:23:48.881Z","repository":{"id":136372376,"uuid":"172926675","full_name":"splink/veto","owner":"splink","description":"If you don't agree with the data","archived":false,"fork":false,"pushed_at":"2020-01-21T23:32:27.000Z","size":48,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-22T13:32:33.354Z","etag":null,"topics":["scala","validation"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/splink.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2019-02-27T14:05:46.000Z","updated_at":"2022-09-23T08:57:44.000Z","dependencies_parsed_at":"2024-01-05T21:57:11.995Z","dependency_job_id":null,"html_url":"https://github.com/splink/veto","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/splink%2Fveto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/splink%2Fveto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/splink%2Fveto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/splink%2Fveto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/splink","download_url":"https://codeload.github.com/splink/veto/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253932796,"owners_count":21986448,"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":["scala","validation"],"created_at":"2024-08-04T00:01:21.449Z","updated_at":"2026-01-16T17:23:48.872Z","avatar_url":"https://github.com/splink.png","language":"Scala","readme":"[![Build Status](https://travis-ci.org/splink/veto.svg?branch=master)](https://travis-ci.org/splink/veto)\n\n# Veto\nA scala validation library without dependencies.\n\n### Let the code speak:\n\n##### Validate a simple case class\n~~~scala\ncase class Size(width: Int, height: Int)\n\nobject SizeValidator extends ModelValidator[Size] {\n  override def apply(size: Size)(implicit context: Option[Context] = None) = {\n    Check(size)\n      .field(_.width, \"width\")(isGreaterThan(0) and isSmallerOrEqual(5))\n      .field(_.height, \"height\")(isGreaterThan(0) and isSmallerOrEqual(5))\n      .validate\n  }\n}\n\nval size = Size(0, 6)\n\nval result: Xor[Size] = SizeValidator(size) \nresult match {\n  case Valid(s) =\u003e\n    println(s\"valid $s\")\n  case iv: Invalid =\u003e\n    iv.errors.foreach(e =\u003e println(e.message))\n}\n\n// '0' must be greater than '0'.\n// '6' must be smaller or equal to '5'.\n~~~\n\n##### Validate values wrapped in Option\n~~~scala\nval validator: Validator[Option[Int]] = optional(isPositive[Int])\nval result: Xor[Option[Int]] = validator(Some(-1))\n~~~\n\n##### Validate elements in a List\n~~~scala\nval validator: Validator[List[Size]] = listValidator(SizeValidator)\nval result: Xor[List[Size]] = validator(List(Size(0, 0), Size(1, 0)))\n~~~\n\n##### Validate keys or values in a Map\n~~~scala\n// pick the values\nval validator: Validator[Map[String, Size]] = mapValidator[String, Size](tuple2Value(stringContains(\"one\")))\nval result: Xor[Map[String, Size]] = validator(Map(\"one\" -\u003e Size(0, 0)))\n// pick the keys\nval validator: Validator[Map[String, Size]] = mapValidator[String, Size](tuple2Key(stringContains(\"one\")))\nval result: Xor[Map[String, Size]] = validator(Map(\"one\" -\u003e Size(0, 0)))\n\n~~~\n\n##### Recursive fields are supported\n~~~scala\ncase class Item(name: String, items: List[Item])\n\nobject ItemValidator extends ModelValidator[Item] {\n  override def apply(item: Item)(implicit parent: Option[Context]) = {\n    Check(item)\n      .field(_.name, \"name\")(stringNonEmpty)\n      .field(_.items, \"items\")(listValidator(ItemValidator))\n      .validate\n  }\n}\n\nval item = Item(\"one\", Item(\"two\", Nil) :: Item(\"\", Nil) :: Nil)\n\nItemValidator(item)\n~~~\n\n##### It's quick to create a custom Validator:\n~~~scala\ndef stringContains(value: String) = Validator[String] { (s, context) =\u003e\n  if (s.contains(value)) Valid(s)\n  else Invalid(Error(context, 'stringContains, Seq(s, value)))\n}\n~~~\nContext is taken care of upstream, namely Check does the job when it is used to declare which fields of a class are to be validated.\nSo you just need to perform the check for validity. If the value is valid, return Valid with the value and if not, return Invalid. \nInvalid requires the context (from upstream), as well as an error message key and a Seq of values which are to be used in the error message.\nLike ```String'{}' should contain '{}'.``` \n\n\n##### Validate fields whose validity depends on the values of each other\n~~~scala\ncase class Person(name: String, age: Int, birthday: LocalDate)\n\ndef AgeValidator = Validator[(Int, LocalDate)] {\n  case ((age, birthday), context) =\u003e\n    val shouldBeAge = Period.between(birthday, LocalDate.now()).getYears\n    val isValid = shouldBeAge == age\n    if (isValid) Valid(age -\u003e birthday)\n    else Invalid(Error(context, 'wrongAge, Seq(age, birthday, shouldBeAge)))\n}\n\nobject PersonValidator extends ModelValidator[Person] {\n  override def apply(person: Person)(implicit parent: Option[Context]) = {\n    Check(person)\n      .field(_.name, \"name\")(stringNonEmpty)\n      .field(p =\u003e p.age -\u003e p.birthday, \"age\")(AgeValidator)\n      .validate\n  }\n}\n\nPersonValidator(Person(\"max\", 30, LocalDate.of(1950, 1, 1)))\n~~~\n","funding_links":[],"categories":["Table of Contents","Data Binding and Validation"],"sub_categories":["Data Binding and Validation"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsplink%2Fveto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsplink%2Fveto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsplink%2Fveto/lists"}