Packages

Reference Elixir validator for WPL (Wellness Plan Language) — JSON Schema + semantic invariants

Current section

Files

Jump to
wpl_validator lib wpl validator rules cyclic_subplan.ex
Raw

lib/wpl/validator/rules/cyclic_subplan.ex

defmodule WPL.Validator.Rules.CyclicSubplan do
@moduledoc """
Detects sub-plan reference cycles.
Single-plan scope: catches self-references where a `SubPlanActivity`
has `sub_plan_ref` equal to the containing plan's `id`. Cross-plan
cycles (`A → B → A`) require a `sub_plans` resolution map at validate
time and are deferred until that API extension lands.
"""
use WPL.Validator.Rule
alias WPL.Validator.{Error, WalkContext}
@impl true
def enter_plan(ctx, plan) do
case Map.get(plan, "id") do
id when is_binary(id) and id != "" ->
WalkContext.put_scope(ctx, :cur_plan_id, id)
_ ->
ctx
end
end
@impl true
def enter_activity(ctx, activity, path) do
with "sub_plan" <- Map.get(activity, "type"),
ref when is_binary(ref) and ref != "" <- Map.get(activity, "sub_plan_ref"),
plan_id when is_binary(plan_id) <-
WalkContext.get_scope(ctx, :cur_plan_id, nil),
true <- ref == plan_id do
WalkContext.emit(ctx, %Error{
path: path,
code: :cyclic_subplan,
message: "Sub-plan reference '#{ref}' creates a self-cycle",
severity: :error,
meta: %{cycle: [plan_id, ref]}
})
else
_ -> ctx
end
end
end