{"id":16318531,"url":"https://github.com/vic/spec","last_synced_at":"2025-03-16T14:30:56.366Z","repository":{"id":57551050,"uuid":"78627188","full_name":"vic/spec","owner":"vic","description":"Data specification conformance and generation for Elixir","archived":false,"fork":false,"pushed_at":"2020-05-19T07:13:41.000Z","size":80,"stargazers_count":78,"open_issues_count":1,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-27T10:36:03.700Z","etag":null,"topics":["clojure-spec","data-conformers","elixir","predicates","property-based-testing","property-based-testing-specifcation","specification"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/spec","language":"Elixir","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/vic.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}},"created_at":"2017-01-11T10:14:48.000Z","updated_at":"2024-07-12T12:20:03.000Z","dependencies_parsed_at":"2022-09-26T18:41:19.247Z","dependency_job_id":null,"html_url":"https://github.com/vic/spec","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fspec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fspec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fspec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fspec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vic","download_url":"https://codeload.github.com/vic/spec/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243818195,"owners_count":20352629,"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":["clojure-spec","data-conformers","elixir","predicates","property-based-testing","property-based-testing-specifcation","specification"],"created_at":"2024-10-10T22:23:49.574Z","updated_at":"2025-03-16T14:30:55.849Z","avatar_url":"https://github.com/vic.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spec \u003ca href=\"https://travis-ci.org/vic/spec\"\u003e\u003cimg src=\"https://travis-ci.org/vic/spec.svg\"\u003e\u003c/a\u003e\n[![help maintain this lib](https://img.shields.io/badge/looking%20for%20maintainer-DM%20%40vborja-663399.svg)](https://twitter.com/vborja)\n\nSpec is a data validation library for Elixir, inspired by [clojure.spec].\n\nLike `clojure.spec`, this library does not implement a type system\nand the data specifications created with it\nare not useful for checking at compile time.\nFor that, use the [@spec typespecs][typespecs] Elixir builtin.\n\nSpec calls cannot be used for pattern matching, nor in function head guards, \nbecause validating with Spec could involve calling some Elixir runtime functions\nwhich are not allowed inside a pattern match.\nIf you are looking for a way to create composable patterns,\ntake a look at [Expat][expat].\nYou can, for example, conform your data with Spec\nand then pattern match on the conformed value,\nusing Expat to easily extract values from it.\n\nHaving said that, you can use Spec to validate that your data is of a\ngiven type,\nhas certain structure, or satisfies some predicates.\nSpec supports all Elixir data types;\nthat is, you can match on lists, maps, scalars, structs, and tuples.\nMaps, structs, and keyword lists can be checked for required keys.\n\nSpecs can be combined in various ways:\npassed as arguments to other specs,\nlogically combined by using the `and`,`or` operators, and finally,\nsequenced or alternated using regex (regular expression) operators.\nYou can validate your function arguments or return values\n(it's all done at *run-time*);\nsee the [`RandomJane`](#instrumented-def) example below.\nFinally, you can \"exercise\" a spec to get sample data that conforms to it.\n\n- [Intro](#purpose)\n  - [Purpose](#purpose)\n  - [Installation](#installation)\n- [Usage](#usage)\n  - [Predicates](#predicates)\n  - [Conformers](#conformers)\n  - [Conforming data](#data-structure-specifications)\n    - [Data structure specifications](#data-structure-specifications)\n    - [Alternating specs](#alternating-specs)\n    - [Key specs](#key-specs)\n    - [Regex repetition operators](#regex-repetition-operators)\n  - [Defining reusable specs](#define-specs)\n    - [Defspec](#define-specs)\n    - [Parameterized Specs](#parameterized-specs)\n  - [Conforming functions](#function-specifications)\n    - [Function specifications](#function-specifications)\n    - [Define conformed functions](#define-conformed-functions)\n    - [Instrumented def](#instrumented-def)\n- [Things to do](#things-to-do)\n\n## Purpose\n\nSpec's purpose is to provide a library\nfor creating composable data structure specifications.\nOnce you create an spec, you can match data with it,\nget human-readable descriptive messages,\nor programatically generate detailed errors\nif something inside of it does not conform to the specification.\nYou can also exercise the spec, obtaining some random (but conformant) data.\nThis can be used, for example, in tests.\n\nAlthough Spec is heavily inspired by `clojure.spec`,\nit does not attempt to exactly match the `clojure.spec` API.\nInstead, Spec tries to follow Elixir/Erlang idioms,\nproducing a more familiar API for alchemists.\n\n## Installation\n\nThe [Spec package](https://hex.pm/packages/spec)\nis [published](https://hex.pm/docs/publish) on [Hex](https://hex.pm).\nSo, it can be installed by adding `spec` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [{:spec, \"~\u003e 0.1\"}]\nend\n```\n\n## Usage\n\nThe rest of this document details the Spec API and example usage.\nYou can also take a look at the several [tests] for more examples.\nIn general, however, you only need to `use` the Spec module\nonce in each module that invokes one or more Spec predicates:\n\n```elixir\nuse Spec\n```\n\n### Predicates\n\nPredicates are Elixir's basic tool for validating data.\nA predicate is a function (or macro) that takes some data\nand returns either `true` or `false`.\nFor example, `is_number/1` is a builtin predicate\nthat will return `true` when invoked like `is_number(42)`.\n\nPredicates can be used as specs by feeding them to `Spec.conform(spec, data)`,\nalong with some data to check.\n\n```elixir\niex\u003e use Spec\niex\u003e conform(is_number(), 24)\n{:ok, 24}\n```\n\nNote that `conform/2` is a macro, rather than a function.\nThis lets it accept a specification (e.g., a predicate such as `is_number()`)\nwith no arguments.\nThe macro provides its second argument (the data value)\nas the first argument for the specification.\nSo, when performing the above validation, Spec will do `24 |\u003e is_number()`.\n\nThe return value of a successful `conform/2` call is an `:ok` tagged tuple,\neven though `is_number/1` actually returns a boolean (more on this later).\nThe second value of the tuple is the input data value.\n\nYou can use any Elixir or Erlang predicate (with any number of arguments)\nto conform data.\nSimply package all but the last argument in a tuple\nand use this as the second argument to the spec:\n\n```elixir\ndef tuple_sum({a, b}, c) when a + b == c, do: true\ndef tuple_sum(_, _), do: false\n\nconform(tuple_sum(44), {12, 32})\n# =\u003e {:ok, {12, 32}}\n```\n\nWhen used with this predicate, Spec will execute `{12, 32} |\u003e tuple_sum(44)`.\nThe returned data will be tagged with `:ok` or `:error`, as appropriate.\n\nActually, Spec adapts boolean predicates and makes them conform to the\nErlang idiom of returning tagged tuples\n(e.g., `{:ok, conformed}`, `{:error, mismatch}`).\nSo, predicates are a particular case of data conformers in Spec.\n\n### Conformers\n\nConformers are functions that take data\nand return `{:ok, conformed}` or `{:error, %Spec.Mismatch{}}`,\nwhere `conformed` is a \"conformed\" version of the input value.\n`Spec.Mismatch` is just a data structure useful\nfor describing what went wrong and where the error occurred.\n\n`Spec.conform!/2` does not return a tuple,\nbut it raises an exception on error:\n\n```elixir\niex\u003e conform!(is_number(), 42)\n# =\u003e 42\niex\u003e conform!(is_number(), \"two\")\n** (Spec.Mismatch) `\"two\"` does not satisfy predicate `is_number()`\n```\n\nThe `conformed` value does not necessarily need to equal the input `data`.\nFor example, the conformer could choose to transform the data\nand return a destructured value. \n\n### Data structure specifications\n\nLet's go back to conforming data with specifications and see how we can \nconstruct them.\n\nAtoms, numbers, and binaries only match equal values:\n\n```elixir\niex\u003e conform!(:hello, :hello)\n:hello\n```\n\nBut tuples and friends can specify their inner elements:\n\n```elixir\niex\u003e conform!({is_atom(), is_number()}, {:ok, 22})\n{:ok, 22}\n\niex\u003e conform!({is_atom(), is_number()}, [:ok, 22])\n** (Spec.Mismatch) `[:ok, 22]` is not a tuple\n\niex\u003e conform!({is_atom(), is_binary()}, {:ok, 22})\n** (Spec.Mismatch) `22` does not satisfy predicate `is_binary()`\nat `1` in `{:ok, 22}`\n```\n\nSo, using the tuple literal syntax,\nSpec will check that the value actually is a tuple of the same size,\nand that every element in it conforms to the corresponding spec.\n\nSimilarly, for list literals,\nthe spec `[is_integer()]` is a list containing a single integer value. \n\nNaturally, the `_` placeholder matches anything.\nSo, `[{is_atom(), _}]` could describe a keyword list with a single key:\n\n```elixir\niex\u003e conform!([{is_atom(), _}], foo: 22)\n[foo: 22]\n```\n\nYou can also use the map literal syntax,\nspecifying more than one valid possibility.\n(To check for the presence of map keys and which combinations\nof keys are valid, see `Spec.keys`, below.)\n\n```elixir\niex\u003e conform!(%{is_binary() =\u003e is_number()}, %{\"hola\" =\u003e 22})\n%{\"hola\" =\u003e 22}\n\niex\u003e conform!(%{is_binary() =\u003e is_binary(), is_atom() =\u003e is_binary()}, \n...\u003e          %{\"hola\" =\u003e \"es\", :hello =\u003e 44})\n** (Spec.Mismatch) Inside `%{:hello =\u003e 44, \"hola\" =\u003e 22}`, one failure:\n(failure 1) at `:hello`\n\n  `44` does not satisfy predicate `is_binary()`\n```\n\n### Alternating specs\n\nInside a spec, the `and`/`or` operators are allowed.\nFor example, as previously shown on the data structure section,\nyou could use the `{_, _}` spec to check for a two-element tuple.\nBut for learning purposes, let's define it by combining two other specs.\n\nWe know Elixir's `is_tuple/1` and `tuple_size/1` could be handy here.\nRemember that each spec expects its data as first argument,\nso by `and`ing them, you can conform like this:\n\n```elixir\niex\u003e conform(is_tuple() and \u0026(tuple_size(\u00261) == 2), {1, 2})\n{:ok, {1, 2}}\n\niex\u003e conform!(is_tuple() and \u0026(tuple_size(\u00261) == 2), {1})\n** (Spec.Mismatch) `{1}` does not satisfy predicate `\u0026(tuple_size(\u00261) == 2)`\n```\n\nIn a similar fashion, you can check against two specification alternatives:\n\n```elixir\niex\u003e conform(is_atom() or is_number(), 20)\n{:ok, 20}\n```\n\nHowever, it would be really handy to know which of the two specs matched `20`.\nFor that, let's introduce the concept of tagged specs.\n\nA tag can be combined with any spec;\nif the spec matches, a tagged tuple will\nbe created for its conformed value. For example:\n\n```elixir\niex\u003e conform!(:hello :: is_binary(), \"world\")\n{:hello, \"world\"}\n```\n*note:* tagged specs use `::` syntax familiar to Elixir [typespecs].\n\nTagged specs are the first example we have seen of a conformed value\nthat is different from the original data given to the spec.\nIn this case, the conformer creates a tagged tuple, wrapping data with a name.\n\nThis way, you can set a tag on any spec alternative:\n\n```elixir\niex\u003e a = :foo\niex\u003e b = :bar\niex\u003e conform((a :: is_atom()) or (b :: is_number()), 20)\n{:ok, {:bar, 20}}\n```\n\nIn addition, using tags inside a list spec creates handy keywords:\n\n```elixir\niex\u003e conform!([:a :: is_atom(), :b :: is_number()], [:michael, 23])\n[a: :michael, b: 23]\n```\n\nFinally, in Spec you can use the Elixir pipe\nto feed the conformed value into any function.\nThe piped function will be called *only*\nif the data has been verified to conform with the preceding specification.\n\nTry not to abuse this;\nit's better to create a function and have at most a single pipe.\nThe purpose of piped specs is so that you can create functions\nthat work on already defined predicates\nand return possibly different conformed values.\n\n```elixir\n# The conformed value from is_tuple is fed to elem(1), then to get(:subject).\niex\u003e conform(is_tuple() |\u003e elem(1) |\u003e Map.get(:subject), {:error, %{subject: 12}})\niex\u003e {:ok, 12}\n```\n\nThe following example, adapted from the test suite,\nshows how pipes could be used to normalize map keys\nand perform case- or format-indifferent matching:\n\n```elixir\ndef right(_left, right), do: right\ndef indif(a, b), do: String.downcase(to_string(a)) == String.downcase(to_string(b))\n\ndata = %{\"a\" =\u003e 1, :B =\u003e 2, :c =\u003e 3}\nconform(%{\n  indif(\"A\") |\u003e right(:foo) =\u003e is_number(),\n  indif(:b)  |\u003e right(:bar) =\u003e is_number()\n}, data)\n# =\u003e {:ok, %{foo: 1, bar: 2}}\n```\n\n### Key specs\n\nKey specs let you state which keys are mandatory or optional.\nThis works not only on Maps, but also on Keyword lists.\n\nKey specs are special, as they can only match on atoms, binaries, and numbers\n(and their combinations, via `and` and/or `or`).\n\nFor example, matching a Map for required and optional keywords might look like:\n\n```elixir\niex\u003e data = %{a: 1, b: 2, c: 3}\niex\u003e conform(keys(required: [:a], optional: [:c]), data)\n{:ok, %{a: 1, c: 3}}\n```\n\nNote that the conformed data does not include `:b`,\nas it was neither supplied in the `required:`\nnor the `optional:` combinations of keys.\n\nSimilarly, and just like in Maps, you can match on keys in a Keyword list:\n\n```elixir\niex\u003e data = [a: 1, c: 0, b: 2, c: 3]\niex\u003e conform(keys(required: [:d or :c]), data)\n{:ok, [c: 0, c: 3]}\n```\n\nThe `keys`/2 conformer will fail if a required key combination is missing:\n\n```elixir\niex\u003e data = %{a: 1, c: 3}\niex\u003e conform!(keys(required: [:d or (:a and :b)]), data)\n** (Spec.Mismatch) `%{a: 1, c: 3}` does not have any of keys `[:d, :b]`\n```\n\n### Regex Repetition Operators\n\nThe `cat` and `alt` specs are defined\nin terms of (previously seen) tagged and list specs,\nbut they are included in Spec for convenience.\n\n`cat` matches a keyword list of values.\nThis saves some keystrokes because you don't have to type `::` \nfor each element in the spec:\n\n```elixir\niex\u003e data = [3, \"firulais\"]\niex\u003e conform!(cat(age: is_integer(), name: is_binary()), data)\n[age: 3, name: \"firulais\"]\n```\n\nSimilarly, `alt` is sugar for tagged `or` specs.\n\n```elixir\niex\u003e data = \"HellBoy\"\niex\u003e conform!(alt(age: is_integer(), name: ~r/hell/i), data)\n[name: \"HellBoy\"]\n```\n\nFinally, Spec provides some repetition operators\nwhich take another spec as an argument\nand will check that all elements inside the collection conform to it. \nThese combinators (`zero_or_one`, `one_or_more`, `many`) work on tuples,\nor any other enumerable in Elixir, including lazy Streams.\nThe `many` combinator is the most interesting,\nbecause the other two are defined in terms of it.\n\n```elixir\niex\u003e data = [\"hola\", 1, \"mundo\", 2] |\u003e Stream.cycle\n\n# fails as soon as the first value from data does not conform\niex\u003e conform!(one_or_more(is_binary()), data)\n** (Spec.Mismatch) `1` does not satisfy predicate `is_binary()`\n```\n\n`many` can take `min:` (defaults to `0`) and `max:` (defaults to `nil`) options.\nAll of them can take a `fail_fast: false` option.\nIf you need to check exhaustively on all elements,\nnote that this is true by default:\nSpec prefers to fail fast on potentially large streams.\n\n```elixir\niex\u003e conform!(many(is_function(), fail_fast: false), [1, 2])\n** (Spec.Mismatch) `[1, 2]` items do not conform\n\n(failure 1)\n\n  `1` does not satisfy predicate `is_function()`\n\n(failure 2)\n\n  `2` does not satisfy predicate `is_function()`\n```\n\n`many` can also take an `as_stream: true` option;\nwhen enabled, it will conform to a new stream\nwhich in turn produces the result of conforming every item lazily:\n\n```elixir\n{:ok, stream} = conform(many(is_number(), as_stream: true), 0..2)\n[{:ok, 0}, {:ok, 1}, {:ok, 2}] = Enum.to_list(stream)\n```\n\n### Define Specs\n\nYou can also define specs on a module, giving them a name\nand having a easy way to be called and composed.\n\n```elixir\n# Remember, POEM stands for Plain Old Elixir Module\ndefmodule LovePOEM do \n  use Spec\n  \n  defspec lovers, do: {is_binary(), is_binary()}\n  \n  def send_love({from, to}) do\n    lovers!({foo, to}) # same as Spec.conform!(lovers(), {from, to})\n  end\nend\n```\n\nThe first advantage of using `defspec` is that it lets you name your specs.\nThe second is that you can use several generated functions: \n\n```elixir\n# The primary generated function takes its data as first argument,\n# so it's fully pipeable (and reusable in other specs):\nlovers(data) # =\u003e {:ok, ...} \n\n# There's a predicate version of it that returns a boolean:\nlovers?(data) # =\u003e true\n\n# And a bang (!) version that returns the conformed data or raises on error:\nlovers!({\"elixir\", \"erlang\"}) # =\u003e {\"elixir\", \"erlang\"}\nlovers!({22, 33}) # raises *Spec.Mismatch*\n```\n\nFor private specs, you can use `defspecp`.\nNote that this will only generate the `lovers?` and `lovers!` private functions\nif you give it an option like: `include: [:pred, :bang]`.\n\n\n### Parameterized Specs\n\nAs we have already seen, specs are just functions.\nThey take the data to validate as first argument,\nbut nothing restrains them from expecting more arguments.\n\nFor example, you could define a spec to conform Maps:\n\n```elixir\ndefmodule MapSpec do\n  use Spec\n\n  defspec map_of(key_spec, val_spec, options \\\\ []), \n  do: is_map() and many({key_spec, val_spec}, options)\n  \nend\n\n# validate that foo is a map of atoms to numbers with size between 2 and three\nfoo = %{a: 1, b: 2, c: 3}\nfoo |\u003e MapSpec.map_of!(\u0026is_atom/1, \u0026is_number/1, min: 2, max: 3)\n```\n\nNotice that this time we are using `MapSpec.map_of!/4` which takes the data to\nvalidate as first argument.\nOnce you define your specs, you can use them directly to conform data.\n\n### Function Specifications\n\nFunction specifications can be created by using `fspec/2`,\nwhich takes several options.\nThe only required option is `args: args_spec`;\nthis must be a spec to conform an array of arguments before applying the function. \n\n```elixir\ndata = {\u0026Kernel.+/2, [3, 4]}\n{:ok, 7} = conform(fspec(args: [is_integer(), is_integer()]), data)\n```\n\nAs you can see, the `fspec` data *must* be a tuple of the form `{function, arguments}`.\nIf all conforms are successful,\nit will conform to the value returned by the function.\nOtherwise, the first `{:error, mismatch}` to occur will be returned.\n\nThese are the options that `fspec` can take:\n\n* `args:` - a spec to conform a list of argument values\n* `ret:` - a spec to conform the function return value\n* `fn:` - a spec that takes a Keyword \n                `[args: conformed_args, ret: conformed_ret]`\n          If present, this will be used to conform the relation\n          between its arguments and return value.\n* `apply:` - nil by default. When given the `:conformed_args` atom, \n           the function will be applied to the *conformed_args*\n           that result from conforming with `args:` spec,\n           instead of the original args.\n* `return:` - nil by default. When given the `:conformed_ret` atom,\n           the return value will be *conformed_ret*,\n           that is the result of conforming the original value\n           returned by the function with the `ret:` spec.\n           When given the `:conformed_fn` atom, the return value\n           will be the result of conforming with the `fn:` spec.\n\nThe following example uses these options to specify a `rand_range` function\nwhose return value must be between the `initial` and `final` numbers.\n\n```elixir\ndefmodule RandSpec do\n\n  defspec rand_range, do:\n    fspec args: cat(a: is_integer(), b: is_integer()) and \u0026( \u00261[:a] \u003c \u00261[:b] ),\n          ret: is_integer(),\n          fn: \u0026( \u00261[:args][:a] \u003c= \u00261[:ret] and \u00261[:ret] \u003c \u00261[:args][:b] )\n\nend\n```\n\nDefining the previous function spec lets us conform any function with some\ncombination of arguments and see if they comply with the `rand_range` spec:\n\n```elixir\nfun = fn a, b -\u003e Range.new(a, b) |\u003e Enum.random end\n{:ok, 12} = RandSpec.rand_range({fun, [10, 20]})\n```\n\nRemember that bang versions either return a conformed value or raise a mismatch:\n\n```elixir\nfun = fn a, b -\u003e Range.new(a, b) |\u003e Enum.random end\n12 = RandSpec.rand_range!({fun, [10, 20]})\n```\n\n```elixir\n# should fail if second arg is lower than first\nRandSpec.rand_range!({fun, [10, 5]})\n** (Spec.Mismatch) `[a: 10, b: 5]` does not satisfy predicate `\"#Function\u003c9.33707904/1 in RandSpec.rand_range/0\u003e\"`\n```\n\n```elixir\n# fails for a function that misbehaves\nRandSpec.rand_range!({fn _, _ -\u003e \"boom\" end, [10, 20]})\n** (Spec.Mismatch) `\"boom\"` does not satisfy predicate `is_integer()`\n```\n\n### Define Conformed Functions\n\nOnce we know how to create function specifications,\nwe can learn to use the `@fspec` annotation to automatically instrument functions.\nThat is, they will be conformed when called.\n\n`@fspec` *must* be a function reference to a previously defined spec.\nFor example, we can use our `RandSpec.rand_range!/1`\n\n```elixir\ndefmodule RandomJoe do\n  use Spec\n\n  @fspec \u0026RandSpec.rand_range!/1\n  defconform foo(a, b) do\n    Range.new(a, b) |\u003e Enum.random\n  end\n  \n  @fspec \u0026RandSpec.rand_range/1\n  defconform bar(a, b) do\n    a + b\n  end\nend\n```\n\n*Important* we used the bang version when defining `foo/2`\nso that if any spec fails, the mismatch will be raised:\n\n```elixir\nRandomJoe.foo(1, :a)\n** (Spec.Mismatch) `[1, :a]` does not match all alternatives `cat(a: is_integer(), b: is_integer()) and \u0026(\u00261[:a] \u003c \u00261[:b])`\n```\n\nInstead, `bar` will return a mismatch if anything goes wrong\n(or {:ok, value} if all is fine):\n\n```elixir\nRandomJoe.bar(1, 3)\n{:error,\n %Spec.Mismatch{at: nil,\n  expr: \"#Function\u003c5.33707904/1 in RandSpec.rand_range/0\u003e\", in: nil,\n  reason: \"does not satisfy predicate\", subject: [args: [a: 1, b: 3], ret: 4]}}\n```\n\n### Instrumented def\n\nYou can automatically instrument your functions by explicitly using `Spec.Def`:\n\n```elixir\ndefmodule RandomJane do\n  use Spec.Def\n  \n  @doc \"Returns a random integer between lower and higher\"\n  @spec in_range(lower :: integer, higher :: integer) :: integer\n  @fspec \u0026RandSpec.rand_range!/1\n  def in_range(a, b) do\n    Range.new(a, b) |\u003e Enum.random\n  end\nend\n```\n\nThis way the changes in your source code are minimal.\nThe recommended practice is to create all your specs in a separate module\nand just reference them with `@fspec`.\n\n## Things to do\n\nYay, thanks for reading till this point; I hope you have found Spec interesting.\nIf you want to give back some love, it can come in many forms.\nFeedback and code are always appreciated;\nfeel free to open a new issue if you come up with something.\n\nHere's a short list you can help Spec to be more awesome, Thank you :heart:!\n\n- [x] Have lots of fun\n- [ ] Have *more* fun\n- [ ] API Docs\n- [ ] Improve readme, talk about all other Spec functions like valid? and friends.\n- [ ] Talk about unforming data (reverse of conforming)\n- [ ] Improve nested error reports\n- [ ] Implement `gen` and `exercise`.\n      Search on hex.pm for current packages that generate data and we can use\n- [ ] Use credo\n- [ ] Add typespecs :P\n\n\n[clojure.spec]: https://clojure.org/guides/spec\n[typespecs]: https://hexdocs.pm/elixir/typespecs.html\n[expat]: https://github.com/vic/expat\n[tests]: https://github.com/vic/spec/tree/master/test\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic%2Fspec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvic%2Fspec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic%2Fspec/lists"}