Skip to content

Commit fe092c2

Browse files
committed
#51 part 2 — mix gen.api_docs task + generated _api fragments
* new Mix.Tasks.Gen.ApiDocs walks each domain's resource_references and emits a markdown table per resource — function, action, args, purpose * filters behaviour-injected :specified_by/:features/:characteristics * type labels prettified (Ash.Type.Struct → struct, etc.) * SPDX headers baked into the generated template (REUSE-compliant across regenerations) * generated documentation/domains/_access_api.md (5 resources) * generated documentation/domains/_nbn_api.md (8 resources)
1 parent d2f0347 commit fe092c2

5 files changed

Lines changed: 334 additions & 10 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 diffo_example contributors <https://github.com/diffo-dev/diffo_example/graphs.contributors>
3+
4+
SPDX-License-Identifier: MIT
5+
6+
Auto-generated by `mix gen.api_docs`. Do not edit by hand.
7+
Regenerate after changing any domain's `code_interface` defines.
8+
-->
9+
10+
# Access Domain API
11+
12+
The Elixir function-call surface for each resource in the `DiffoExample.Access` domain. Generated from the `define` declarations in the domain's `resources do` block.
13+
14+
## Cable
15+
16+
| Function | Action | Arguments | Purpose |
17+
|---|---|---|---|
18+
| `assign_pair` | `:assign_pair` | `assignment` (struct) | relates the cable with an instance by assigning a pair |
19+
| `build_cable` | `:build` | `id`, `name`, `type`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new Cable resource instance for build |
20+
| `define_cable` | `:define` | `characteristic_value_updates` (list of term) | defines the cable |
21+
| `get_cable_by_id` | `:read` | `id` | read a service or resource instance |
22+
| `relate_cable` | `:relate` | `relationships` (list of struct) | relates the cable with other instances |
23+
24+
## Card
25+
26+
| Function | Action | Arguments | Purpose |
27+
|---|---|---|---|
28+
| `assign_port` | `:assign_port` | `assignment` (struct) | relates the card with an instance by assigning a port |
29+
| `build_card` | `:build` | `id`, `name`, `type`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new Card resource instance for build |
30+
| `define_card` | `:define` | `characteristic_value_updates` (list of term) | defines the card |
31+
| `get_card_by_id` | `:read` | `id` | read a service or resource instance |
32+
| `relate_card` | `:relate` | `relationships` (list of struct) | relates the card with other instances |
33+
34+
## DslAccess
35+
36+
| Function | Action | Arguments | Purpose |
37+
|---|---|---|---|
38+
| `design_dsl_result` | `:design_result` | `characteristic_value_updates` (list of term) | updates the DSL Access service with the design |
39+
| `get_dsl_by_id` | `:read` | `id` | read a service or resource instance |
40+
| `qualify_dsl` | `:qualify` | `id`, `name`, `type`, `which`, `places` (list of struct), `parties` (list of struct) | creates a new DSL Access service instance for qualification |
41+
| `qualify_dsl_result` | `:qualify_result` | `service_operating_status`, `places` (list of struct) | updates the DSL Access service with qualification result |
42+
43+
## Path
44+
45+
| Function | Action | Arguments | Purpose |
46+
|---|---|---|---|
47+
| `build_path` | `:build` | `id`, `name`, `type`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new Path resource instance for build |
48+
| `define_path` | `:define` | `characteristic_value_updates` (list of term) | defines the path |
49+
| `get_path_by_id` | `:read` | `id` | read a service or resource instance |
50+
| `relate_path` | `:relate` | `relationships` (list of struct) | relates the path with other instances |
51+
52+
## Shelf
53+
54+
| Function | Action | Arguments | Purpose |
55+
|---|---|---|---|
56+
| `assign_slot` | `:assign_slot` | `assignment` (struct) | relates the shelf with an instance by assigning a slot |
57+
| `build_shelf` | `:build` | `id`, `name`, `type`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new Shelf resource instance for build |
58+
| `define_shelf` | `:define` | `characteristic_value_updates` (list of term) | defines the shelf |
59+
| `get_shelf_by_id` | `:read` | `id` | read a service or resource instance |
60+
| `relate_shelf` | `:relate` | `relationships` (list of struct) | relates the shelf with cards |

documentation/domains/_nbn_api.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 diffo_example contributors <https://github.com/diffo-dev/diffo_example/graphs.contributors>
3+
4+
SPDX-License-Identifier: MIT
5+
6+
Auto-generated by `mix gen.api_docs`. Do not edit by hand.
7+
Regenerate after changing any domain's `code_interface` defines.
8+
-->
9+
10+
# NBN Domain API
11+
12+
The Elixir function-call surface for each resource in the `DiffoExample.Nbn` domain. Generated from the `define` declarations in the domain's `resources do` block.
13+
14+
## Avc
15+
16+
| Function | Action | Arguments | Purpose |
17+
|---|---|---|---|
18+
| `build_avc` | `:build` | `id`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new AVC resource instance |
19+
| `define_avc` | `:define` | `characteristic_value_updates` (list of term) | defines the AVC |
20+
| `get_avc_by_id` | `:read` | `id` | read a service or resource instance |
21+
| `relate_avc` | `:relate` | `relationships` (list of struct) | relates the AVC with other instances |
22+
23+
## Cvc
24+
25+
| Function | Action | Arguments | Purpose |
26+
|---|---|---|---|
27+
| `assign_cvlan` | `:assign_cvlan` | `assignment` (struct) | assigns a C-VLAN ID from the CVC pool to an AVC |
28+
| `build_cvc` | `:build` | `id`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new CVC resource instance |
29+
| `define_cvc` | `:define` | `characteristic_value_updates` (list of term) | defines the CVC |
30+
| `get_cvc_by_id` | `:read` | `id` | read a service or resource instance |
31+
| `relate_cvc` | `:relate` | `relationships` (list of struct) | relates the CVC with other instances (e.g. AVC aggregation, NNI Group termination) |
32+
33+
## NbnEthernet
34+
35+
| Function | Action | Arguments | Purpose |
36+
|---|---|---|---|
37+
| `build_nbn_ethernet` | `:build` | `id`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new NBN Ethernet access resource instance |
38+
| `define_nbn_ethernet` | `:define` | `characteristic_value_updates` (list of term) | defines the NBN Ethernet access |
39+
| `get_nbn_ethernet_by_id` | `:read` | `id` | read a service or resource instance |
40+
| `relate_nbn_ethernet` | `:relate` | `relationships` (list of struct) | relates the NBN Ethernet access with other instances (e.g. UNI) |
41+
42+
## Nni
43+
44+
| Function | Action | Arguments | Purpose |
45+
|---|---|---|---|
46+
| `build_nni` | `:build` | `id`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new NNI resource instance |
47+
| `define_nni` | `:define` | `characteristic_value_updates` (list of term) | defines the NNI |
48+
| `get_nni_by_id` | `:read` | `id` | read a service or resource instance |
49+
| `relate_nni` | `:relate` | `relationships` (list of struct) | relates the NNI with other instances (e.g. its parent NNI Group) |
50+
51+
## NniGroup
52+
53+
| Function | Action | Arguments | Purpose |
54+
|---|---|---|---|
55+
| `assign_svlan` | `:assign_svlan` | `assignment` (struct) | assigns an S-VLAN ID from the NNI Group pool to a CVC |
56+
| `build_nni_group` | `:build` | `id`, `name`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new NNI Group resource instance |
57+
| `define_nni_group` | `:define` | `characteristic_value_updates` (list of term) | defines the NNI Group |
58+
| `get_nni_group_by_id` | `:read` | `id` | read a service or resource instance |
59+
| `relate_nni_group` | `:relate` | `relationships` (list of struct) | relates the NNI Group with other instances (e.g. NNI resources it comprises) |
60+
61+
## Ntd
62+
63+
| Function | Action | Arguments | Purpose |
64+
|---|---|---|---|
65+
| `assign_port` | `:assign_port` | `assignment` (struct) | assigns a port from the NTD pool to a UNI |
66+
| `build_ntd` | `:build` | `id`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new NTD resource instance |
67+
| `define_ntd` | `:define` | `characteristic_value_updates` (list of term) | defines the NTD |
68+
| `get_ntd_by_id` | `:read` | `id` | read a service or resource instance |
69+
| `relate_ntd` | `:relate` | `relationships` (list of struct) | relates the NTD with other instances (e.g. UNI) |
70+
71+
## Rsp
72+
73+
| Function | Action | Arguments | Purpose |
74+
|---|---|---|---|
75+
| `activate_rsp` | `:activate` |||
76+
| `create_rsp` | `:build` | `name`, `short_name`, `id` ||
77+
| `deactivate_rsp` | `:deactivate` |||
78+
| `get_rsp_by_epid` | `:read` | `id` ||
79+
| `get_rsp_by_short_name` | `:read` | `short_name` ||
80+
| `list_rsps` | `:inventory` |||
81+
| `suspend_rsp` | `:suspend` |||
82+
83+
## Uni
84+
85+
| Function | Action | Arguments | Purpose |
86+
|---|---|---|---|
87+
| `build_uni` | `:build` | `id`, `which`, `relationships` (list of struct), `places` (list of struct), `parties` (list of struct) | creates a new UNI resource instance |
88+
| `define_uni` | `:define` | `characteristic_value_updates` (list of term) | defines the UNI |
89+
| `get_uni_by_id` | `:read` | `id` | read a service or resource instance |
90+
| `relate_uni` | `:relate` | `relationships` (list of struct) | relates the UNI with other instances (e.g. NTD, NBN Ethernet access) |

lib/mix/tasks/gen.api_docs.ex

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# SPDX-FileCopyrightText: 2025 diffo_example contributors <https://github.com/diffo-dev/diffo_example/graphs.contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule Mix.Tasks.Gen.ApiDocs do
6+
@shortdoc "Generates per-domain API markdown fragments from code_interface defines"
7+
@moduledoc """
8+
Walks each configured domain's `code_interface` defines and writes a
9+
markdown table fragment for inclusion in the domain doc pages.
10+
11+
Each fragment lists the resource sections, with one row per `define`
12+
showing the generated Elixir function, the underlying action, the
13+
meaningful arguments, and the action's purpose (its `description:`).
14+
15+
## Usage
16+
17+
mix gen.api_docs
18+
19+
Writes to:
20+
21+
- `documentation/domains/_access_api.md`
22+
- `documentation/domains/_nbn_api.md`
23+
24+
These fragments are intended to be referenced (e.g. via `!include`
25+
pseudo-markers or just kept open in the editor alongside the narrative
26+
page). They're regenerated on demand so they don't drift from the
27+
code-interface declarations.
28+
"""
29+
30+
use Mix.Task
31+
32+
@doc_root "documentation/domains"
33+
34+
@domains [
35+
{DiffoExample.Access, "_access_api.md", "Access"},
36+
{DiffoExample.Nbn, "_nbn_api.md", "NBN"}
37+
]
38+
39+
@autogen_banner """
40+
<!--
41+
SPDX-FileCopyrightText: 2025 diffo_example contributors <https://github.com/diffo-dev/diffo_example/graphs.contributors>
42+
43+
SPDX-License-Identifier: MIT
44+
45+
Auto-generated by `mix gen.api_docs`. Do not edit by hand.
46+
Regenerate after changing any domain's `code_interface` defines.
47+
-->
48+
"""
49+
50+
@impl Mix.Task
51+
def run(_args) do
52+
Mix.Task.run("compile", [])
53+
54+
File.mkdir_p!(@doc_root)
55+
56+
Enum.each(@domains, fn {domain, file, title} ->
57+
path = Path.join(@doc_root, file)
58+
content = render_domain(domain, title)
59+
File.write!(path, content)
60+
Mix.shell().info("wrote #{path}")
61+
end)
62+
end
63+
64+
defp render_domain(domain, title) do
65+
refs =
66+
domain
67+
|> Ash.Domain.Info.resource_references()
68+
|> Enum.sort_by(&short_name(&1.resource))
69+
70+
body =
71+
refs
72+
|> Enum.map(&render_resource/1)
73+
|> Enum.reject(&(&1 == :empty))
74+
|> Enum.join("\n\n")
75+
76+
[
77+
@autogen_banner,
78+
"\n",
79+
"# #{title} Domain API\n",
80+
"\n",
81+
"The Elixir function-call surface for each resource in the `#{inspect(domain)}` domain. ",
82+
"Generated from the `define` declarations in the domain's `resources do` block.\n",
83+
"\n",
84+
body,
85+
"\n"
86+
]
87+
|> IO.iodata_to_binary()
88+
end
89+
90+
defp render_resource(ref) do
91+
case ref.definitions do
92+
[] ->
93+
:empty
94+
95+
defs ->
96+
sorted = Enum.sort_by(defs, & &1.name)
97+
98+
rows =
99+
sorted
100+
|> Enum.map(&render_row(&1, ref.resource))
101+
|> Enum.join("\n")
102+
103+
"""
104+
## #{short_name(ref.resource)}
105+
106+
| Function | Action | Arguments | Purpose |
107+
|---|---|---|---|
108+
#{rows}
109+
"""
110+
|> String.trim_trailing()
111+
end
112+
end
113+
114+
defp render_row(interface, resource) do
115+
action_name = interface.action || interface.name
116+
action = Ash.Resource.Info.action(resource, action_name)
117+
118+
fn_cell = "`#{interface.name}`"
119+
action_cell = "`:#{action_name}`"
120+
args_cell = render_args(interface, action)
121+
purpose_cell = render_purpose(action)
122+
123+
"| #{fn_cell} | #{action_cell} | #{args_cell} | #{purpose_cell} |"
124+
end
125+
126+
# Auto-injected by Diffo's `behaviour do create :build end` fragment —
127+
# not part of the user-facing API surface.
128+
@injected_build_args [:specified_by, :features, :characteristics]
129+
130+
defp render_args(interface, action) do
131+
get_by_arg =
132+
cond do
133+
interface.get_by -> Enum.map(List.wrap(interface.get_by), &"`#{&1}`")
134+
interface.get_by_identity -> ["`#{interface.get_by_identity}`"]
135+
true -> []
136+
end
137+
138+
accept = Enum.map(Map.get(action, :accept) || [], &"`#{&1}`")
139+
140+
arguments =
141+
(Map.get(action, :arguments) || [])
142+
|> Enum.reject(&(&1.name in @injected_build_args))
143+
|> Enum.map(fn arg -> "`#{arg.name}` (#{type_label(arg.type)})" end)
144+
145+
case get_by_arg ++ accept ++ arguments do
146+
[] -> "—"
147+
list -> Enum.join(list, ", ")
148+
end
149+
end
150+
151+
defp render_purpose(action) do
152+
case action.description do
153+
nil -> "—"
154+
"" -> "—"
155+
desc -> desc |> String.replace("\n", " ") |> String.trim()
156+
end
157+
end
158+
159+
defp type_label({:array, type}), do: "list of #{type_label(type)}"
160+
161+
defp type_label(type) when is_atom(type) do
162+
type
163+
|> to_string()
164+
|> String.replace_prefix("Elixir.Ash.Type.", "")
165+
|> String.replace_prefix("Elixir.", "")
166+
|> String.downcase()
167+
end
168+
169+
defp type_label(other), do: inspect(other)
170+
171+
defp short_name(module) do
172+
module |> Module.split() |> List.last()
173+
end
174+
end

lib/nbn/resources/ntd.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ defmodule DiffoExample.Nbn.Ntd do
2222
domain: Nbn,
2323
authorizers: [Ash.Policy.Authorizer]
2424

25-
resource do
26-
description "An Ash Resource representing a Network Termination Device (NTD)"
27-
plural_name :Ntds
28-
end
29-
3025
policies do
3126
bypass DiffoExample.Nbn.Checks.NoActor do
3227
authorize_if always()
@@ -41,6 +36,11 @@ defmodule DiffoExample.Nbn.Ntd do
4136
end
4237
end
4338

39+
resource do
40+
description "An Ash Resource representing a Network Termination Device (NTD)"
41+
plural_name :Ntds
42+
end
43+
4444
provider do
4545
specification do
4646
id "c3d4e5f6-7a8b-4c9d-ae0f-2a3b4c5d6e7f"

lib/nbn/resources/uni.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ defmodule DiffoExample.Nbn.Uni do
2222
domain: Nbn,
2323
authorizers: [Ash.Policy.Authorizer]
2424

25-
resource do
26-
description "An Ash Resource representing a User Network Interface (UNI)"
27-
plural_name :Unis
28-
end
29-
3025
policies do
3126
bypass DiffoExample.Nbn.Checks.NoActor do
3227
authorize_if always()
@@ -41,6 +36,11 @@ defmodule DiffoExample.Nbn.Uni do
4136
end
4237
end
4338

39+
resource do
40+
description "An Ash Resource representing a User Network Interface (UNI)"
41+
plural_name :Unis
42+
end
43+
4444
provider do
4545
specification do
4646
id "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"

0 commit comments

Comments
 (0)