Skip to content

Commit

Permalink
feat: add display_name to union type
Browse files Browse the repository at this point in the history
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)"
  • Loading branch information
zmstone committed Nov 8, 2023
1 parent 6de354f commit 0beb9be
Show file tree
Hide file tree
Showing 7 changed files with 22 additions and 16 deletions.
3 changes: 2 additions & 1 deletion include/hocon_types.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion sample-schemas/demo_schema.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
13 changes: 7 additions & 6 deletions src/hocon_schema.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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) ->
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)) ->
#{
Expand Down
2 changes: 1 addition & 1 deletion src/hocon_schema_example.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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} ->
Expand Down
4 changes: 2 additions & 2 deletions src/hocon_tconf.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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) ->
Expand Down Expand Up @@ -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);
Expand Down
12 changes: 8 additions & 4 deletions src/hoconsc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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]).
Expand Down Expand Up @@ -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).
Expand All @@ -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;
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion test/hocon_tconf_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down

0 comments on commit 0beb9be

Please sign in to comment.