diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ab559d..b1cf29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# [Unreleased] + +## Enhancements + +* Align the `Solid.UndefinedFilterError` message with `Solid.UndefinedVariableError` - include line number + +## Bug fixes + +* Return `{:error, errors}` tuple when both strict_filters and strict_variables are enforced while rendering a template +* Use correct variable name in the `Solid.UndefinedVariableError` message + # 1.0.1 (2025-07-04) ## Bug fixes diff --git a/lib/solid/argument.ex b/lib/solid/argument.ex index ab651a6..d1b819c 100644 --- a/lib/solid/argument.ex +++ b/lib/solid/argument.ex @@ -187,7 +187,11 @@ defmodule Solid.Argument do {:error, {:not_found, key}, context} -> context = if strict_variables do - Context.put_errors(context, %UndefinedVariableError{variable: key, loc: arg.loc}) + Context.put_errors(context, %UndefinedVariableError{ + variable: key, + original_name: arg.original_name, + loc: arg.loc + }) else context end diff --git a/lib/solid/undefined_filter_error.ex b/lib/solid/undefined_filter_error.ex index b7dd367..fca4a52 100644 --- a/lib/solid/undefined_filter_error.ex +++ b/lib/solid/undefined_filter_error.ex @@ -3,5 +3,9 @@ defmodule Solid.UndefinedFilterError do defexception [:filter, :loc] @impl true - def message(exception), do: "Undefined filter #{exception.filter}" + def message(exception) do + line = exception.loc.line + reason = "Undefined filter #{exception.filter}" + "#{line}: #{reason}" + end end diff --git a/lib/solid/undefined_variable_error.ex b/lib/solid/undefined_variable_error.ex index b493138..5768ff4 100644 --- a/lib/solid/undefined_variable_error.ex +++ b/lib/solid/undefined_variable_error.ex @@ -1,11 +1,11 @@ defmodule Solid.UndefinedVariableError do @type t :: %__MODULE__{} - defexception [:variable, :loc] + defexception [:variable, :original_name, :loc] @impl true def message(exception) do line = exception.loc.line - reason = "Undefined variable #{exception.variable}" + reason = "Undefined variable #{exception.original_name}" "#{line}: #{reason}" end end diff --git a/test/solid/argument_test.exs b/test/solid/argument_test.exs index e148633..b9cfb03 100644 --- a/test/solid/argument_test.exs +++ b/test/solid/argument_test.exs @@ -296,7 +296,10 @@ defmodule Solid.ArgumentTest do arg = %Variable{original_name: "key[1]", loc: @loc, identifier: "key", accesses: accesses} context = %Solid.Context{vars: %{"key" => "a string"}} assert {:ok, nil, context} = Argument.get(arg, context, [], strict_variables: true) - assert context.errors == [%UndefinedVariableError{variable: ["key", 1], loc: @loc}] + + assert context.errors == [ + %UndefinedVariableError{variable: ["key", 1], original_name: "key[1]", loc: @loc} + ] end test "array access and nested" do @@ -376,7 +379,9 @@ defmodule Solid.ArgumentTest do assert {:ok, 456, context} = Argument.get(arg, context, filters, strict_variables: true) - assert context.errors == [%UndefinedVariableError{variable: ["key"], loc: @loc}] + assert context.errors == [ + %UndefinedVariableError{variable: ["key"], original_name: "key", loc: @loc} + ] end test "missing filter strict_filters" do @@ -445,7 +450,7 @@ defmodule Solid.ArgumentTest do assert context.errors == [ %Solid.UndefinedFilterError{filter: "unknown", loc: @loc}, - %UndefinedVariableError{variable: ["key"], loc: @loc} + %UndefinedVariableError{variable: ["key"], original_name: "key", loc: @loc} ] end @@ -523,7 +528,9 @@ defmodule Solid.ArgumentTest do strict_variables: true ) - assert context.errors == [%UndefinedVariableError{variable: ["name"], loc: @loc}] + assert context.errors == [ + %UndefinedVariableError{variable: ["name"], original_name: "name", loc: @loc} + ] end end end diff --git a/test/solid_test.exs b/test/solid_test.exs index b2ad5a7..90f3d4b 100644 --- a/test/solid_test.exs +++ b/test/solid_test.exs @@ -213,10 +213,12 @@ defmodule SolidTest do assert error == [ %Solid.UndefinedVariableError{ variable: ["var1"], + original_name: "var1", loc: %Solid.Parser.Loc{line: 1, column: 5} }, %Solid.UndefinedVariableError{ variable: ["var2"], + original_name: "var2", loc: %Solid.Parser.Loc{line: 1, column: 16} } ] @@ -254,16 +256,19 @@ defmodule SolidTest do assert error == [ %Solid.UndefinedVariableError{ variable: ["var1"], + original_name: "var1", loc: %Solid.Parser.Loc{line: 1, column: 5} }, %Solid.UndefinedVariableError{ variable: ["var2"], + original_name: "var2", loc: %Solid.Parser.Loc{line: 1, column: 16} }, # FIXME this should somehow point out which file? # Check how liquid does this %Solid.UndefinedVariableError{ variable: ["var3"], + original_name: "var3", loc: %Solid.Parser.Loc{line: 1, column: 4} } ] @@ -296,6 +301,7 @@ defmodule SolidTest do assert error == [ %Solid.UndefinedVariableError{ variable: ["var1"], + original_name: "var1", loc: %Solid.Parser.Loc{line: 1, column: 5} }, %Solid.UndefinedFilterError{ @@ -304,9 +310,47 @@ defmodule SolidTest do }, %Solid.UndefinedVariableError{ variable: ["var2"], + original_name: "var2", loc: %Solid.Parser.Loc{line: 1, column: 38} } ] end + + test "undefined variable error message with multiple variables" do + template = + "{{ var1 }}\n{{ event.name }}\n{{ user.properties['name'] }}\n" + + {:error, [first_error, second_error, third_error], _partial_result} = + template + |> Solid.parse!() + |> Solid.render(%{}, strict_variables: true, file_system: {TestFileSystem, nil}) + + assert String.contains?(Solid.UndefinedVariableError.message(first_error), "var1") + + assert String.contains?( + Solid.UndefinedVariableError.message(second_error), + "event.name" + ) + + assert String.contains?( + Solid.UndefinedVariableError.message(third_error), + "user.properties['name']" + ) + end + + test "undefined filter error message with line number" do + template = "{{ var1 | not_a_filter }}" + + assert_raise Solid.RenderError, + "1 error(s) found while rendering\n1: Undefined filter not_a_filter", + fn -> + template + |> Solid.parse!() + |> Solid.render!(%{"var1" => "value"}, + strict_filters: true, + file_system: {TestFileSystem, nil} + ) + end + end end end