From 0beb9bef367b1decbc05af362a444842ed1c1e5a Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Wed, 8 Nov 2023 22:37:10 +0100 Subject: [PATCH] feat: add display_name to union type With typerefl:alias/2, the primitive can be quite descriptive. Other types are quite self-descriptive too. Unions however, can get quite big and ugly when: 1. Some old types added for backward compatibility e.g. we want to document something like "ArrayOf(String)", but we also support comma separated strings which can be described in docs but not necessarily show in the type. 2. When the union is very big, we may want to provide a shorter name for it. For example, if all union members are enumerated for a 5-member union, it may look like OneOf(t1,t2,t3,t4,t5). When using display name, we can do: "Object (see all possible values below)" --- include/hocon_types.hrl | 3 ++- sample-schemas/demo_schema.erl | 2 +- src/hocon_schema.erl | 13 +++++++------ src/hocon_schema_example.erl | 2 +- src/hocon_tconf.erl | 4 ++-- src/hoconsc.erl | 12 ++++++++---- test/hocon_tconf_tests.erl | 2 +- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/include/hocon_types.hrl b/include/hocon_types.hrl index 21505bf..b258eb1 100644 --- a/include/hocon_types.hrl +++ b/include/hocon_types.hrl @@ -18,7 +18,8 @@ -define(HOCONSC_TYPES_HRL, true). -define(ARRAY(OfTYpe), {array, OfTYpe}). --define(UNION(OfTypes), {union, OfTypes}). +-define(UNION(OfTypes), {union, OfTypes, undefined}). +-define(UNION(OfTypes, DisplayName), {union, OfTypes, DisplayName}). -define(ENUM(OfSymbols), {enum, OfSymbols}). -define(REF(Name), {ref, Name}). % remote ref diff --git a/sample-schemas/demo_schema.erl b/sample-schemas/demo_schema.erl index bacc902..7885563 100644 --- a/sample-schemas/demo_schema.erl +++ b/sample-schemas/demo_schema.erl @@ -38,7 +38,7 @@ fields("a_b") -> ]; fields("b") -> - [ {"u", fun (type) -> {union, ["priv.bool", "priv.int"]}; + [ {"u", fun (type) -> hoconsc:union(["priv.bool", "priv.int"]); (mapping) -> "app_foo.u"; (_) -> undefined end} , {"arr", fun (type) -> hoconsc:array("priv.int"); diff --git a/src/hocon_schema.erl b/src/hocon_schema.erl index 560fbb9..c2add48 100644 --- a/src/hocon_schema.erl +++ b/src/hocon_schema.erl @@ -95,13 +95,13 @@ %% array of | ?ARRAY(type()) %% one-of - | ?UNION(union_members()) + | ?UNION(union_members(), _) %% one-of atoms, data is allowed to be binary() | ?ENUM([atom()]). -type field_schema() :: typerefl:type() - | ?UNION(union_members()) + | ?UNION(union_members(), _) | ?ARRAY(type()) | ?ENUM(type()) | field_schema_map() @@ -340,7 +340,7 @@ find_structs_per_type(Schema, ?LAZY(Type), Acc, Stack, TStack, Opts) -> find_structs_per_type(Schema, Type, Acc, Stack, TStack, Opts); find_structs_per_type(Schema, ?ARRAY(Type), Acc, Stack, TStack, Opts) -> find_structs_per_type(Schema, Type, Acc, ["$INDEX" | Stack], TStack, Opts); -find_structs_per_type(Schema, ?UNION(Types0), Acc, Stack, TStack, Opts) -> +find_structs_per_type(Schema, ?UNION(Types0, _), Acc, Stack, TStack, Opts) -> Types = hoconsc:union_members(Types0), lists:foldl( fun(T, AccIn) -> @@ -442,7 +442,7 @@ field_schema(?R_REF(_, _) = Ref, SchemaKey) -> field_schema(hoconsc:mk(Ref), SchemaKey); field_schema(?ARRAY(_) = Array, SchemaKey) -> field_schema(hoconsc:mk(Array), SchemaKey); -field_schema(?UNION(_) = Union, SchemaKey) -> +field_schema(?UNION(_, _) = Union, SchemaKey) -> field_schema(hoconsc:mk(Union), SchemaKey); field_schema(?ENUM(_) = Enum, SchemaKey) -> field_schema(hoconsc:mk(Enum), SchemaKey); @@ -476,10 +476,11 @@ fmt_type(Ns, ?ARRAY(T)) -> kind => array, elements => fmt_type(Ns, T) }; -fmt_type(Ns, ?UNION(Ts)) -> +fmt_type(Ns, ?UNION(Ts, DisplayName)) -> #{ kind => union, - members => [fmt_type(Ns, T) || T <- hoconsc:union_members(Ts)] + members => [fmt_type(Ns, T) || T <- hoconsc:union_members(Ts)], + dispay_name => DisplayName }; fmt_type(_Ns, ?ENUM(Symbols)) -> #{ diff --git a/src/hocon_schema_example.erl b/src/hocon_schema_example.erl index 47b8a67..73e943e 100644 --- a/src/hocon_schema_example.erl +++ b/src/hocon_schema_example.erl @@ -90,7 +90,7 @@ fmt_field( case maps:get(examples, Field, #{}) of #{} = Example -> fmt_field_with_example(Path, SubFields, Example, Opts2); - {union, UnionExamples} -> + ?UNION(UnionExamples, _) -> Examples1 = filter_union_example(UnionExamples, SubFields), fmt_field_with_example(Path, SubFields, Examples1, Opts2); {array, ArrayExamples} -> diff --git a/src/hocon_tconf.erl b/src/hocon_tconf.erl index 4b07c6b..5beb7aa 100644 --- a/src/hocon_tconf.erl +++ b/src/hocon_tconf.erl @@ -587,7 +587,7 @@ map_field(?REF(Ref), FieldSchema, Value, #{schema := Schema} = Opts) -> map_field(Ref, FieldSchema, Value, #{schema := Schema} = Opts) when is_list(Ref) -> Fields = hocon_schema:fields(Schema, Ref), do_map(Fields, Value, Opts, FieldSchema); -map_field(?UNION(Types0), Schema0, Value, Opts) -> +map_field(?UNION(Types0, _), Schema0, Value, Opts) -> try select_union_members(Types0, Value, Opts) of Types -> F = fun(Type) -> @@ -898,7 +898,7 @@ is_path(Schema, ?ARRAY(Type), [Name | Path]) -> {true, _} -> is_path(Schema, Type, Path); false -> false end; -is_path(Schema, ?UNION(Types), Path) -> +is_path(Schema, ?UNION(Types, _), Path) -> lists:any(fun(T) -> is_path(Schema, T, Path) end, hoconsc:union_members(Types)); is_path(Schema, ?MAP(_, Type), [_ | Path]) -> is_path(Schema, Type, Path); diff --git a/src/hoconsc.erl b/src/hoconsc.erl index a3d8539..540a36e 100644 --- a/src/hoconsc.erl +++ b/src/hoconsc.erl @@ -19,7 +19,7 @@ -export([mk/1, mk/2]). -export([ref/1, ref/2]). --export([array/1, union/1, enum/1]). +-export([array/1, union/1, union/2, enum/1]). -export([lazy/1, map/2]). -export([is_schema/1]). -export([union_members/1]). @@ -59,7 +59,11 @@ array(OfType) -> ?ARRAY(OfType). %% `({value, #{<<"kind">> := <<"foo">>}) -> [ref(foo)];' %% `({value, #{<<"kind">> := <<"bar">>}} -> [ref(bar)].' -spec union(hocon_schema:union_members()) -> ?UNION(hocon_schema:union_members()). -union(OfTypes) when is_list(OfTypes) orelse is_function(OfTypes, 1) -> ?UNION(OfTypes). +union(OfTypes) when is_list(OfTypes) orelse is_function(OfTypes, 1) -> + ?UNION(OfTypes, undefined). + +union(OfTypes, DisplayName) when is_list(OfTypes) orelse is_function(OfTypes, 1) -> + ?UNION(OfTypes, DisplayName). %% @doc make a enum type. enum(OfSymbols) when is_list(OfSymbols) -> ?ENUM(OfSymbols). @@ -71,7 +75,7 @@ lazy(HintType) -> ?LAZY(HintType). map(Name, Type) -> ?MAP(Name, Type). %% @doc Check Type is a hocon type. -is_schema(?UNION(Members)) -> lists:all(fun is_schema/1, union_members(Members)); +is_schema(?UNION(Members, _)) -> lists:all(fun is_schema/1, union_members(Members)); is_schema(?ARRAY(ElemT)) -> is_schema(ElemT); is_schema(?LAZY(HintT)) -> is_schema(HintT); is_schema(?REF(_)) -> true; @@ -86,7 +90,7 @@ is_schema(_) -> false. assert_type(S) when is_function(S) -> error({expecting_type_but_got_schema, S}); assert_type(#{type := _} = S) -> error({expecting_type_but_got_schema, S}); -assert_type(?UNION(Members)) -> +assert_type(?UNION(Members, _)) -> lists:foreach(fun assert_type/1, union_members(Members)); assert_type(?ENUM(Symbols)) -> lists:foreach( diff --git a/test/hocon_tconf_tests.erl b/test/hocon_tconf_tests.erl index 2fd1d91..d628abe 100644 --- a/test/hocon_tconf_tests.erl +++ b/test/hocon_tconf_tests.erl @@ -590,7 +590,7 @@ with_envs(Fun, Envs) -> hocon_test_lib:with_envs(Fun, Envs). with_envs(Fun, Args, Envs) -> hocon_test_lib:with_envs(Fun, Args, Envs). union_as_enum_test() -> - Sc = #{roots => [{enum, hoconsc:union([a, b, c])}]}, + Sc = #{roots => [{enum, hoconsc:union([a, b, c], <<"string()">>)}]}, ?assertEqual( #{<<"enum">> => a}, hocon_tconf:check_plain(Sc, #{<<"enum">> => a})