{"id":13509737,"url":"https://github.com/albert-io/optimal","last_synced_at":"2026-02-21T13:01:16.848Z","repository":{"id":60775492,"uuid":"128116955","full_name":"albert-io/optimal","owner":"albert-io","description":"A schema based keyword list option validator.","archived":false,"fork":false,"pushed_at":"2025-07-07T17:41:14.000Z","size":79,"stargazers_count":50,"open_issues_count":1,"forks_count":5,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-07-07T17:41:19.020Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/albert-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-04T20:18:49.000Z","updated_at":"2025-07-07T17:41:17.000Z","dependencies_parsed_at":"2022-10-04T17:15:53.496Z","dependency_job_id":null,"html_url":"https://github.com/albert-io/optimal","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/albert-io/optimal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/albert-io%2Foptimal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/albert-io%2Foptimal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/albert-io%2Foptimal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/albert-io%2Foptimal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/albert-io","download_url":"https://codeload.github.com/albert-io/optimal/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/albert-io%2Foptimal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29681468,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T12:30:22.644Z","status":"ssl_error","status_checked_at":"2026-02-21T12:29:55.402Z","response_time":107,"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":[],"created_at":"2024-08-01T02:01:12.154Z","updated_at":"2026-02-21T13:01:16.823Z","avatar_url":"https://github.com/albert-io.png","language":"Elixir","funding_links":[],"categories":["Validations"],"sub_categories":[],"readme":"# Optimal\n\nOptimal is a schema based `opt` validator. It is verbose, but I've tried many other data validation libraries, and their succinctness came with a cost when it came to features. There are still a lot of optimizations and improvements that can be made, so contributions are very welcome.\n\nThis `opt` validator has a bit of a niche. It fits in just fine with validating any keyword list, but its especially useful for validating compile-time options, like ones provided to functions in a DSL.\n\nView the documentation: [https://hexdocs.pm/optimal](https://hexdocs.pm/optimal)\n\n## Roadmap\n\n* Better error messages, both for type mismatches and in general\n* Optimize. The schema based design allows schemas to be declared at compile time (for instance in module attributes) and that should be leveraged as much as possible to ensure that validating a schema does no work that could be done when building the schema.\n* Macro. We could potentially provide something that can partially validate opts at compile time. For instance, any literal values or known values could be validated at compile time.\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package can be installed\nby adding `optimal` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:optimal, \"~\u003e 0.3.7\"}\n  ]\nend\n```\n\n## Getting Started \n\n### Validation Examples\n\nTo use Optimal, you define your validation rules as an Optimal schema and then validate input against it using the `Optimal.validate/2` or `Optimal.validate!/2` functions.\n \nValidate a keyword list:\n```elixir\niex\u003e schema = Optimal.schema(opts: [:foo, :bar, :baz])\niex\u003e my_list = [{:foo, \"foo val\"}, {:bar, \"bar val\"}, {:baz, \"bazz val\"}]\niex\u003e Optimal.validate(my_list, schema)\n{:ok, [foo: \"foo val\", bar: \"bar val\", baz: \"bazz val\"]}\n```\n\nOr validate a map:\n\n```elixir\niex\u003e my_map = %{foo: \"foo val\", bar: \"bar val\", baz: \"bazz val\"}\n%{bar: \"bar val\", baz: \"bazz val\", foo: \"foo val\"}\niex\u003e Optimal.validate(my_map, schema)\n{:ok, [bar: \"bar val\", baz: \"bazz val\", foo: \"foo val\"]}\n```\n\nNotice that in both cases, a keyword list is returned.\n\nUse `Optimal.validate!/2` to return an error instead of a tuple:\n```elixir\niex\u003e bad_map = %{d: \"not allowed\"}\n%{other: \"stuff\"}\niex\u003e schema = Optimal.schema(opts: [:a, :b, :c])\niex\u003e Optimal.validate!(bad_map, schema)\n** (ArgumentError) Opt Validation Error: other - is not allowed (no extra keys)\n    (optimal) lib/optimal.ex:44: Optimal.validate!/2\n```\n\nYou can require that your inputs be of a certain type:\n\n```elixir\niex\u003e schema = Optimal.schema(opts: [age: :int, name: :string])\niex\u003e my_data = [{:age, 12}, {:name, false}]\niex\u003e Optimal.validate(my_data, schema)\n{:error, [name: \"must be of type :string\"]}\n```\n\n### Schema Examples\n\nDefine your validation rules in your schema.\n\n```elixir\n# Allow no opts\nOptimal.schema()\n\n# Allow any opts\nOptimal.schema(extra_keys?: true)\n\n# Allow a specific set of opts\nOptimal.schema(opts: [:foo, :bar, :baz])\n\n# Allow specific types\nOptimal.schema(opts: [foo: :int, bar: :string, baz: :pid])\n\n# Require certain opts\nOptimal.schema(opts: [foo: :int, bar: :string, baz: :pid], required: [:foo, :bar])\n\n# Provide defaults for arguments (defaults will have to pass any type validation)\n# If they provide they key, but a `nil` value, the default is *not* used.\nOptimal.schema(opts: [foo: :int, bar: :string, baz: :boolean], defaults: [baz: true])\n\n# Allow only specific values for certain opts\nOptimal.schema(opts: [foo: {:enum, [1, 2, 3]}])\n\n# Custom validations\n# Read below for more info\ndef custom(field_value, field_name, all_opts, schema) do\n  if is_special(field_value) do\n    :ok\n  else\n    [{field_name, \"must be special\"}]\n  end\nend\n\nOptimal.schema(opts: [foo: :integer, bar: :string], custom: [bar: \u0026custom/4])\n```\n\n## Types\n\n### Scalar Types\n\n* :any\n* :atom\n* :binary\n* :bitstring\n* :boolean\n* :float\n* :function\n* :int\n* :integer\n* :keyword\n* :list\n* :string\n* :map\n* :nil\n* :number\n* :pid\n* :port\n* :reference\n* :tuple\n* :struct\n\n### Composite/Complex Types\n\n* `{:keyword, value_type}` - Keyword where all values are of type `value_type`\n* `{:list, value_type}` - List where all values are of type `value_type`\n* `{:function, arity}` - A function with the arity given by `arity`\n* `{:struct, Some.Struct`} - An instance of `Some.Struct`\n* `%Some.Struct{}` - Same as `{:struct, Some.Struct}`\n* `{:enum, [value1, value2]}` - Allows any value in the list.\n* `{:tuple, tuple_size}` - Tuple with size `tuple_size`.\n* `{:tuple, {type1, type2, ...}}` - Tuple with given type structure, so the first element is of type `type1`, etc.\n* `{:tuple, tuple_size, value_type}` - Tuple with size `tuple_size` and every element of type `value_type`.\n* A nested optimal schema - Will validate that the provided keyword list adheres to the schema.\n\n## Custom Validations\n\nYour custom validators are defined as keyword list added to the `custom:` atom, e.g.\n\n```elixir\nOptimal.schema(opts: [foo: :integer, bar: :string], custom: [bar: \u0026my_custom_validator/4])\n```\n\nCustom validations have the ability to add arbitrary errors and can modify the `opts` as they pass through. They are run in order, and unlike all built in validations, they are only run on valid opts. In other words, the custom validators run _after_ the other validators.\n\nYour custom validation functions should receive 4 arguments:\n \n* field value\n* field id (atom)\n* options\n* schema\n\nAnd they may return several different types of responses:\n\n* `true` / `false` to indicate whether it passed or failed validation\n* `:ok` to indicate that it passed validation\n* `{:ok, updated_options}` to provide modifications to the options before output\n* `{:error, error_or_errors}` to provide a custom message(s) about a failed validation\n* `[]` to indicate that it passed validation\n* a list of errors to indicate why it failed validation\n\n### Custom Validator Usage Example\n\nBecause custom validators can modify the `opts`, we can change the final output to a map (arbitrarily, this validation rule is attached to the `c` field):\n\n```elixir\niex\u003e my_data = [{:a, \"Apple\"}, {:b, \"Boy\"}, {:c, \"Cat\"}]\niex\u003e schema = Optimal.schema(opts: [a: :string, b: :string, c: :string], custom: [c: fn _, _, opts, _ -\u003e {:ok, Enum.into(opts, %{})} end])\niex\u003e Optimal.validate(my_data, schema)                                      \n{:ok, %{a: \"Apple\", b: \"Boy\", c: \"Cat\"}\n```\n\n### Custom Validation Function Examples\n\n```elixir\n# Simple (returning booleans)\ndef is_ten(field_value, _, _, _) do\n  field_value == 10\nend\n\n# Custom errors (ok/error tuples)\ndef is_ten(field_value, field, _, _) do\n  if field_value == 10 do\n    :ok\n  else\n    {:error, {field, \"should really have equaled ten\"}}\n  end\nend\n\n# Returning a list of errors\ndef greater_than_1_and_even(field_value, field, _, _) do\n  errors =\n    if field_value \u003e 1 do\n      []\n    else\n      [{field, \"should be greater than 1\"}]\n    end\n\n  if Integer.is_even(field_value) do\n    errors\n  else\n    [{field, \"should be even\"} | errors]\n  end\nend\n```\n\n## Auto Documentation\n\nIf your schemas are defined at compile time, it is possible to interpolate a generated documentation for them into your docstrings.\nIf you are doing this, you may also want to leverage the `describe` opt when building schemas, that lets you attach descriptions.\n\nFor example:\n\n```elixir\n\n@opts Optimal.schema(opts: [\n    foo: [:int, :string],\n    bars: {:list, :int}\n  ],\n  required: [:foo],\n  describe: [\n    foo: \"The id of the foo you want\",\n    bars: \"The ids of all of the bars you want\"\n  ],\n  defaults: [\n    bars: []\n  ],\n  extra_keys?: true\n)\n\n@doc \"\"\"\nThis does a special thing.\n\n#{Optimal.Doc.document(@opts)}\n\nMore in-depth documentation\n\"\"\"\ndef my_special_function(opts) do\n\nend\n```\n\nThis would generate a docstring that looks like:\n\n### Doc Example\n\nThis does a special thing.\n\n---\n\n## Opts\n\n* `foo`(`[:int, :string]`) **Required**: The id of the foo you want\n* `bars`(`{:list, :int}`): The ids of all of the bars you want - Default: []\n\nAlso accepts extra opts that are not named here.\n\n---\n\nMore in-depth documentation\n\n## Schema merging\n\nThis behavior is not set in stone, and will probably need to take a `strategy` option to support different kinds of merging opt schemas. This is very useful when working with many functions that are more specific versions of some generic action, or that all eventually call into the same function and need to accept that function's opts as well.\n\n```elixir\n\nschema1 = Optimal.schema(opts: [foo: :int])\nschema2 = Optimal.schema(opts: [foo: :string, bar: :int])\n\nOptimal.merge(schema1, schema2) == Optimal.schema(opts: [foo: [:int, :string], bar: :int])\n```\n\n### Merge annotations\n\nYou can provide an annotation when merging, and options will be further grouped by that annotation.\n\n```elixir\nOptimal.merge(schema1, schema2, annotate: \"Shared\")\n```\n\n---\n\n* `id`(`:int`) **Required**\n* `foo`(`:int`)\n\n#### Shared\n\n* `baz`(`:int`)\n* `bar`(`:int`)\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falbert-io%2Foptimal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falbert-io%2Foptimal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falbert-io%2Foptimal/lists"}