{"id":28417010,"url":"https://github.com/sgilson/mine","last_synced_at":"2025-08-08T04:05:28.281Z","repository":{"id":57526650,"uuid":"221643696","full_name":"sgilson/mine","owner":"sgilson","description":"A minimalistic anti-corruption layer for Elixir structs","archived":false,"fork":false,"pushed_at":"2021-10-02T06:19:40.000Z","size":104,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-04T08:23:55.251Z","etag":null,"topics":["anti-corruption-layer","elixir","elixir-lang","elixir-library","macros","view"],"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/sgilson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-14T08:05:55.000Z","updated_at":"2025-02-12T12:33:14.000Z","dependencies_parsed_at":"2022-09-07T02:50:42.137Z","dependency_job_id":null,"html_url":"https://github.com/sgilson/mine","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sgilson/mine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgilson%2Fmine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgilson%2Fmine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgilson%2Fmine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgilson%2Fmine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sgilson","download_url":"https://codeload.github.com/sgilson/mine/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgilson%2Fmine/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261891673,"owners_count":23225772,"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":["anti-corruption-layer","elixir","elixir-lang","elixir-library","macros","view"],"created_at":"2025-06-04T02:12:44.673Z","updated_at":"2025-06-25T14:31:10.465Z","avatar_url":"https://github.com/sgilson.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mine\n\n[![Hex Version](https://img.shields.io/hexpm/v/mine.svg)](https://hex.pm/packages/mine) \n[![Documentation](https://img.shields.io/badge/docs-hexpm-blue.svg)](https://hexdocs.pm/mine/) \n[![Build Status Travis](https://travis-ci.com/sgilson/mine.svg?branch=master)](https://travis-ci.com/sgilson/mine) \n[![Coverage Status](https://coveralls.io/repos/github/sgilson/mine/badge.svg?branch=master)](https://coveralls.io/github/sgilson/mine?branch=master)\n\nMine is a lightweight package for defining views for structs. It\naims to eliminate the boilerplate required to write and maintain an \nanti-corruption layer between your structs and any external APIs you\ninterface with.\n\n**Note:** Mine currently exports the minimum functionality required for my own use case \nbut is not ready for adoption yet. Until v1.0.0, the interface may change in ways that break \nbackwards compatibility.\n\n## Usage\n\n### Basic Views\n\n```elixir\ndefmodule User do\n  use Mine\n\n  # module must declare a struct\n  defstruct [:name, :email, :password]\n  \n  defview do\n    field :name, as: \"userName\", default: \"\"\n    ignore :password\n  end\nend\n```\n\nA view is created by importing `Mine` at the beginning of your module and using\nthe exported `defview/2` macro. Note that `User` declares a struct and the `defview`\nis used after the declaration. This requirement stems from the intent of `Mine`\nto be purely additive. It will not replace functionality provided by more mature\nmodules but rather build on existing code. The most notable benefit to this approach\nis that other packages that internally define structs (like Ecto) can be extended.\n\nAt compile time, `Mine` will create views for every module in use. Any\nincompatibilities between the declared views and their base structs will be caught here,\nproviding the benefit of runtime safety. To access the compiled view at runtime,\nthe following methods will be found on your module.\n\n- `to_view/2`: Covert struct to a map for a given view\n   ```elixir\n   User.to_view(%User{name: \"Bob\", email: \"abc@d.com\", password: \"secret\"}, :default)\n   # %{\"userName\" =\u003e \"Bob\", \"email\" =\u003e \"abc@d.com\"}\n   ```\n- `from_view/2`: Convert source map to struct\n  ```elixir\n  User.from_view(%{\"email\" =\u003e \"abc@d.com\", \"password\" =\u003e \"shouldn't be here\"}, :default)\n  # %User{name: \"\", email: \"abc@d.com\", password: nil}\n  ```\n\nThe first argument for both of these functions is the data to translate. The second is the name of the view to use \nfor this translation. A module using Mine can have multiple views to capture the formatting requirements of \ndifferent domains. In this case, `:default` was not needed since the second parameter defaults to this value \nunless configured otherwise.\n\nThese functions should ideally be used between your serialization layer and business logic. For example,\nafter retrieving data from a third party API, your application would deserialiaze the data using \n[devinus/poison](https://github.com/devinus/poison) or [michalmuskala/jason](https://github.com/michalmuskala/jason),\npass the resulting map through Mine, and then validate the results using an \n[Ecto changeset](https://hexdocs.pm/ecto/Ecto.Changeset.html#content) before persisting the data.\n\nMore info on these functions can be found on HexDocs.\n\n### Available Macros\n\nThere are several macros that can be used inside the scope of `defview`. They are:\n\n- `field/2`: Change the key for a given field. Has the following uses:\n     - `field(:key, \"as\")` View will use `\"as\"` instead of the field name `:key`\n     - `field(:key, default: \"def\")` Same as above, but if `:key` is\n     not found or it's value is `nil`, `\"def\"` will be used as the value instead.\n     - `field(:key, map_to: \u0026String.upcase/1)` When using `to_view`,\n     value of field in struct will be accessed, mapped using `map_to`, and stored \n     under the key `\"as\"` in the resulting map\n     - `field(:key, map_from: \u0026String.downcase/1)` When using \n     `from_view`, value of `\"as\"` in the given map will be fetched and passed\n     as the only argument to the function in `map_from`.\n- `ignore/1`: Field will be ignored in both `to_view` and from `from_view`.\n- `append/2`: A key value pair that will be added to any map produced by `to_view`.\nIgnored in `from_view`.\n\n### Named Views\n\nIn some cases, it may not be enough to have a single exported view of a struct.\nTo support this use case, every view created by `Mine` has a name. `:default` will\nbe used when a name is not provided.\n\n`default_view/1` changes which view is used for the single arity versions of \n`to_view` and `from_view`.\n\nA more complicated `User` module may look something like this:\n\n```elixir\n...\n\n# to_veiw/1 and from_view/1 will use this view\n# instead of :default\ndefault_view :front_end\n\n# other fields will remain untouched\ndefview :third_party_api do\n  field :name, as: \"userName\"\nend\n\ndefview :front_end do\n  field :name, default: \"?\"\n  field :email, default: \"unknown\"\n  ignore :password\nend\n\n# alternatively, use the @default_view annotation\n@default_view true\ndefview :front_end do\n  # ...\nend\n\n...\n```\n\nAfter generation, the module will have function definitions similar to the following:\n\n```elixir\ndef to_view(%User{}, view \\\\ :front_end)\ndef to_view(%User{}, :front_end)\ndef to_view(%User{}, :third_party_api)\n\n# plus corresponding from_view functions\n```\n\nWith this layout, you can let pattern matching determine which view to use.\n\n## Key Naming Strategies\n\nA common requirement for views is to translate struct keys to another naming  \nconvention, as snake case is not always the norm. To support this use case,\na view can be annotated with `@naming_strategy` and by default, the generated\naliases will use the new naming convention.\n\n```elixir\ndefstruct [:field_one, :field_two]\n\n@naming_strategy :camel\ndefview do\n  field :field_one, as: \"something\" # explicit field aliases take precedence\nend\n```\n\nAvailable naming strategies are: `:camel`, `:constant`, `:dot`, `:kebab`, `:pascal`, and `:path`.\n  \nRefer to [Recase](https://hexdocs.pm/recase) for details.\n\n## Rationale\n\nWhile interfacing with an external API written in Java, I frequently ran across \ninstances in which I would need to map a struct's key, ignore certain fields, \nor add constant fields to outgoing requests. The process of setting up these mappings\nis tiresome and potentially error prone.\n\nLet's take a small, real-world example. This is the required JSON format\nfor a port in an unnamed API:\n\n```json\n{\n  \"$\": 7000,\n  \"@enabled\": false\n}\n```\nFormats such as this were scattered throughout the API, leading to several\nmodules with structures similar to the following:\n\n```elixir\ndefmodule Port do\n  defstruct [:num, :enabled]\n\n  def to_view(%Port{num: num, enabled: enabled}) do\n    %{\n      \"$\" =\u003e num,\n      \"@enabled\" =\u003e enabled\n    }\n  end\n\n  def from_view(%{\"$\" =\u003e num, \"@enabled\" =\u003e enabled}) do\n    %Port{num: num, enabled: enabled}\n  end\nend\n```\n\nCode like this:\n\n- is necessary to maintain a clean internal structure\n- adds more noise than meaning\n- can be error prone\n\nGiven these conditions, I opted to break my first rule of writing macros in Elixir\n(avoid them). Instead of the example seen above, using `mine` allows for a much more\nconcise representation of a mapping to the external world.\n\n```elixir\ndefmodule Port do\n  use Mine\n  defstruct [:num, :enabled]\n\n  defview do\n    field :num, \"$\"\n    field :enabled, \"@enabled\"\n  end\nend\n```\n\nThis representation is easier to read, maintain, and is even checked for validity \nat compile time. In addition, the generated code is nearly identical to the \ncode written by hand, resulting in a minimal performance impact.\n\n## Installation\n\nMine can be installed by adding `mine` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:mine, \"~\u003e 0.3.4\"} # check Hex for the most recent version\n  ]\nend\n```\n\n## Benchmarking\n\nTo benchmark the generated functions against those written by hand, run the \nfollowing:\n\n```shell script\nMIX_ENV=bench mix run bench/run.exs\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsgilson%2Fmine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsgilson%2Fmine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsgilson%2Fmine/lists"}