fluxer/fluxer_gateway/src/gateway/fluxer_gateway_config.erl

283 lines
11 KiB
Erlang

%% Copyright (C) 2026 Fluxer Contributors
%%
%% This file is part of Fluxer.
%%
%% Fluxer is free software: you can redistribute it and/or modify
%% it under the terms of the GNU Affero General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% Fluxer is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU Affero General Public License for more details.
%%
%% You should have received a copy of the GNU Affero General Public License
%% along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
-module(fluxer_gateway_config).
-export([load/0, load_from/1]).
-type config() :: map().
-type log_level() :: debug | info | notice | warning | error | critical | alert | emergency.
-spec load() -> config().
load() ->
case os:getenv("FLUXER_CONFIG") of
false -> erlang:error({missing_env, "FLUXER_CONFIG"});
"" -> erlang:error({missing_env, "FLUXER_CONFIG"});
Path -> load_from(Path)
end.
-spec load_from(string()) -> config().
load_from(Path) when is_list(Path) ->
case file:read_file(Path) of
{ok, Content} ->
Json = json:decode(Content),
build_config(Json);
{error, Reason} ->
erlang:error({json_read_failed, Path, Reason})
end.
-spec build_config(map()) -> config().
build_config(Json) ->
Service = get_map(Json, [<<"services">>, <<"gateway">>]),
Nats = get_map(Json, [<<"services">>, <<"nats">>]),
Telemetry = get_map(Json, [<<"telemetry">>]),
Sentry = get_map(Json, [<<"sentry">>]),
Vapid = get_map(Json, [<<"auth">>, <<"vapid">>]),
#{
port => get_int(Service, <<"port">>, 8080),
admin_reload_secret => get_optional_binary(Service, <<"admin_reload_secret">>),
nats_core_url => get_string(Nats, <<"core_url">>, "nats://127.0.0.1:4222"),
nats_auth_token => get_string(Nats, <<"auth_token">>, ""),
identify_rate_limit_enabled => get_bool(Service, <<"identify_rate_limit_enabled">>, false),
push_enabled => get_bool(Service, <<"push_enabled">>, true),
push_user_guild_settings_cache_mb => get_int(
Service,
<<"push_user_guild_settings_cache_mb">>,
1024
),
push_subscriptions_cache_mb => get_int(Service, <<"push_subscriptions_cache_mb">>, 1024),
push_blocked_ids_cache_mb => get_int(Service, <<"push_blocked_ids_cache_mb">>, 1024),
presence_cache_shards => get_optional_int(Service, <<"presence_cache_shards">>),
presence_bus_shards => get_optional_int(Service, <<"presence_bus_shards">>),
presence_shards => get_optional_int(Service, <<"presence_shards">>),
guild_shards => get_optional_int(Service, <<"guild_shards">>),
session_shards => get_optional_int(Service, <<"session_shards">>),
push_badge_counts_cache_mb => get_int(Service, <<"push_badge_counts_cache_mb">>, 256),
push_badge_counts_cache_ttl_seconds =>
get_int(Service, <<"push_badge_counts_cache_ttl_seconds">>, 60),
push_dispatcher_max_inflight => get_int(Service, <<"push_dispatcher_max_inflight">>, 16),
push_dispatcher_max_queue => get_int(Service, <<"push_dispatcher_max_queue">>, 2048),
gateway_http_push_connect_timeout_ms =>
get_int(Service, <<"gateway_http_push_connect_timeout_ms">>, 3000),
gateway_http_push_recv_timeout_ms =>
get_int(Service, <<"gateway_http_push_recv_timeout_ms">>, 5000),
gateway_http_rpc_max_concurrency =>
get_int(Service, <<"gateway_http_rpc_max_concurrency">>, 512),
gateway_http_push_max_concurrency =>
get_int(Service, <<"gateway_http_push_max_concurrency">>, 256),
gateway_http_failure_threshold =>
get_int(Service, <<"gateway_http_failure_threshold">>, 6),
gateway_http_recovery_timeout_ms =>
get_int(Service, <<"gateway_http_recovery_timeout_ms">>, 15000),
gateway_http_cleanup_interval_ms =>
get_int(Service, <<"gateway_http_cleanup_interval_ms">>, 30000),
gateway_http_cleanup_max_age_ms =>
get_int(Service, <<"gateway_http_cleanup_max_age_ms">>, 300000),
media_proxy_endpoint => get_optional_binary(Service, <<"media_proxy_endpoint">>),
vapid_email => get_binary(Vapid, <<"email">>, <<>>),
vapid_public_key => get_optional_binary(Vapid, <<"public_key">>),
vapid_private_key => get_optional_binary(Vapid, <<"private_key">>),
gateway_metrics_enabled => get_optional_bool(Service, <<"gateway_metrics_enabled">>),
gateway_metrics_report_interval_ms =>
get_optional_int(Service, <<"gateway_metrics_report_interval_ms">>),
release_node => get_string(Service, <<"release_node">>, "fluxer_gateway@127.0.0.1"),
logger_level => get_log_level(Service, <<"logger_level">>, info),
telemetry => #{
enabled => get_bool(Telemetry, <<"enabled">>, true),
otlp_endpoint => get_string(Telemetry, <<"otlp_endpoint">>, ""),
api_key => get_string(Telemetry, <<"api_key">>, ""),
service_name => get_string(Telemetry, <<"service_name">>, "fluxer-gateway"),
environment => get_string(Telemetry, <<"environment">>, "development"),
trace_sampling_ratio => get_float(Telemetry, <<"trace_sampling_ratio">>, 1.0)
},
sentry => #{
build_sha => get_string(Sentry, <<"build_sha">>, ""),
release_channel => get_string(Sentry, <<"release_channel">>, "")
}
}.
-spec get_map(map(), [binary()]) -> map().
get_map(Map, Keys) ->
case get_in(Map, Keys) of
Value when is_map(Value) -> Value;
_ -> #{}
end.
-spec get_int(map(), binary(), integer()) -> integer().
get_int(Map, Key, Default) when is_integer(Default) ->
to_int(get_value(Map, Key), Default).
-spec get_optional_int(map(), binary()) -> integer() | undefined.
get_optional_int(Map, Key) ->
to_optional_int(get_value(Map, Key)).
-spec get_bool(map(), binary(), boolean()) -> boolean().
get_bool(Map, Key, Default) when is_boolean(Default) ->
to_bool(get_value(Map, Key), Default).
-spec get_optional_bool(map(), binary()) -> boolean() | undefined.
get_optional_bool(Map, Key) ->
case get_value(Map, Key) of
undefined -> undefined;
Value -> to_bool(Value, undefined)
end.
-spec get_string(map(), binary(), string()) -> string().
get_string(Map, Key, Default) when is_list(Default) ->
to_string(get_value(Map, Key), Default).
-spec get_binary(map(), binary(), binary() | undefined) -> binary() | undefined.
get_binary(Map, Key, Default) ->
to_binary(get_value(Map, Key), Default).
-spec get_optional_binary(map(), binary()) -> binary() | undefined.
get_optional_binary(Map, Key) ->
case get_value(Map, Key) of
undefined -> undefined;
Value -> to_binary(Value, undefined)
end.
-spec get_log_level(map(), binary(), log_level()) -> log_level().
get_log_level(Map, Key, Default) when is_atom(Default) ->
Value = get_value(Map, Key),
case normalize_log_level(Value) of
undefined -> Default;
Level -> Level
end.
-spec get_float(map(), binary(), number()) -> float().
get_float(Map, Key, Default) when is_number(Default) ->
to_float(get_value(Map, Key), Default).
-spec get_in(term(), [binary()]) -> term().
get_in(Map, [Key | Rest]) when is_map(Map) ->
case get_value(Map, Key) of
undefined -> undefined;
Value when Rest =:= [] -> Value;
Value -> get_in(Value, Rest)
end;
get_in(_, _) ->
undefined.
-spec get_value(term(), binary()) -> term().
get_value(Map, Key) when is_map(Map) ->
case maps:get(Key, Map, undefined) of
undefined when is_binary(Key) ->
maps:get(binary_to_list(Key), Map, undefined);
Value ->
Value
end.
-spec to_int(term(), integer() | undefined) -> integer() | undefined.
to_int(Value, _Default) when is_integer(Value) ->
Value;
to_int(Value, _Default) when is_float(Value) ->
trunc(Value);
to_int(Value, Default) ->
case to_string(Value, "") of
"" ->
Default;
Str ->
case string:to_integer(Str) of
{Int, _} when is_integer(Int) -> Int;
{error, _} -> Default
end
end.
-spec to_optional_int(term()) -> integer() | undefined.
to_optional_int(Value) ->
case to_int(Value, undefined) of
undefined -> undefined;
Int -> Int
end.
-spec to_bool(term(), boolean() | undefined) -> boolean() | undefined.
to_bool(Value, _Default) when is_boolean(Value) ->
Value;
to_bool(Value, Default) when is_atom(Value) ->
case Value of
true -> true;
false -> false;
_ -> Default
end;
to_bool(Value, Default) ->
case string:lowercase(to_string(Value, "")) of
"true" -> true;
"1" -> true;
"false" -> false;
"0" -> false;
_ -> Default
end.
-spec to_string(term(), string()) -> string().
to_string(Value, Default) when is_list(Default) ->
case Value of
undefined -> Default;
Bin when is_binary(Bin) -> binary_to_list(Bin);
Str when is_list(Str) -> Str;
Atom when is_atom(Atom) -> atom_to_list(Atom);
_ -> Default
end.
-spec to_binary(term(), binary() | undefined) -> binary() | undefined.
to_binary(Value, Default) ->
case Value of
undefined -> Default;
Bin when is_binary(Bin) -> Bin;
Str when is_list(Str) -> list_to_binary(Str);
Atom when is_atom(Atom) -> list_to_binary(atom_to_list(Atom));
_ -> Default
end.
-spec to_float(term(), float()) -> float().
to_float(Value, _Default) when is_float(Value) ->
Value;
to_float(Value, _Default) when is_integer(Value) ->
float(Value);
to_float(Value, Default) ->
case to_string(Value, "") of
"" ->
Default;
Str ->
case string:to_float(Str) of
{Float, _} when is_float(Float) -> Float;
{error, _} -> Default
end
end.
-spec normalize_log_level(term()) -> log_level() | undefined.
normalize_log_level(undefined) ->
undefined;
normalize_log_level(Level) when is_atom(Level) ->
normalize_log_level(atom_to_list(Level));
normalize_log_level(Level) when is_binary(Level) ->
normalize_log_level(binary_to_list(Level));
normalize_log_level(Level) when is_list(Level) ->
case string:lowercase(string:trim(Level)) of
"debug" -> debug;
"info" -> info;
"notice" -> notice;
"warning" -> warning;
"error" -> error;
"critical" -> critical;
"alert" -> alert;
"emergency" -> emergency;
_ -> undefined
end;
normalize_log_level(_) ->
undefined.