diff --git a/src/ldclient_config.erl b/src/ldclient_config.erl index 9fa4f25..5a5306e 100644 --- a/src/ldclient_config.erl +++ b/src/ldclient_config.erl @@ -75,7 +75,7 @@ use_ldd => boolean(), send_events => boolean(), file_datasource => boolean(), - file_paths => [string()], + file_paths => [string() | binary()], file_auto_update => boolean(), file_poll_interval => pos_integer(), file_allow_duplicate_keys => boolean(), diff --git a/src/ldclient_instance.erl b/src/ldclient_instance.erl index e067e9f..2e7d77d 100644 --- a/src/ldclient_instance.erl +++ b/src/ldclient_instance.erl @@ -36,7 +36,7 @@ cache_ttl => integer(), send_events => boolean(), file_datasource => boolean(), - file_paths => [string()], + file_paths => [string() | binary()], file_auto_update => boolean(), file_poll_interval => pos_integer(), file_allow_duplicate_keys => boolean(), diff --git a/src/ldclient_update_file_server.erl b/src/ldclient_update_file_server.erl index d0881b7..97bc110 100644 --- a/src/ldclient_update_file_server.erl +++ b/src/ldclient_update_file_server.erl @@ -15,7 +15,7 @@ -export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -type state() :: #{ - file_paths := [string()], + file_paths := [string() | binary()], file_auto_update := boolean(), file_poll_interval := pos_integer(), file_allow_duplicate_keys := boolean(), @@ -155,7 +155,7 @@ upsert_valid_flag_data(true, Tag, ParsedFlags, ParsedSegments) -> upsert_valid_flag_data(false, _Tag, _ParsedFlags, _ParsedSegments) -> error. --spec read_file(FilePath :: string(), Extension :: string()) -> {ok | error, map()}. +-spec read_file(FilePath :: string() | binary(), Extension :: string()) -> {ok | error, map()}. read_file(FilePath, ".yaml") -> Result = yamerl_constr:file(FilePath, [{detailed_constr, true}]), [Head | _] = ldclient_yaml_mapper:to_map_docs(Result, []), @@ -167,7 +167,7 @@ read_file(FilePath, _Extension) -> error_logger:warning_msg("File had unrecognized file extension. Valid extensions are .yaml and .json. File: ~p", [FilePath]), {error, #{}}. --spec try_read_file(FilePath :: string(), Extension :: string()) -> {ok | error, map()}. +-spec try_read_file(FilePath :: string() | binary(), Extension :: string()) -> {ok | error, map()}. try_read_file(FilePath, Extension) -> try read_file(FilePath, Extension) @@ -176,7 +176,7 @@ try_read_file(FilePath, Extension) -> {error, #{}} end. --spec load_file(FilePath :: string(), Extension :: string(), State :: state(), LoadedFlags :: map(), LoadedSegments :: map(), FlagCount :: non_neg_integer()) -> +-spec load_file(FilePath :: string() | binary(), Extension :: string(), State :: state(), LoadedFlags :: map(), LoadedSegments :: map(), FlagCount :: non_neg_integer()) -> {ok | error, NewState :: state(), LoadedFlags :: map(), LoadedSegments :: map(), FlagCount :: non_neg_integer()}. load_file(FilePath, Extension, #{file_info := CurFileInfo} = State, LoadedFlags, LoadedSegments, FlagCount) -> ModifiedTime = filelib:last_modified(FilePath), @@ -196,7 +196,7 @@ process_decoded_file(ok, Decoded, State, LoadedFlags, LoadedSegments, FlagCount) process_decoded_file(error, _Decoded, State, LoadedFlags, LoadedSegments, FlagCount) -> {error, State, LoadedFlags, LoadedSegments, FlagCount}. --spec check_modified(FilesToCheck :: [string()], Modified :: boolean(), State :: state()) -> +-spec check_modified(FilesToCheck :: [string() | binary()], Modified :: boolean(), State :: state()) -> {Modified :: boolean(), State :: state()}. check_modified([], Modified, State) -> {Modified, State}; @@ -207,29 +207,29 @@ check_modified([FileToCheck | RestOfFiles], Modified, State) -> NewModified = Modified or (ExistingModifiedTime =/= ModifiedTime), check_modified(RestOfFiles, NewModified, State). --spec load_files_if_modified(Files :: [string()], State :: state()) -> {ok | error, State :: state()}. +-spec load_files_if_modified(Files :: [string() | binary()], State :: state()) -> {ok | error, State :: state()}. load_files_if_modified(Files, State) -> case check_modified(Files, false, State) of {true, UpdatedState} -> load_files(Files, UpdatedState); {false, UpdatedState} -> {ok, UpdatedState} end. --spec load_regular_file(FilePath :: string(), IsRegularFile :: boolean(), State :: state(), LoadedFlags :: map(), LoadedSegments :: map(), FlagCount :: non_neg_integer()) -> +-spec load_regular_file(FilePath :: string() | binary(), IsRegularFile :: boolean(), State :: state(), LoadedFlags :: map(), LoadedSegments :: map(), FlagCount :: non_neg_integer()) -> {ok | error, NewState :: state(), LoadedFlags :: map(), LoadedSegments :: map(), FlagCount :: non_neg_integer()}. load_regular_file(FileToLoad, true, State, LoadedFlags, LoadedSegments, FlagCount) -> load_file(FileToLoad, filename:extension(FileToLoad), State, LoadedFlags, LoadedSegments, FlagCount); load_regular_file(_FileToLoad, false, State, LoadedFlags, LoadedSegments, FlagCount) -> {error, State, LoadedFlags, LoadedSegments, FlagCount}. --spec load_files(Files :: [string()], State :: state()) -> {ok | error, State :: state()}. +-spec load_files(Files :: [string() | binary()], State :: state()) -> {ok | error, State :: state()}. load_files(Files, State) -> load_files(Files, State, #{}, #{}, 0, ok). --spec load_files(Files :: [string()], State :: state(), CombinedFlags :: map(), CombinedSegments :: map(), FlagCount :: non_neg_integer(), Status :: ok | error) -> +-spec load_files(Files :: [string() | binary()], State :: state(), CombinedFlags :: map(), CombinedSegments :: map(), FlagCount :: non_neg_integer(), Status :: ok | error) -> {ok | error, State :: state()}. load_files([FileToLoad | RestOfFiles], State, LoadedFlags, LoadedSegments, FlagCount, ok) -> {NewStatus, NewState, CombinedFlags, CombinedSegments, UpdatedCount} = - load_regular_file(FileToLoad, filelib:is_regular(FileToLoad), State, LoadedFlags, LoadedSegments, FlagCount), + load_regular_file(handle_file_path_type(FileToLoad), filelib:is_regular(handle_file_path_type(FileToLoad)), State, LoadedFlags, LoadedSegments, FlagCount), load_files(RestOfFiles, NewState, CombinedFlags, CombinedSegments, UpdatedCount, NewStatus); load_files([], #{file_allow_duplicate_keys := AllowDuplicateKeys, storage_tag := Tag} = State, LoadedFlags, LoadedSegments, FlagCount, ok) -> Valid = (FlagCount == length(maps:keys(LoadedFlags))) or AllowDuplicateKeys, @@ -238,3 +238,9 @@ load_files([], #{file_allow_duplicate_keys := AllowDuplicateKeys, storage_tag := load_files(_Files, State, _LoadedFlags, _LoadedSegments, _FlagCount, error) -> %% If there is an error, then do not process any more files. {error, State}. + +-spec handle_file_path_type(FilePath :: string() | binary()) -> string(). +handle_file_path_type(FilePath) when is_binary(FilePath) -> + binary_to_list(FilePath); +handle_file_path_type(FilePath) -> + FilePath. diff --git a/test/ldclient_file_SUITE.erl b/test/ldclient_file_SUITE.erl index 8261542..76fff29 100644 --- a/test/ldclient_file_SUITE.erl +++ b/test/ldclient_file_SUITE.erl @@ -18,6 +18,7 @@ check_file_simple_flags_yaml/1, check_file_all_properties_yaml/1, check_multiple_data_files/1, + check_binary_file_path/1, check_file_watcher/1, check_with_missing_file/1, check_with_only_missing_file/1, @@ -35,6 +36,7 @@ all() -> check_file_simple_flags_yaml, check_file_all_properties_yaml, check_multiple_data_files, + check_binary_file_path, check_file_watcher, check_with_missing_file, check_with_only_missing_file, @@ -91,6 +93,14 @@ init_per_suite(Config) -> }, ldclient:start_instance("", simple_flags_yaml, OptionsSimpleFlagsYaml), + OptionsBinaryPathYaml = #{ + datasource => file, + send_events => false, + file_paths => [list_to_binary(DataFileSimpleFlagsYaml)], + feature_store => ldclient_storage_map + }, + ldclient:start_instance("", binary_path_yaml, OptionsBinaryPathYaml), + DataFileAllPropertiesYaml = code:priv_dir(ldclient) ++ "/flags-all-properties.yaml", OptionsAllPropertiesYaml = #{ datasource => file, @@ -208,6 +218,11 @@ check_multiple_data_files(_) -> {{0, true, fallthrough}, _} = ldclient_eval:flag_key_for_context(all_properties_yaml, <<"my-boolean-flag-key">>, #{key => <<"user123">>, kind => <<"user">>}, "foo"), {{0, 3, fallthrough}, _} = ldclient_eval:flag_key_for_context(all_properties_yaml, <<"my-integer-flag-key">>, #{key => <<"user123">>, kind => <<"user">>}, "foo"). +check_binary_file_path(_) -> + {{0, <<"value-1">>, fallthrough}, _} = ldclient_eval:flag_key_for_context(binary_path_yaml, <<"my-string-flag-key">>, #{key => <<"user123">>, kind => <<"user">>}, "foo"), + {{0, true, fallthrough}, _} = ldclient_eval:flag_key_for_context(binary_path_yaml, <<"my-boolean-flag-key">>, #{key => <<"user123">>, kind => <<"user">>}, "foo"), + {{0, 3, fallthrough}, _} = ldclient_eval:flag_key_for_context(binary_path_yaml, <<"my-integer-flag-key">>, #{key => <<"user123">>, kind => <<"user">>}, "foo"). + check_file_watcher(_) -> {{0, true, fallthrough}, _} = ldclient_eval:flag_key_for_context(watch_files, <<"test-flag">>, #{key => <<"user123">>, kind => <<"user">>}, "foo"), %The timestamp for the modified time is in seconds. So we wait a moment to get a new timestamp compared to creation.