diff --git a/lib/contexted.ex b/lib/contexted.ex index ff6a77e..7fd893f 100644 --- a/lib/contexted.ex +++ b/lib/contexted.ex @@ -206,4 +206,27 @@ defmodule Contexted do See more in [Contexted.CRUD](https://hexdocs.pm/contexted/Contexted.CRUD.html). """ + + @doc ~S""" + Marks a module reference as intentionally ignored by the cross‑context tracer. + + Wrap a cross‑context module reference with `ignore/1` when you explicitly allow + that call and don't want `Contexted.Tracer` to raise a compile‑time error for it. + + This function is a no‑op at runtime — it simply returns the module. It only + serves as a hint for the tracer during compilation. + + ## Examples + + Usage inside a context (the cross‑context call compiles because the module is wrapped): + + ```elixir + defmodule App.Account.UserContext do + def hello(id) do + Contexted.ignore(App.Blog.PostContext).hello(id) + end + end + """ + @spec ignore(module()) :: module() + def ignore(module), do: module end diff --git a/test/context_test.exs b/test/context_test.exs index d548184..e5c42c6 100644 --- a/test/context_test.exs +++ b/test/context_test.exs @@ -1,4 +1,47 @@ defmodule ContextedTest do use ExUnit.Case doctest Contexted + + describe "cross-context reference checking" do + test "raises on cross-reference without ignore/1" do + code = """ + defmodule Foo.Account.UserContext do + alias Foo.Blog.PostContext + def hello, do: PostContext.hello() + end + + defmodule Foo.Blog.PostContext do + def hello, do: :ok + end + """ + + Application.put_env(:contexted, :contexts, [Foo.Account, Foo.Blog]) + Code.put_compiler_option(:tracers, [Contexted.Tracer]) + + assert_raise RuntimeError, + ~r/You can't reference Foo.Blog context within Foo.Account context\./, + fn -> + Code.compile_string(code, "test/foo_without_ignore.ex") + end + end + + test "does NOT raise when the call is wrapped with Contexted.ignore/1" do + code = """ + defmodule Bar.Account.UserContext do + def hello, do: Contexted.ignore(Bar.Blog.PostContext).hello() + end + + defmodule Bar.Blog.PostContext do + def hello, do: :ok + end + """ + + Application.put_env(:contexted, :contexts, [Bar.Account, Bar.Blog]) + Code.put_compiler_option(:tracers, [Contexted.Tracer]) + + Code.compile_string(code, "test/bar_with_ignore.ex") + # credo:disable-for-next-line + assert :ok = apply(Bar.Account.UserContext, :hello, []) + end + end end