Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions lib/spark/dsl/extension.ex
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ defmodule Spark.Dsl.Extension do
only_sections =
sections
|> Enum.reject(& &1.top_level?)
|> Enum.map(&{&1.name, 1})
|> Enum.flat_map(&[{&1.name, 1}, {&1.name, 2}])

quote generated: true, location: :keep do
require Spark.Dsl.Extension
Expand Down Expand Up @@ -878,11 +878,37 @@ defmodule Spark.Dsl.Extension do
end

unless section.top_level? && path == [] do
defmacro unquote(section.name)(body) do
# Handle mixed syntax - personal_details(opts, do: block)
defmacro unquote(section.name)(opts, [do: _] = _block) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 what if we actually just could handle both? Do we want to? We could theoretically just call it with just opts and then call it with just do and it should "just work"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel like this question engages the "why are sections and entities separate?" question raised in a previous issue. Could entities handle everything and simplify the logic? I think so

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly but that's a major version bump and an overhaul of the whole Ash ecosystem so I'm not particularly interested in that at the moment 😂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed up the same handling for mixed entity syntax which was the issue that @marot originally found which I was doing while travelling.

I've got some other changes coming, so will have a think about whether "just doing it" is better than throwing a helpful error.

Either way, a helpful error is better than the horrible errors users currently get, so would it be worth merging this as is?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 This is defining a new function just to tell you that you can't call it. If we're going to define a new function that expands the API, and the option exists to make it work, then we should prefer that. If Elixir's undefined function error is confusing then we should fix that.

section_name = unquote(section.name)

raise Spark.Error.DslError,
module: __CALLER__.module,
message:
"Cannot use both inline syntax and block syntax for DSL section `#{section_name}`. " <>
"Use block syntax `#{section_name} do ... end`",
path: unquote(path ++ [section.name])
end

# Handle inline syntax and normal block syntax
defmacro unquote(section.name)(body_or_opts) do
section_name = unquote(section.name)

# Check if this is inline syntax (keyword list without :do)
if is_list(body_or_opts) and not Keyword.has_key?(body_or_opts, :do) do
raise Spark.Error.DslError,
module: __CALLER__.module,
message:
"Cannot use inline syntax for DSL section `#{section_name}`. " <>
"Use block syntax: `#{section_name} do ... end`.",
path: unquote(path ++ [section.name])
end

body = body_or_opts

opts_module = unquote(opts_module)
section_path = unquote(path ++ [section.name])
extension = unquote(extension)

entity_modules = unquote(entity_modules)

patch_modules =
Expand Down Expand Up @@ -1226,6 +1252,25 @@ defmodule Spark.Dsl.Extension do
end

@moduledoc false

# Generate mixed syntax macro only for entities without optional args
if not Enum.any?(entity.args, fn
{:optional, _, _} -> true
{:optional, _} -> true
_ -> false
end) do
defmacro unquote(entity.name)(unquote_splicing(args), extra_opts, [do: _] = _block) do
entity_name = unquote(entity.name)

raise Spark.Error.DslError,
module: __CALLER__.module,
message:
"Cannot use both inline syntax and block syntax for entity `#{entity_name}`. " <>
"Use block syntax `#{entity_name} args... do ... end`",
path: unquote(section_path ++ nested_entity_path)
end
end

defmacro unquote(entity.name)(unquote_splicing(args), opts \\ nil) do
section_path = unquote(Macro.escape(section_path))
entity_schema = unquote(Macro.escape(entity.schema))
Expand Down
47 changes: 47 additions & 0 deletions test/dsl_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,53 @@ defmodule Spark.DslTest do
end
end

describe "DSL section and entity syntax validation" do
test "mixed inline/block syntax should provide helpful error" do
assert_raise Spark.Error.DslError,
~r/Cannot use both inline syntax and block syntax.*personal_details/,
fn ->
defmodule MixedSyntax do
@moduledoc false
use Spark.Test.Contact

personal_details first_name: "Luke" do
last_name("Skywalker")
end
end
end
end

test "inline syntax for DSL section should provide helpful error" do
assert_raise Spark.Error.DslError,
~r/Cannot use inline syntax for DSL section.*personal_details/,
fn ->
defmodule InlineSyntax do
@moduledoc false
use Spark.Test.Contact

personal_details(first_name: "Luke", last_name: "Skywalker")
end
end
end

test "mixed inline/block syntax for entity should provide helpful error" do
assert_raise Spark.Error.DslError,
~r/Cannot use both inline syntax and block syntax.*preset/,
fn ->
defmodule EntityMixedSyntax do
@moduledoc false
use Spark.Test.Contact

presets do
preset :einstein, default_message: "E=mc²" do
contacter(fn x -> x end)
end
end
end
end
end
end

describe "optional entity arguments" do
test "optional arguments can be supplied either as arguments or as dsl options" do
defmodule OptionEinstein do
Expand Down
2 changes: 1 addition & 1 deletion test/support/contact/contact.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule Spark.Test.Contact do
doc: "A module"
],
contacter: [
doc: "A function that wil contact this person with a message",
doc: "A function that will contact this person with a message",
type:
{:spark_function_behaviour, Spark.Test.Contact.Contacter,
Spark.Test.ContacterBuiltins, {Spark.Test.Contact.Contacter.Function, 1}}
Expand Down
Loading