{"id":13563814,"url":"https://github.com/ejpcmac/typed_struct","last_synced_at":"2025-08-01T10:16:19.914Z","repository":{"id":41055461,"uuid":"135536596","full_name":"ejpcmac/typed_struct","owner":"ejpcmac","description":"An Elixir library for defining structs with a type without writing boilerplate code.","archived":false,"fork":false,"pushed_at":"2023-12-06T20:44:00.000Z","size":253,"stargazers_count":753,"open_issues_count":14,"forks_count":36,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-07-21T12:32:56.353Z","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/ejpcmac.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","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":"2018-05-31T05:36:04.000Z","updated_at":"2025-07-08T17:17:09.000Z","dependencies_parsed_at":"2024-01-08T19:22:16.325Z","dependency_job_id":null,"html_url":"https://github.com/ejpcmac/typed_struct","commit_stats":{"total_commits":140,"total_committers":5,"mean_commits":28.0,"dds":0.08571428571428574,"last_synced_commit":"7c26d1654097476ebae9944ba6675a0f3fd21e9d"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/ejpcmac/typed_struct","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejpcmac%2Ftyped_struct","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejpcmac%2Ftyped_struct/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejpcmac%2Ftyped_struct/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejpcmac%2Ftyped_struct/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ejpcmac","download_url":"https://codeload.github.com/ejpcmac/typed_struct/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejpcmac%2Ftyped_struct/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268206109,"owners_count":24213001,"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","status":"online","status_checked_at":"2025-08-01T02:00:08.611Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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-01T13:01:23.560Z","updated_at":"2025-08-01T10:16:19.843Z","avatar_url":"https://github.com/ejpcmac.png","language":"Elixir","funding_links":[],"categories":["Macros","Elixir"],"sub_categories":[],"readme":"# TypedStruct\n\n[![Build Status](https://travis-ci.com/ejpcmac/typed_struct.svg?branch=develop)](https://travis-ci.com/ejpcmac/typed_struct)\n[![hex.pm version](https://img.shields.io/hexpm/v/typed_struct.svg?style=flat)](https://hex.pm/packages/typed_struct)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg?style=flat)](https://hexdocs.pm/typed_struct/)\n[![Total Download](https://img.shields.io/hexpm/dt/typed_struct.svg?style=flat)](https://hex.pm/packages/typed_struct)\n[![License](https://img.shields.io/hexpm/l/typed_struct.svg?style=flat)](https://github.com/ejpcmac/typed_struct/blob/master/LICENSE.md)\n\n\u003c!-- @moduledoc --\u003e\n\nTypedStruct is a library for defining structs with a type without writing\nboilerplate code.\n\n## Rationale\n\nTo define a struct in Elixir, you probably want to define three things:\n\n* the struct itself, with default values,\n* the list of enforced keys,\n* its associated type.\n\nIt ends up in something like this:\n\n```elixir\ndefmodule Person do\n  @moduledoc \"\"\"\n  A struct representing a person.\n  \"\"\"\n\n  @enforce_keys [:name]\n  defstruct name: nil,\n            age: nil,\n            happy?: true,\n            phone: nil\n\n  @typedoc \"A person\"\n  @type t() :: %__MODULE__{\n          name: String.t(),\n          age: non_neg_integer() | nil,\n          happy?: boolean(),\n          phone: String.t() | nil\n        }\nend\n```\n\nIn the example above you can notice several points:\n\n* the keys are present in both the `defstruct` and type definition,\n* enforced keys must also be written in `@enforce_keys`,\n* if a key has no default value and is not enforced, its type should be\n  nullable.\n\nIf you want to add a field in the struct, you must therefore:\n\n* add the key with its default value in the `defstruct` list,\n* add the key with its type in the type definition.\n\nIf the field is not optional, you should even add it to `@enforce_keys`. This is\nway too much work for lazy people like me, and moreover it can be error-prone.\n\nIt would be way better if we could write something like this:\n\n```elixir\ndefmodule Person do\n  @moduledoc \"\"\"\n  A struct representing a person.\n  \"\"\"\n\n  use TypedStruct\n\n  typedstruct do\n    @typedoc \"A person\"\n\n    field :name, String.t(), enforce: true\n    field :age, non_neg_integer()\n    field :happy?, boolean(), default: true\n    field :phone, String.t()\n  end\nend\n```\n\nThanks to TypedStruct, this is now possible :)\n\n## Usage\n\n### Setup\n\nTo use TypedStruct in your project, add this to your Mix dependencies:\n\n```elixir\n{:typed_struct, \"~\u003e 0.3.0\"}\n```\n\nIf you do not plan to compile modules using TypedStruct at runtime, you can add\n`runtime: false` to the dependency tuple as TypedStruct is only used at build\ntime.\n\nIf you want to avoid `mix format` putting parentheses on field definitions,\nyou can add to your `.formatter.exs`:\n\n```elixir\n[\n  ...,\n  import_deps: [:typed_struct]\n]\n```\n\n### General usage\n\nTo define a typed struct, use\n[`TypedStruct`](https://hexdocs.pm/typed_struct/TypedStruct.html), then define\nyour struct within a `typedstruct` block:\n\n```elixir\ndefmodule MyStruct do\n  # Use TypedStruct to import the typedstruct macro.\n  use TypedStruct\n\n  # Define your struct.\n  typedstruct do\n    # Define each field with the field macro.\n    field :a_string, String.t()\n\n    # You can set a default value.\n    field :string_with_default, String.t(), default: \"default\"\n\n    # You can enforce a field.\n    field :enforced_field, integer(), enforce: true\n  end\nend\n```\n\nEach field is defined through the\n[`field/2`](https://hexdocs.pm/typed_struct/TypedStruct.html#field/2) macro.\n\n### Options\n\nIf you want to enforce all the keys by default, you can do:\n\n```elixir\ndefmodule MyStruct do\n  use TypedStruct\n\n  # Enforce keys by default.\n  typedstruct enforce: true do\n    # This key is enforced.\n    field :enforced_by_default, term()\n\n    # You can override the default behaviour.\n    field :not_enforced, term(), enforce: false\n\n    # A key with a default value is not enforced.\n    field :not_enforced_either, integer(), default: 1\n  end\nend\n```\n\nYou can also generate an opaque type for the struct:\n\n```elixir\ndefmodule MyOpaqueStruct do\n  use TypedStruct\n\n  # Generate an opaque type for the struct.\n  typedstruct opaque: true do\n    field :name, String.t()\n  end\nend\n```\n\nIf you often define submodules containing only a struct, you can avoid\nboilerplate code:\n\n```elixir\ndefmodule MyModule do\n  use TypedStruct\n\n  # You now have %MyModule.Struct{}.\n  typedstruct module: Struct do\n    field :field, term()\n  end\nend\n```\n\n### Documentation\n\nTo add a `@typedoc` to the struct type, just add the attribute in the\n`typedstruct` block:\n\n```elixir\ntypedstruct do\n  @typedoc \"A typed struct\"\n\n  field :a_string, String.t()\n  field :an_int, integer()\nend\n```\n\nYou can also document submodules this way:\n\n```elixir\ntypedstruct module: MyStruct do\n  @moduledoc \"A submodule with a typed struct.\"\n  @typedoc \"A typed struct in a submodule\"\n\n  field :a_string, String.t()\n  field :an_int, integer()\nend\n```\n\n## Plugins\n\nIt is possible to extend the scope of TypedStruct by using its plugin interface,\nas described in\n[`TypedStruct.Plugin`](https://hexdocs.pm/typed_struct/TypedStruct.Plugin.html).\nFor instance, to automatically generate lenses with the\n[Lens](https://github.com/obrok/lens) library, you can use\n[`TypedStructLens`](https://github.com/ejpcmac/typed_struct_lens) and do:\n\n```elixir\ndefmodule MyStruct do\n  use TypedStruct\n\n  typedstruct do\n    plugin TypedStructLens\n\n    field :a_field, String.t()\n    field :other_field, atom()\n  end\n\n  @spec change(t()) :: t()\n  def change(data) do\n    # a_field/0 is generated by TypedStructLens.\n    lens = a_field()\n    put_in(data, [lens], \"Changed\")\n  end\nend\n```\n\n### Some available plugins\n\n* [`typed_struct_lens`](https://github.com/ejpcmac/typed_struct_lens) –\n    Integration with the [Lens](https://github.com/obrok/lens) library.\n* [`typed_struct_legacy_reflection`](https://github.com/ejpcmac/typed_struct_legacy_reflection)\n  – Re-enables the legacy reflection functions from TypedStruct 0.1.x.\n\nThis list is not meant to be exhaustive, please [search for “typed_struct” on\nhex.pm](https://hex.pm/packages?search=typed_struct) for other results. If you\nwant your plugin to appear here, please open an issue.\n\n## What do I get?\n\nWhen defining an empty `typedstruct` block:\n\n```elixir\ndefmodule Example do\n  use TypedStruct\n\n  typedstruct do\n  end\nend\n```\n\nyou get an empty struct with its module type `t()`:\n\n```elixir\ndefmodule Example do\n  @enforce_keys []\n  defstruct []\n\n  @type t() :: %__MODULE__{}\nend\n```\n\nEach `field` call adds information to the struct, `@enforce_keys` and the type\n`t()`.\n\nA field with no options adds the name to the `defstruct` list, with `nil` as\ndefault. The type itself is made nullable:\n\n```elixir\ndefmodule Example do\n  use TypedStruct\n\n  typedstruct do\n    field :name, String.t()\n  end\nend\n```\n\nbecomes:\n\n```elixir\ndefmodule Example do\n  @enforce_keys []\n  defstruct name: nil\n\n  @type t() :: %__MODULE__{\n          name: String.t() | nil\n        }\nend\n```\n\nThe `default` option adds the default value to the `defstruct`:\n\n```elixir\nfield :name, String.t(), default: \"John Smith\"\n\n# Becomes\ndefstruct name: \"John Smith\"\n```\n\nWhen set to `true`, the `enforce` option enforces the key by adding it to the\n`@enforce_keys` attribute.\n\n```elixir\nfield :name, String.t(), enforce: true\n\n# Becomes\n@enforce_keys [:name]\ndefstruct name: nil\n```\n\nIn both cases, the type has no reason to be nullable anymore by default. In one\ncase the field is filled with its default value and not `nil`, and in the other\ncase it is enforced. Both options would generate the following type:\n\n```elixir\n@type t() :: %__MODULE__{\n        name: String.t() # Not nullable\n      }\n```\n\nPassing `opaque: true` replaces `@type` with `@opaque` in the struct type\nspecification:\n\n```elixir\ntypedstruct opaque: true do\n  field :name, String.t()\nend\n```\n\ngenerates the following type:\n\n```elixir\n@opaque t() :: %__MODULE__{\n          name: String.t()\n        }\n```\n\nWhen passing `module: ModuleName`, the whole `typedstruct` block is wrapped in a\nmodule definition. This way, the following definition:\n\n```elixir\ndefmodule MyModule do\n  use TypedStruct\n\n  typedstruct module: Struct do\n    field :field, term()\n  end\nend\n```\n\nbecomes:\n\n```elixir\ndefmodule MyModule do\n  defmodule Struct do\n    @enforce_keys []\n    defstruct field: nil\n\n    @type t() :: %__MODULE__{\n            field: term() | nil\n          }\n  end\nend\n```\n\n\u003c!-- @moduledoc --\u003e\n\n## Initial roadmap\n\n* [x] Struct definition\n* [x] Type definition (with nullable types)\n* [x] Default values\n* [x] Enforced keys (non-nullable types)\n* [x] Plugin API\n\n## Plugin ideas\n\n* [ ] Default value type-checking (is it possible?)\n* [ ] Guard generation\n* [x] Integration with [Lens](https://github.com/obrok/lens)\n* [ ] Integration with [Ecto](https://github.com/elixir-ecto/ecto)\n\n## Related libraries\n\n* [Domo](https://github.com/IvanRublev/Domo): a library to validate structs that\n    define a `t()` type, like the one generated by `TypedStruct`.\n* [TypedEctoSchema](https://github.com/bamorim/typed_ecto_schema): a library\n    that provides a DSL on top of `Ecto.Schema` to achieve the same result as\n    `TypedStruct`, with `Ecto`.\n\n## [Contributing](CONTRIBUTING.md)\n\nBefore contributing to this project, please read the\n[CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nCopyright © 2018-2022 Jean-Philippe Cugnet and Contributors\n\nThis project is licensed under the [MIT license](./LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fejpcmac%2Ftyped_struct","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fejpcmac%2Ftyped_struct","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fejpcmac%2Ftyped_struct/lists"}