{"id":15063566,"url":"https://github.com/edgurgel/ham","last_synced_at":"2025-07-10T18:34:38.688Z","repository":{"id":253626356,"uuid":"840630167","full_name":"edgurgel/ham","owner":"edgurgel","description":"Library to validate function arguments and return values against their typespecs. Extracted out from Hammox","archived":false,"fork":false,"pushed_at":"2025-05-28T22:47:41.000Z","size":413,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-08T22:07:03.320Z","etag":null,"topics":["elixir","specs","typechecker","types","typespec"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/ham","language":"Elixir","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/edgurgel.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,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-08-10T07:50:36.000Z","updated_at":"2025-05-28T22:47:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"9630a61e-b14f-4d80-a261-21404e475b00","html_url":"https://github.com/edgurgel/ham","commit_stats":null,"previous_names":["edgurgel/ham"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/edgurgel/ham","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fham","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fham/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fham/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fham/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edgurgel","download_url":"https://codeload.github.com/edgurgel/ham/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fham/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264631213,"owners_count":23640941,"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":["elixir","specs","typechecker","types","typespec"],"created_at":"2024-09-25T00:04:34.108Z","updated_at":"2025-07-10T18:34:38.652Z","avatar_url":"https://github.com/edgurgel.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ham\n\n[![CI](https://github.com/edgurgel/ham/actions/workflows/ci.yml/badge.svg)](https://github.com/edgurgel/ham/actions/workflows/ci.yml)\n[![Module Version](https://img.shields.io/hexpm/v/ham.svg)](https://hex.pm/packages/ham)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/ham)\n[![Total Download](https://img.shields.io/hexpm/dt/ham.svg)](https://hex.pm/packages/ham)\n[![License](https://img.shields.io/hexpm/l/ham.svg)](https://github.com/edgurgel/ham/blob/main/LICENSE)\n[![Last Updated](https://img.shields.io/github/last-commit/edgurgel/ham.svg)](https://github.com/edgurgel/ham/commits/main)\n\nHam is a library to validate function arguments and return values against their typespecs.\nIt was originally extracted out from [Ham](https://github.com/msz/hammox/) but\nwithout the Mox integration. Hammox - Mox = Ham!\nThanks [@msz](https://github.com/msz) for creating Hammox!\n\nThe main reason why I wanted to extract it out is so that it can be used as part of Mimic or other testing libraries.\n\n## Installation\n\nAdd `:ham`:\n\n```elixir\ndef deps do\n  [\n    {:ham, \"~\u003e 0.1\", only: :test}\n  ]\nend\n```\n\n## Using Ham\n\nOne can simply let Ham apply a module function using `Ham.apply/2` (function or macro) and it will validate argument and return value:\n\n```elixir\niex\u003e Ham.apply(URI, :decode, [\"https%3A%2F%2Felixir-lang.org\"])\n\"https://elixir-lang.org\"\niex\u003e Ham.apply(URI, :char_reserved?, [\"a\"])\n** (Ham.TypeMatchError) 1st argument value \"a\" does not match 1st parameter's type byte().\n  Value \"a\" does not match type 0..255.\n```\n\n```elixir\niex\u003e Ham.apply(URI.decode(\"https%3A%2F%2Felixir-lang.org\"))\n\"https://elixir-lang.org\"\niex\u003e Ham.apply(URI.char_reserved?(\"a\"))\n** (Ham.TypeMatchError) 1st argument value \"a\" does not match 1st parameter's type byte().\n  Value \"a\" does not match type 0..255.\n```\n\nAnother way is to pass the args and return value without Ham executing anything:\n\n```elixir\niex\u003e Ham.validate(URI, :char_reserved?, [\"a\"], true)\n{:error,\n %Ham.TypeMatchError{\n   reasons: [\n     {:arg_type_mismatch, 0, \"a\", {:type, {324, 24}, :byte, []}},\n     {:type_mismatch, \"a\", {:type, 0, :range, [{:integer, 0, 0}, {:integer, 0, 255}]}}\n   ]\n }}\n\niex\u003e Ham.validate!(URI, :char_reserved?, [\"a\"], true)\n** (Ham.TypeMatchError) 1st argument value \"a\" does not match 1st parameter's type byte().\n  Value \"a\" does not match type 0..255.\n```\n\nBoth `apply` and `validate` accept `behaviours` as an option to declare that the module\nimplements certain behaviours.\n\nFor example let's implement a module that does a poor job of implementing `Access`\n\n```elixir\ndefmodule CustomAccess do\n  @behaviour Access\n\n  def fetch(_data, _key), do: :poor\n  def get_and_update(_data, _key, _function), do: :poor\n  def pop(data, key), do: :poor\nend\n\nHam.apply(CustomAccess.fetch([], \"key\"), behaviours: [Access])\n** (Ham.TypeMatchError) Returned value :poor does not match type {:ok, Access.value()} | :error.\n  Value :wrong does not match type {:ok, Access.value()} | :error.\n```\n\n## Why use Ham for my application code when I have Dialyzer?\n\nDialyzer is a powerful static analysis tool that can uncover serious problems.\nBut during tests dialyzer is not as useful. Ham can be used to validate function arguments\nand return values during tests even if they are randomly generated or loaded from files.\n\n## Protocol types\n\nA `t()` type defined on a protocol is taken by Ham to mean \"a struct\nimplementing the given protocol\". Therefore, trying to pass `:atom` for an\n`Enumerable.t()` will produce an error, even though the type is defined as\n`term()`:\n\n```none\n** (Ham.TypeMatchError)\nReturned value :atom does not match type Enumerable.t().\n  Value :atom does not implement the Enumerable protocol.\n```\n\n## Limitations\n- For anonymous function types in typespecs, only the arity is checked.\nParameter types and return types are not checked.\n\n- For records we are only checking if it's a tuple and that the first value is the name of the record.\n\n## License\n\nCopyright 2019 Michał Szewczak\n\nCopyright 2024 Eduardo Gurgel\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License. You may obtain a copy of\nthe License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\nor agreed to in writing, software distributed under the License is\ndistributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgurgel%2Fham","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedgurgel%2Fham","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgurgel%2Fham/lists"}