Skip to content

Commit

Permalink
New data type: tagged string
Browse files Browse the repository at this point in the history
In some cases the string data type is not
expressive enough. It requires key-specific
handling in the application, that is,
the key name becomes hardcoded if the string
has any special meaning to the app.

By introducing a new data type we enforce
a convention where a schema can define
a "special" kind (in fact, multiple kinds)
of string and it will be expressed in the
generated value, which will be a tagged tuple.

This approach avoids parser extensions,
whether this means native syntax or
a complex data type extension such as
the duration data type.

Closes #40.
  • Loading branch information
michaelklishin committed Aug 3, 2024
1 parent e614d97 commit 1de230d
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/cuttlefish_conf.erl
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ pretty_datatype({duration, _}) -> "a time duration with units, e.g. '10s' for 10
pretty_datatype(bytesize) -> "a byte size with units, e.g. 10GB";
pretty_datatype({integer, I}) -> "the integer " ++ integer_to_list(I);
pretty_datatype({string, S}) -> "the text \"" ++ S ++ "\"";
pretty_datatype({tagged_string, {Tag, String}}) -> "the text \"" ++ String ++ "\"" ++ " tagged as \"" ++ Tag ++ "\"";
pretty_datatype({atom, A}) -> "the text \"" ++ atom_to_list(A) ++ "\"";
pretty_datatype({ip, {IP, Port}}) -> ?FMT("the address ~ts:~tp", [IP, Port]);
pretty_datatype({domain_socket, {local, Path, Port}}) ->
Expand Down
24 changes: 23 additions & 1 deletion src/cuttlefish_datatypes.erl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
{percent, integer} |
{percent, float} |
float |
tagged_string |
{list, datatype()}.
-type extended() :: { integer, integer() } |
{ string, string() } |
Expand All @@ -54,7 +55,8 @@
{ bytesize, string() } |
{ {percent, integer}, integer() } |
{ {percent, float}, float() } |
{ float, float() }.
{ float, float() } |
{ tagged_string, string() }.
-type datatype_list() :: [ datatype() | extended() ].

-export_type([datatype/0, extended/0, datatype_list/0]).
Expand Down Expand Up @@ -92,6 +94,7 @@ is_supported(bytesize) -> true;
is_supported({percent, integer}) -> true;
is_supported({percent, float}) -> true;
is_supported(float) -> true;
is_supported(tagged_string) -> true;
is_supported({list, {list, _}}) ->
% lists of lists are not supported
false;
Expand All @@ -102,6 +105,7 @@ is_supported(_) -> false.
-spec is_extended(any()) -> boolean().
is_extended({integer, I}) when is_integer(I) -> true;
is_extended({string, S}) when is_list(S) -> true;
is_extended({tagged_string, S}) when is_list(S) -> true;
is_extended({atom, A}) when is_atom(A) -> true;
is_extended({file, F}) when is_list(F) -> true;
is_extended({directory, D}) when is_list(D) -> true;
Expand All @@ -127,6 +131,7 @@ is_extended(_) -> false.
-spec extended_from(extended()) -> datatype().
extended_from({integer, _}) -> integer;
extended_from({string, _}) -> string;
extended_from({tagged_string, _}) -> tagged_string;
extended_from({atom, _}) -> atom;
extended_from({file, _}) -> file;
extended_from({directory, _}) -> directory;
Expand Down Expand Up @@ -184,6 +189,8 @@ to_string(Bytesize, bytesize) when is_integer(Bytesize) -> cuttlefish_bytesize:t

to_string(String, string) when is_list(String) -> String;

to_string({Tag, String}, tagged_string) when is_list(Tag), is_list(String) -> Tag ++ ":" ++ String;

to_string(File, file) when is_list(File) -> File;

to_string(Directory, directory) when is_list(Directory) -> Directory;
Expand Down Expand Up @@ -239,6 +246,9 @@ from_string({FQDN, Port}, fqdn) when is_list(FQDN), is_integer(Port) -> {FQDN, P
from_string(String, fqdn) when is_list(String) ->
from_string_to_fqdn(String, lists:split(string:rchr(String, $:), String));

from_string(String, tagged_string) when is_list(String) ->
from_string_to_tagged_string(String, lists:split(string:rchr(String, $:), String));

from_string({local, UDS, Port}, domain_socket) when is_list(UDS), is_integer(Port) -> {local, UDS, Port};
from_string(String, domain_socket) when is_list(String) ->
from_string_to_uds(String, lists:split(string:rchr(String, $:), String));
Expand Down Expand Up @@ -363,6 +373,14 @@ from_string_to_fqdn(String, {FQDNPlusColon, PortString}) ->
FQDN = droplast(FQDNPlusColon),
fqdn_conversions(String, FQDN, validate_fqdn(FQDN), port_to_integer(PortString)).

from_string_to_tagged_string(String, {[], String}) ->
%% does not follow the tag:value format convention
{error, {conversion, {String, "tagged string"}}};
from_string_to_tagged_string(_String, {TagPlusColon, TaggedValue}) ->
%% Drop the trailing colon from the tag
Tag = droplast(TagPlusColon),
{list_to_atom(Tag), TaggedValue}.

from_string_to_uds(String, {[], String}) ->
{error, {conversion, {String, 'UDS'}}};
from_string_to_uds(String, {UDSPlusColon, PortString}) ->
Expand Down Expand Up @@ -642,6 +660,7 @@ is_supported_test() ->
?assert(is_supported({duration, ms})),
?assert(is_supported(bytesize)),
?assert(is_supported(domain_socket)),
?assert(is_supported(tagged_string)),
?assert(is_supported({list, string})),
?assert(not(is_supported({list, {list, string}}))),
?assert(not(is_supported(some_unsupported_type))),
Expand Down Expand Up @@ -691,6 +710,9 @@ is_extended_test() ->
?assertEqual(true, is_extended({{percent, float}, "10%"})),
?assertEqual(true, is_extended({{percent, float}, 0.1})),
?assertEqual(true, is_extended({float, 0.1})),

?assertEqual(true, is_extended({tagged_string, "tag:value"})),

ok.

-endif.
2 changes: 1 addition & 1 deletion src/cuttlefish_escript.erl
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ parse_and_command(Args) ->
main(Args) ->
{Command, ParsedArgs, Extra} = parse_and_command(Args),

SuggestedLogLevel = list_to_atom(proplists:get_value(log_level, ParsedArgs)),
SuggestedLogLevel = list_to_atom(proplists:get_value(log_level, ParsedArgs, "notice")),
LogLevel = case lists:member(SuggestedLogLevel, [debug, info, notice, warning, error, critical, alert, emergency]) of
true -> SuggestedLogLevel;
_ -> notice
Expand Down
3 changes: 2 additions & 1 deletion src/cuttlefish_generator.erl
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,8 @@ transform_supported_type(DT, Value) ->
{error, Message} -> {error, Message};
NewValue -> {ok, NewValue}
catch
Class:Error ->
Class:Error:_Stacktrace ->
%% io:format("Failed to transform a type. Stacktrace: ~p~n", [Stacktrace]),
{error, {transform_type_exception, {DT, {Class, Error}}}}
end.

Expand Down
7 changes: 7 additions & 0 deletions test/cuttlefish_integration_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ duration_test() ->
ErrConfig = cuttlefish_generator:map(Schema, Conf2),
?assertMatch({error, transform_datatypes, _}, ErrConfig).

tagged_string_test() ->
Schema = cuttlefish_schema:files(["test/tagged_string.schema"]),

Conf = conf_parse:parse(<<"tagged_key = tagged:e614d97599dab483f\n">>),
NewConfig = cuttlefish_generator:map(Schema, Conf),
?assertEqual({tagged, "e614d97599dab483f"}, proplists:get_value(tagged_key, proplists:get_value(cuttlefish, NewConfig))).

proplist_equals(Expected, Actual) ->
ExpectedKeys = lists:sort(proplists:get_keys(Expected)),
ActualKeys = lists:sort(proplists:get_keys(Actual)),
Expand Down
3 changes: 3 additions & 0 deletions test/tagged_string.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{mapping, "tagged_key", "cuttlefish.tagged_key", [
{datatype, [tagged_string]}
]}.

0 comments on commit 1de230d

Please sign in to comment.