From 29e3c114901740409bfef03fc39bfe792e580f51 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Thu, 19 Dec 2024 16:09:53 +0100 Subject: [PATCH 1/7] chore(deps): bump `typerefl` to 0.9.6 Also bump minimum supported Erlang/OTP version to 23. --- rebar.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index 9f6880c..8bcd1bc 100644 --- a/rebar.config +++ b/rebar.config @@ -1,8 +1,8 @@ -{minimum_otp_vsn, "21.0"}. +{minimum_otp_vsn, "23.0"}. {deps, [ {getopt, "1.0.1"}, - {typerefl, {git, "https://github.com/ieQu1/typerefl.git", {tag, "0.9.1"}}} + {typerefl, {git, "https://github.com/ieQu1/typerefl.git", {tag, "0.9.6"}}} ]}. {edoc_opts, [{preprocess, true}]}. From 1695737759aa4718fdda4706496690dd0bdb73e8 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Thu, 19 Dec 2024 16:12:01 +0100 Subject: [PATCH 2/7] chore: quote `maybe`s as they're now valid language constructs --- src/hocon_parser.yrl | 2 +- src/hocon_scanner.xrl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hocon_parser.yrl b/src/hocon_parser.yrl index e399f00..23fbd3f 100644 --- a/src/hocon_parser.yrl +++ b/src/hocon_parser.yrl @@ -75,7 +75,7 @@ make_array(Line, Array) -> #{'$hcTyp' => array, '$hcVal' => Array, '$hcMeta' => make_primitive_value({endstr, Line, Value}) -> #{'$hcTyp' => string, '$hcVal' => Value, '$hcMeta' => #{line => Line}}; make_primitive_value({T, Line, Value}) -> #{'$hcTyp' => T, '$hcVal' => Value, '$hcMeta' => #{line => Line}}. -make_variable({V, Line, {maybe, Value}}) when V =:= variable orelse V =:= endvar -> +make_variable({V, Line, {'maybe', Value}}) when V =:= variable orelse V =:= endvar -> #{'$hcTyp' => variable, '$hcVal' => Value, name => Value, '$hcMeta' => #{line => Line}, required => false}; make_variable({V, Line, Value}) when V =:= variable orelse V =:= endvar -> #{'$hcTyp' => variable, '$hcVal' => Value, name => Value, '$hcMeta' => #{line => Line}, required => true}. diff --git a/src/hocon_scanner.xrl b/src/hocon_scanner.xrl index 164a7a8..9510bca 100644 --- a/src/hocon_scanner.xrl +++ b/src/hocon_scanner.xrl @@ -86,7 +86,7 @@ Rules. {Percent} : {token, {string, TokenLine, TokenChars}}. {Duration} : {token, {string, TokenLine, TokenChars}}. {Variable} : {token, {variable, TokenLine, var_ref_name(TokenChars)}}. -{MaybeVar} : {token, {variable, TokenLine, {maybe, maybe_var_ref_name(TokenChars)}}}. +{MaybeVar} : {token, {variable, TokenLine, {'maybe', maybe_var_ref_name(TokenChars)}}}. {Required} : {token, {required, TokenLine}, get_filename_from_required(TokenChars)}. From acd3d71233d4703bb7ffbb4397b9cc4193755843 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Thu, 19 Dec 2024 16:12:59 +0100 Subject: [PATCH 3/7] chore: contruct translation/validation error terms directly Using macros for that purpose now causes a lot of warnings similar to: ``` typerefl.erl:233:24: expression updates a literal ``` --- src/hocon_tconf.erl | 59 +++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/hocon_tconf.erl b/src/hocon_tconf.erl index 7d468d7..8316599 100644 --- a/src/hocon_tconf.erl +++ b/src/hocon_tconf.erl @@ -69,9 +69,6 @@ -type name() :: hocon_schema:name(). -type schema() :: hocon_schema:schema(). --define(VALIDATION_ERRS(Context), [Context#{kind => validation_error}]). --define(TRANSLATION_ERRS(Context), [Context#{kind => translation_error}]). - -define(DEFAULT_REQUIRED, false). -define(META_BOX(Tag, Metadata), #{?METADATA => #{Tag => Metadata}}). @@ -148,21 +145,21 @@ do_translate([{MappedField, Translator} | More], TrNamespace, Conf, Acc) -> catch throw:Reason -> Error = - {error, - ?TRANSLATION_ERRS(#{ - reason => Reason, - path => MappedField0 - })}, + {error, #{ + kind => translation_error, + reason => Reason, + path => MappedField0 + }}, do_translate(More, TrNamespace, Conf, [Error | Acc]); Exception:Reason:St -> Error = - {error, - ?TRANSLATION_ERRS(#{ - reason => Reason, - stacktrace => St, - path => MappedField0, - exception => Exception - })}, + {error, #{ + kind => translation_error, + reason => Reason, + stacktrace => St, + path => MappedField0, + exception => Exception + }}, do_translate(More, TrNamespace, Conf, [Error | Acc]) end. @@ -189,13 +186,13 @@ assert_integrity(Schema, [{Name, Validator} | Rest], Conf, Acc) -> assert_integrity_failure(Schema, Rest, Conf, Name, Reason); Exception:Reason:St -> Error = - {error, - ?VALIDATION_ERRS(#{ - reason => integrity_validation_crash, - validation_name => Name, - exception => {Exception, Reason}, - stacktrace => St - })}, + {error, #{ + kind => validation_error, + reason => integrity_validation_crash, + validation_name => Name, + exception => {Exception, Reason}, + stacktrace => St + }}, assert_integrity(Schema, Rest, Conf, [Error | Acc]) end. @@ -205,12 +202,12 @@ assert_integrity_failure(Schema, Rest, Conf, Name, Reason) -> Rest, Conf, [ - {error, - ?VALIDATION_ERRS(#{ - reason => integrity_validation_failure, - validation_name => Name, - result => Reason - })} + {error, #{ + kind => validation_error, + reason => integrity_validation_failure, + validation_name => Name, + result => Reason + }} ] ). @@ -1181,9 +1178,9 @@ validation_errs(Opts, Reason, Value) -> end, validation_errs(Opts, Err). -validation_errs(Opts, Context) -> - ContextWithPath = ensure_path(Opts, Context), - [{error, ?VALIDATION_ERRS(ContextWithPath)}]. +validation_errs(Opts, ContextIn) -> + Context = ContextIn#{kind => validation_error}, + [{error, ensure_path(Opts, Context)}]. ensure_path(_Opts, #{path := _} = Context) -> Context; ensure_path(Opts, Context) -> Context#{path => path(Opts)}. From a25c0d9136a5e176939ed46aeebfe73d0c537eda Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 24 Dec 2024 15:29:35 +0100 Subject: [PATCH 4/7] fix(builtin): adapt builtin converter to `typerefl` API changes Also treat builting converter errors as validation errors rather than another special `failed_to_check_field` error class. --- src/hocon_schema_builtin.erl | 4 +- src/hocon_tconf.erl | 16 +++++--- test/hocon_schema_builtin_tests.erl | 61 ++++++++++++++++------------- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/hocon_schema_builtin.erl b/src/hocon_schema_builtin.erl index 5a2b716..cdb2c38 100644 --- a/src/hocon_schema_builtin.erl +++ b/src/hocon_schema_builtin.erl @@ -53,8 +53,8 @@ convert(Str, Type) when is_list(Str) -> case typerefl:from_string(Type, Str) of {ok, V} -> V; - {error, _} -> - Str + {error, Reason} -> + throw({?MODULE, Reason}) end; false -> Str diff --git a/src/hocon_tconf.erl b/src/hocon_tconf.erl index 8316599..cd77869 100644 --- a/src/hocon_tconf.erl +++ b/src/hocon_tconf.erl @@ -650,11 +650,17 @@ map_field(Type, Schema, Value0, Opts) -> %% primitive type Value = unbox(Opts, Value0), PlainValue = ensure_plain(Value), - ConvertedValue = eval_builtin_converter(PlainValue, Type, Opts), - Validators = get_validators(Schema, Type, Opts), - ValidationResult = validate(Opts, Schema, ConvertedValue, Validators), - Value1 = boxit(Opts, ConvertedValue, Value0), - {ValidationResult, ensure_obfuscate_sensitive(Opts, Schema, Value1)}. + try + ConvertedValue = eval_builtin_converter(PlainValue, Type, Opts), + Validators = get_validators(Schema, Type, Opts), + ValidationResult = validate(Opts, Schema, ConvertedValue, Validators), + Value1 = boxit(Opts, ConvertedValue, Value0), + {ValidationResult, ensure_obfuscate_sensitive(Opts, Schema, Value1)} + catch + {hocon_schema_builtin, Error} -> + ValidationErrors = validation_errs(Opts, Error, obfuscate(Schema, PlainValue)), + {ValidationErrors, ensure_obfuscate_sensitive(Opts, Schema, Value0)} + end. eval_builtin_converter(PlainValue, Type, Opts) -> case is_make_serializable(Opts) of diff --git a/test/hocon_schema_builtin_tests.erl b/test/hocon_schema_builtin_tests.erl index 1a04149..989bfc5 100644 --- a/test/hocon_schema_builtin_tests.erl +++ b/test/hocon_schema_builtin_tests.erl @@ -14,6 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- -module(hocon_schema_builtin_tests). +-feature(maybe_expr, enable). -include_lib("typerefl/include/types.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -42,32 +43,35 @@ builtin_check_test() -> ?assertEqual(#{<<"listener">> => #{<<"bind">> => 65535}}, check_plain(Conf2)), BadConf1 = "listener.bind = 65536", ?assertThrow( - #{ - exception := "port_number_too_large", - field := <<"bind">>, - path := "listener", - reason := failed_to_check_field - }, + {?MODULE, [ + #{ + kind := validation_error, + reason := "port_number_too_large", + path := "listener.bind" + } + ]}, check_plain(BadConf1) ), BadConf2 = "listener.bind = -1", ?assertThrow( - #{ - exception := "port_number_must_be_positive", - field := <<"bind">>, - path := "listener", - reason := failed_to_check_field - }, + {?MODULE, [ + #{ + kind := validation_error, + reason := "port_number_must_be_positive", + path := "listener.bind" + } + ]}, check_plain(BadConf2) ), BadConf3 = "listener.bind = 1883d", ?assertThrow( - #{ - exception := "bad_port_number", - field := <<"bind">>, - path := "listener", - reason := failed_to_check_field - }, + {?MODULE, [ + #{ + kind := validation_error, + reason := "bad_port_number", + path := "listener.bind" + } + ]}, check_plain(BadConf3) ), ok. @@ -80,13 +84,14 @@ to_ip_port(Str) -> case split_ip_port(Str) of {"", Port} -> %% this is a local address - {ok, parse_port(Port)}; + parse_port(Port); {MaybeIp, Port} -> - PortVal = parse_port(Port), - case inet:parse_address(MaybeIp) of - {ok, IpTuple} -> - {ok, {IpTuple, PortVal}}; - _ -> + maybe + {ok, PortVal} ?= parse_port(Port), + {ok, IpTuple} ?= inet:parse_address(MaybeIp), + {ok, {IpTuple, PortVal}} + else + {error, _} -> {error, bad_ip_port} end; _ -> @@ -115,8 +120,8 @@ split_ip_port(Str0) -> parse_port(Port) -> case string:to_integer(string:strip(Port)) of - {P, ""} when P < 0 -> throw("port_number_must_be_positive"); - {P, ""} when P > 65535 -> throw("port_number_too_large"); - {P, ""} -> P; - _ -> throw("bad_port_number") + {P, ""} when P < 0 -> {error, "port_number_must_be_positive"}; + {P, ""} when P > 65535 -> {error, "port_number_too_large"}; + {P, ""} -> {ok, P}; + _ -> {error, "bad_port_number"} end. From 7db14adf8b8303b5ff4dbae318618368c7c77687 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 24 Dec 2024 15:33:14 +0100 Subject: [PATCH 5/7] chore: bump `getopt` to 1.0.3 --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 8bcd1bc..06c0fb9 100644 --- a/rebar.config +++ b/rebar.config @@ -1,7 +1,7 @@ {minimum_otp_vsn, "23.0"}. {deps, [ - {getopt, "1.0.1"}, + {getopt, "1.0.3"}, {typerefl, {git, "https://github.com/ieQu1/typerefl.git", {tag, "0.9.6"}}} ]}. From 8450e1b6501c02bdf415cb2a8441606283cdc5f3 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 24 Dec 2024 16:24:26 +0100 Subject: [PATCH 6/7] ci: update workflows to run under Erlang/OTP 27 where needed --- .github/workflows/run_elvis.yaml | 10 ++++------ .github/workflows/run_test_case.yaml | 9 ++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run_elvis.yaml b/.github/workflows/run_elvis.yaml index 9a56d60..6b2c80e 100644 --- a/.github/workflows/run_elvis.yaml +++ b/.github/workflows/run_elvis.yaml @@ -7,11 +7,9 @@ jobs: runs-on: ubuntu-latest container: - image: erlang:25.3 + image: erlang:26 steps: - - uses: actions/checkout@v1 - - run: | - ./scripts/elvis-check.sh $GITHUB_BASE_REF - - run: | - make erlfmt-check + - uses: actions/checkout@v4 + - run: ./scripts/elvis-check.sh $GITHUB_BASE_REF + - run: make erlfmt-check diff --git a/.github/workflows/run_test_case.yaml b/.github/workflows/run_test_case.yaml index 9c2313e..5cdc408 100644 --- a/.github/workflows/run_test_case.yaml +++ b/.github/workflows/run_test_case.yaml @@ -7,12 +7,15 @@ jobs: run_test_case: runs-on: ubuntu-latest + strategy: + matrix: + erlang-vsn: ['26', '27'] container: - image: erlang:25.3 + image: erlang:${{ matrix.erlang-vsn }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Code dialyzer run: | make dialyzer @@ -24,5 +27,5 @@ jobs: make cover - uses: actions/upload-artifact@v4 with: - name: cover + name: cover-${{ matrix.erlang-vsn }} path: _build/test/cover From 336c8cfe014e956cb71cb3a6bf356692cb39f98c Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 24 Dec 2024 16:30:42 +0100 Subject: [PATCH 7/7] chore: please dialyzer --- src/hocon.app.src | 1 + src/hocon_pp.erl | 3 ++- src/hocon_tconf.erl | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hocon.app.src b/src/hocon.app.src index 805c340..958f606 100644 --- a/src/hocon.app.src +++ b/src/hocon.app.src @@ -5,6 +5,7 @@ {applications, [ kernel, stdlib, + compiler, getopt, typerefl ]}, diff --git a/src/hocon_pp.erl b/src/hocon_pp.erl index 858182e..f8372de 100644 --- a/src/hocon_pp.erl +++ b/src/hocon_pp.erl @@ -478,8 +478,9 @@ esc($\") -> "\\\""; esc($\\) -> "\\\\"; esc(Char) -> Char. --include_lib("eunit/include/eunit.hrl"). -ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + simple_string_test_() -> [ ?_assert(is_simple_string("")), diff --git a/src/hocon_tconf.erl b/src/hocon_tconf.erl index cd77869..a27316f 100644 --- a/src/hocon_tconf.erl +++ b/src/hocon_tconf.erl @@ -1191,7 +1191,7 @@ validation_errs(Opts, ContextIn) -> ensure_path(_Opts, #{path := _} = Context) -> Context; ensure_path(Opts, Context) -> Context#{path => path(Opts)}. --spec plain_put(opts(), [binary()], term(), hocon:confing()) -> hocon:config(). +-spec plain_put(opts(), [binary()], term(), hocon:config()) -> hocon:config(). plain_put(_Opts, [], Value, _Old) -> Value; plain_put(Opts, [Name | Path], Value, Conf0) ->