Current section
Files
Jump to
Current section
Files
lib/graph_api/view.ex
defmodule GraphApi.View do
@moduledoc """
Macro for defining view structs that project a subset of fields from a schema module.
Views provide IDE autocomplete, automatic `$select` injection, and compile-time
field validation against the parent schema.
## Usage
defmodule MyApp.UserSummary do
use GraphApi.View,
schema: GraphApi.Schema.User,
fields: [:id, :display_name, :mail, :job_title]
end
This generates:
- `defstruct` with only the listed fields
- `from_map/1` — casts via parent schema's `from_map/1`, then takes only view fields
- `to_map/1` — converts back to camelCase via parent schema
- `__select__/0` — returns camelCase field names for auto `$select` injection
- `__schema__/0` — returns the parent schema module
"""
defmacro __using__(opts) do
schema = Keyword.fetch!(opts, :schema)
fields = Keyword.fetch!(opts, :fields)
quote do
@schema unquote(schema)
@view_fields unquote(fields)
# Compile-time validation: ensure all view fields exist on the schema
schema_fields = @schema.__struct__() |> Map.keys() |> MapSet.new()
Enum.each(@view_fields, fn field ->
unless MapSet.member?(schema_fields, field) do
raise CompileError,
description:
"field #{inspect(field)} does not exist on schema #{inspect(@schema)}. " <>
"Available fields: #{inspect(MapSet.to_list(schema_fields) -- [:__struct__])}"
end
end)
defstruct @view_fields
@type t :: %__MODULE__{}
# Build the select list at compile time
@select_fields (
field_mapping = @schema.__field_mapping__()
reverse = Map.new(field_mapping, fn {k, v} -> {v, k} end)
Enum.map(@view_fields, fn field ->
Map.fetch!(reverse, field)
end)
)
@doc "Returns the list of camelCase field names for OData `$select`."
@spec __select__() :: [String.t()]
def __select__, do: @select_fields
@doc "Returns the parent schema module."
@spec __schema__() :: module()
def __schema__, do: @schema
@doc "Converts a camelCase string-key map into this view struct."
@spec from_map(map()) :: t()
def from_map(map) when is_map(map) do
full = @schema.from_map(map)
struct(__MODULE__, Map.take(Map.from_struct(full), @view_fields))
end
@doc "Converts this view struct to a camelCase string-key map. Nil fields are omitted."
@spec to_map(t()) :: map()
def to_map(%__MODULE__{} = view) do
view_map = Map.from_struct(view)
full = struct(@schema, view_map)
@schema.to_map(full)
end
end
end
end