Skip to content

Commit

Permalink
doc: add the reflection guide
Browse files Browse the repository at this point in the history
  • Loading branch information
fahchen committed Jul 3, 2024
1 parent 204cb0a commit af05ae0
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 79 deletions.
1 change: 1 addition & 0 deletions guides/plugins/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ We provide some example plugins that you can use as a reference, or copy-paste.
**Plugin examples:**
- [Registering plugins globally](./registering_plugins_globally.md)
- [Implement `Access` behavior](./accessible.md)
- [Implement reflection functions](./reflection.md)
- [Type Only on Ecto Schema](./type_only_on_ecto_schema.md)
- [Add primary key and timestamps types to your Ecto schema](./primary_key_and_timestamps.md)
- [Derives the `Jason.Encoder` for `struct`](./derive_jason.md)
Expand Down
76 changes: 76 additions & 0 deletions guides/plugins/reflection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Implement reflection functions

Define a plugin that generates reflection functions that
can be used to access the fields and parameters of a struct.

## Implement

```elixir
defmodule Guides.Plugins.Reflection do
use TypedStructor.Plugin

@impl TypedStructor.Plugin
defmacro after_definition(definition, _opts) do
quote bind_quoted: [definition: definition] do
fields = Enum.map(definition.fields, &Keyword.fetch!(&1, :name))

enforced_fields =
definition.fields
|> Stream.filter(&Keyword.get(&1, :enforce, false))
|> Stream.map(&Keyword.fetch!(&1, :name))
|> Enum.to_list()

def __typed_structor__(:fields), do: unquote(fields)
def __typed_structor__(:parameters), do: unquote(definition.parameters)
def __typed_structor__(:enforced_fields), do: unquote(enforced_fields)

for field <- definition.fields do
name = Keyword.fetch!(field, :name)
type = field |> Keyword.fetch!(:type) |> Macro.escape()

def __typed_structor__(:type, unquote(name)), do: unquote(type)
def __typed_structor__(:field, unquote(name)), do: unquote(Macro.escape(field))
end
end
end
end
```

## Usage
```elixir
defmodule User do
use TypedStructor

typed_structor do
plugin Guides.Plugins.Reflection

parameter :age

field :name, String.t(), enforce: true
field :age, age, default: 20
end
end

defmodule MyApp do
use TypedStructor

typed_structor module: User, enforce: true do
plugin Guides.Plugins.Reflection

field :name, String.t()
field :age, integer()
end
end
```

```elixir
iex> User.__typed_structor__(:fields)
[:name, :age]
iex> User.__typed_structor__(:parameters)
[:age]

iex> MyApp.User.__typed_structor__(:enforced_fields)
[:name, :age]
iex> Macro.to_string(User.__typed_structor__(:type, :age))
"age"
```
26 changes: 0 additions & 26 deletions lib/typed_structor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ defmodule TypedStructor do

TypedStructor.__struct_ast__()
TypedStructor.__type_ast__()
TypedStructor.__reflection_ast__()

# create a lexical scope
try do
Expand Down Expand Up @@ -307,31 +306,6 @@ defmodule TypedStructor do
end
end

@doc false
defmacro __reflection_ast__ do
quote unquote: false do
fields = Enum.map(@__ts_definition__.fields, &Keyword.fetch!(&1, :name))

enforced_fields =
@__ts_definition__.fields
|> Stream.filter(&Keyword.get(&1, :enforce, false))
|> Stream.map(&Keyword.fetch!(&1, :name))
|> Enum.to_list()

def __typed_structor__(:fields), do: unquote(fields)
def __typed_structor__(:parameters), do: @__ts_definition__.parameters
def __typed_structor__(:enforced_fields), do: unquote(enforced_fields)

for field <- @__ts_definition__.fields do
name = Keyword.fetch!(field, :name)
type = field |> Keyword.fetch!(:type) |> Macro.escape()

def __typed_structor__(:type, unquote(name)), do: unquote(type)
def __typed_structor__(:field, unquote(name)), do: unquote(Macro.escape(field))
end
end
end

@doc false
defmacro __call_plugins_before_definitions__(definition) do
alias TypedStructor.Definition
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ defmodule TypedStructor.MixProject do
{"guides/plugins/introduction.md", [title: "Introduction"]},
"guides/plugins/registering_plugins_globally.md",
"guides/plugins/accessible.md",
"guides/plugins/reflection.md",
"guides/plugins/type_only_on_ecto_schema.md",
"guides/plugins/primary_key_and_timestamps.md",
"guides/plugins/derive_jason.md",
Expand Down
35 changes: 35 additions & 0 deletions test/guides/plugins/reflection_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule Guides.Plugins.ReflectionTest do
use TypedStructor.GuideCase,
async: true,
guide: "reflection.md"

test "generates reflection functions" do
assert [:name, :age] === User.__typed_structor__(:fields)
assert [:name, :age] === MyApp.User.__typed_structor__(:fields)

assert [:age] === User.__typed_structor__(:parameters)
assert [] === MyApp.User.__typed_structor__(:parameters)

assert [:name] === User.__typed_structor__(:enforced_fields)
assert [:name, :age] === MyApp.User.__typed_structor__(:enforced_fields)

assert "String.t()" === Macro.to_string(User.__typed_structor__(:type, :name))
assert "age" === Macro.to_string(User.__typed_structor__(:type, :age))

assert "String.t()" === Macro.to_string(MyApp.User.__typed_structor__(:type, :name))
assert "integer()" === Macro.to_string(MyApp.User.__typed_structor__(:type, :age))

assert [enforce: true, name: :name, type: type] = User.__typed_structor__(:field, :name)
assert "String.t()" === Macro.to_string(type)

assert [enforce: true, name: :age, type: type] =
MyApp.User.__typed_structor__(:field, :age)

assert "integer()" === Macro.to_string(type)

assert [enforce: true, name: :name, type: type] = User.__typed_structor__(:field, :name)
assert "String.t()" === Macro.to_string(type)
assert [default: 20, name: :age, type: type] = User.__typed_structor__(:field, :age)
assert "age" === Macro.to_string(type)
end
end
53 changes: 0 additions & 53 deletions test/reflection_test.exs

This file was deleted.

0 comments on commit af05ae0

Please sign in to comment.