From cc8631223ed676a8bb89c2a57d14bd228725ff8b Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 7 Jun 2023 10:26:54 -0300 Subject: [PATCH] fix(schema): avoid `function_clause` error when compacting errors Fixes https://emqx.atlassian.net/browse/EMQX-10168 The bridge probe API displayed the typecheck errors for the new timeout duration types correctly, but when an user tried to create the bridge anyway a `function_clause` error was raised when trying to compact hocon errors: ``` 09:47:19.045 [warning] [exception: :error, path: '/bridges', reason: {:case_clause, {:error, {:config_update_crashed, :function_clause}}}, stacktrace: [{:emqx_bridge_api, :create_or_update_bridge, 4, [file: '/home/thales/dev/emqx/emqx/apps/emqx_bridge/src/emqx_bridge_api.erl', line: 602]}, {:minirest_handler, :apply_callback, 3, [file: '/home/thales/dev/emqx/emqx/deps/minirest/src/minirest_handler.erl', line: 111]}, {:minirest_handler, :handle, 2, [file: '/home/thales/dev/emqx/emqx/deps/minirest/src/minirest_handler.erl', line: 44]}, {:minirest_handler, :init, 2, [file: '/home/thales/dev/emqx/emqx/deps/minirest/src/minirest_handler.erl', line: 27]}, {:cowboy_handler, :execute, 2, [file: '/home/thales/dev/emqx/emqx/deps/cowboy/src/cowboy_handler.erl', line: 41]}, {:cowboy_stream_h, :execute, 3, [file: '/home/thales/dev/emqx/emqx/deps/cowboy/src/cowboy_stream_h.erl', line: 318]}, {:cowboy_stream_h, :request_process, 3, [file: '/home/thales/dev/emqx/emqx/deps/cowboy/src/cowboy_stream_h.erl', line: 302]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 240]}]] ``` This fixes the issue so that both APIs return more friendly error messages. --- apps/emqx/src/emqx_hocon.erl | 11 ++++++++++- apps/emqx/src/emqx_schema.erl | 6 +++++- apps/emqx/test/emqx_schema_tests.erl | 18 +++++++++++++++--- .../test/emqx_resource_schema_tests.erl | 2 +- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/apps/emqx/src/emqx_hocon.erl b/apps/emqx/src/emqx_hocon.erl index 08192e9be..e028ebb14 100644 --- a/apps/emqx/src/emqx_hocon.erl +++ b/apps/emqx/src/emqx_hocon.erl @@ -100,7 +100,16 @@ no_stacktrace(Map) -> %% it's maybe too much when reporting to the user -spec compact_errors(any(), Stacktrace :: list()) -> {error, any()}. compact_errors({SchemaModule, Errors}, Stacktrace) -> - compact_errors(SchemaModule, Errors, Stacktrace). + compact_errors(SchemaModule, Errors, Stacktrace); +compact_errors(ErrorContext0, _Stacktrace) when is_map(ErrorContext0) -> + case ErrorContext0 of + #{exception := #{schema_module := _Mod, message := _Msg} = Detail} -> + Error0 = maps:remove(exception, ErrorContext0), + Error = maps:merge(Error0, Detail), + {error, Error}; + _ -> + {error, ErrorContext0} + end. compact_errors(SchemaModule, [Error0 | More], _Stacktrace) when is_map(Error0) -> Error1 = diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 521293f7a..28404a7da 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -2695,7 +2695,11 @@ do_to_timeout_duration(Str, Fn, Max, Unit) -> Msg = lists:flatten( io_lib:format("timeout value too large (max: ~b ~s)", [Max, Unit]) ), - throw(Msg) + throw(#{ + schema_module => ?MODULE, + message => Msg, + kind => validation_error + }) end; Err -> Err diff --git a/apps/emqx/test/emqx_schema_tests.erl b/apps/emqx/test/emqx_schema_tests.erl index 58f9a94d5..b5fd156ad 100644 --- a/apps/emqx/test/emqx_schema_tests.erl +++ b/apps/emqx/test/emqx_schema_tests.erl @@ -886,15 +886,27 @@ timeout_types_test_() -> typerefl:from_string(emqx_schema:timeout_duration_s(), <<"4294967000ms">>) ), ?_assertThrow( - "timeout value too large (max: 4294967295 ms)", + #{ + kind := validation_error, + message := "timeout value too large (max: 4294967295 ms)", + schema_module := emqx_schema + }, typerefl:from_string(emqx_schema:timeout_duration(), <<"4294967296ms">>) ), ?_assertThrow( - "timeout value too large (max: 4294967295 ms)", + #{ + kind := validation_error, + message := "timeout value too large (max: 4294967295 ms)", + schema_module := emqx_schema + }, typerefl:from_string(emqx_schema:timeout_duration_ms(), <<"4294967296ms">>) ), ?_assertThrow( - "timeout value too large (max: 4294967 s)", + #{ + kind := validation_error, + message := "timeout value too large (max: 4294967 s)", + schema_module := emqx_schema + }, typerefl:from_string(emqx_schema:timeout_duration_s(), <<"4294967001ms">>) ) ]. diff --git a/apps/emqx_resource/test/emqx_resource_schema_tests.erl b/apps/emqx_resource/test/emqx_resource_schema_tests.erl index 676a42510..219861a4e 100644 --- a/apps/emqx_resource/test/emqx_resource_schema_tests.erl +++ b/apps/emqx_resource/test/emqx_resource_schema_tests.erl @@ -67,7 +67,7 @@ health_check_interval_validator_test_() -> parse_and_check_webhook_bridge(webhook_bridge_health_check_hocon(<<"3_600_000ms">>)) )}, ?_assertThrow( - #{exception := "timeout value too large" ++ _}, + #{exception := #{message := "timeout value too large" ++ _}}, parse_and_check_webhook_bridge( webhook_bridge_health_check_hocon(<<"150000000000000s">>) )