refactor progress
This commit is contained in:
@@ -30,3 +30,29 @@ calculate(Attempt) ->
|
||||
calculate(Attempt, MaxMs) ->
|
||||
BackoffMs = round(1000 * math:pow(2, Attempt)),
|
||||
min(BackoffMs, MaxMs).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
calculate_default_max_test() ->
|
||||
?assertEqual(1000, calculate(0)),
|
||||
?assertEqual(2000, calculate(1)),
|
||||
?assertEqual(4000, calculate(2)),
|
||||
?assertEqual(8000, calculate(3)),
|
||||
?assertEqual(16000, calculate(4)),
|
||||
?assertEqual(30000, calculate(5)),
|
||||
?assertEqual(30000, calculate(10)).
|
||||
|
||||
calculate_custom_max_test() ->
|
||||
?assertEqual(1000, calculate(0, 5000)),
|
||||
?assertEqual(2000, calculate(1, 5000)),
|
||||
?assertEqual(4000, calculate(2, 5000)),
|
||||
?assertEqual(5000, calculate(3, 5000)),
|
||||
?assertEqual(5000, calculate(10, 5000)).
|
||||
|
||||
calculate_small_max_test() ->
|
||||
?assertEqual(1000, calculate(0, 1000)),
|
||||
?assertEqual(1000, calculate(1, 1000)),
|
||||
?assertEqual(1000, calculate(5, 1000)).
|
||||
|
||||
-endif.
|
||||
|
||||
@@ -35,10 +35,12 @@
|
||||
speak_permission/0,
|
||||
stream_permission/0,
|
||||
use_vad_permission/0,
|
||||
read_message_history_permission/0,
|
||||
kick_members_permission/0,
|
||||
ban_members_permission/0
|
||||
]).
|
||||
|
||||
-spec gateway_opcode(integer()) -> atom().
|
||||
gateway_opcode(0) -> dispatch;
|
||||
gateway_opcode(1) -> heartbeat;
|
||||
gateway_opcode(2) -> identify;
|
||||
@@ -52,10 +54,10 @@ gateway_opcode(9) -> invalid_session;
|
||||
gateway_opcode(10) -> hello;
|
||||
gateway_opcode(11) -> heartbeat_ack;
|
||||
gateway_opcode(12) -> gateway_error;
|
||||
gateway_opcode(13) -> call_connect;
|
||||
gateway_opcode(14) -> lazy_request;
|
||||
gateway_opcode(_) -> unknown.
|
||||
|
||||
-spec opcode_to_num(atom()) -> integer().
|
||||
opcode_to_num(dispatch) -> 0;
|
||||
opcode_to_num(heartbeat) -> 1;
|
||||
opcode_to_num(identify) -> 2;
|
||||
@@ -69,9 +71,9 @@ opcode_to_num(invalid_session) -> 9;
|
||||
opcode_to_num(hello) -> 10;
|
||||
opcode_to_num(heartbeat_ack) -> 11;
|
||||
opcode_to_num(gateway_error) -> 12;
|
||||
opcode_to_num(call_connect) -> 13;
|
||||
opcode_to_num(lazy_request) -> 14.
|
||||
|
||||
-spec close_code_to_num(atom()) -> integer().
|
||||
close_code_to_num(unknown_error) -> 4000;
|
||||
close_code_to_num(unknown_opcode) -> 4001;
|
||||
close_code_to_num(decode_error) -> 4002;
|
||||
@@ -83,264 +85,16 @@ close_code_to_num(rate_limited) -> 4008;
|
||||
close_code_to_num(session_timeout) -> 4009;
|
||||
close_code_to_num(invalid_shard) -> 4010;
|
||||
close_code_to_num(sharding_required) -> 4011;
|
||||
close_code_to_num(invalid_api_version) -> 4012.
|
||||
close_code_to_num(invalid_api_version) -> 4012;
|
||||
close_code_to_num(ack_backpressure) -> 4013.
|
||||
|
||||
dispatch_event_atom(<<"READY">>) ->
|
||||
ready;
|
||||
dispatch_event_atom(<<"RESUMED">>) ->
|
||||
resumed;
|
||||
dispatch_event_atom(<<"SESSIONS_REPLACE">>) ->
|
||||
sessions_replace;
|
||||
dispatch_event_atom(<<"USER_UPDATE">>) ->
|
||||
user_update;
|
||||
dispatch_event_atom(<<"USER_SETTINGS_UPDATE">>) ->
|
||||
user_settings_update;
|
||||
dispatch_event_atom(<<"USER_GUILD_SETTINGS_UPDATE">>) ->
|
||||
user_guild_settings_update;
|
||||
dispatch_event_atom(<<"USER_PINNED_DMS_UPDATE">>) ->
|
||||
user_pinned_dms_update;
|
||||
dispatch_event_atom(<<"USER_NOTE_UPDATE">>) ->
|
||||
user_note_update;
|
||||
dispatch_event_atom(<<"RECENT_MENTION_DELETE">>) ->
|
||||
recent_mention_delete;
|
||||
dispatch_event_atom(<<"SAVED_MESSAGE_CREATE">>) ->
|
||||
saved_message_create;
|
||||
dispatch_event_atom(<<"SAVED_MESSAGE_DELETE">>) ->
|
||||
saved_message_delete;
|
||||
dispatch_event_atom(<<"AUTH_SESSION_CHANGE">>) ->
|
||||
auth_session_change;
|
||||
dispatch_event_atom(<<"PRESENCE_UPDATE">>) ->
|
||||
presence_update;
|
||||
dispatch_event_atom(<<"GUILD_CREATE">>) ->
|
||||
guild_create;
|
||||
dispatch_event_atom(<<"GUILD_UPDATE">>) ->
|
||||
guild_update;
|
||||
dispatch_event_atom(<<"GUILD_DELETE">>) ->
|
||||
guild_delete;
|
||||
dispatch_event_atom(<<"GUILD_MEMBER_ADD">>) ->
|
||||
guild_member_add;
|
||||
dispatch_event_atom(<<"GUILD_MEMBER_UPDATE">>) ->
|
||||
guild_member_update;
|
||||
dispatch_event_atom(<<"GUILD_MEMBER_REMOVE">>) ->
|
||||
guild_member_remove;
|
||||
dispatch_event_atom(<<"GUILD_ROLE_CREATE">>) ->
|
||||
guild_role_create;
|
||||
dispatch_event_atom(<<"GUILD_ROLE_UPDATE">>) ->
|
||||
guild_role_update;
|
||||
dispatch_event_atom(<<"GUILD_ROLE_UPDATE_BULK">>) ->
|
||||
guild_role_update_bulk;
|
||||
dispatch_event_atom(<<"GUILD_ROLE_DELETE">>) ->
|
||||
guild_role_delete;
|
||||
dispatch_event_atom(<<"GUILD_EMOJIS_UPDATE">>) ->
|
||||
guild_emojis_update;
|
||||
dispatch_event_atom(<<"GUILD_STICKERS_UPDATE">>) ->
|
||||
guild_stickers_update;
|
||||
dispatch_event_atom(<<"GUILD_BAN_ADD">>) ->
|
||||
guild_ban_add;
|
||||
dispatch_event_atom(<<"GUILD_BAN_REMOVE">>) ->
|
||||
guild_ban_remove;
|
||||
dispatch_event_atom(<<"GUILD_MEMBERS_CHUNK">>) ->
|
||||
guild_members_chunk;
|
||||
dispatch_event_atom(<<"CHANNEL_CREATE">>) ->
|
||||
channel_create;
|
||||
dispatch_event_atom(<<"CHANNEL_UPDATE">>) ->
|
||||
channel_update;
|
||||
dispatch_event_atom(<<"CHANNEL_UPDATE_BULK">>) ->
|
||||
channel_update_bulk;
|
||||
dispatch_event_atom(<<"PASSIVE_UPDATES">>) ->
|
||||
passive_updates;
|
||||
dispatch_event_atom(<<"CHANNEL_DELETE">>) ->
|
||||
channel_delete;
|
||||
dispatch_event_atom(<<"CHANNEL_RECIPIENT_ADD">>) ->
|
||||
channel_recipient_add;
|
||||
dispatch_event_atom(<<"CHANNEL_RECIPIENT_REMOVE">>) ->
|
||||
channel_recipient_remove;
|
||||
dispatch_event_atom(<<"CHANNEL_PINS_UPDATE">>) ->
|
||||
channel_pins_update;
|
||||
dispatch_event_atom(<<"CHANNEL_PINS_ACK">>) ->
|
||||
channel_pins_ack;
|
||||
dispatch_event_atom(<<"INVITE_CREATE">>) ->
|
||||
invite_create;
|
||||
dispatch_event_atom(<<"INVITE_DELETE">>) ->
|
||||
invite_delete;
|
||||
dispatch_event_atom(<<"MESSAGE_CREATE">>) ->
|
||||
message_create;
|
||||
dispatch_event_atom(<<"MESSAGE_UPDATE">>) ->
|
||||
message_update;
|
||||
dispatch_event_atom(<<"MESSAGE_DELETE">>) ->
|
||||
message_delete;
|
||||
dispatch_event_atom(<<"MESSAGE_DELETE_BULK">>) ->
|
||||
message_delete_bulk;
|
||||
dispatch_event_atom(<<"MESSAGE_REACTION_ADD">>) ->
|
||||
message_reaction_add;
|
||||
dispatch_event_atom(<<"MESSAGE_REACTION_REMOVE">>) ->
|
||||
message_reaction_remove;
|
||||
dispatch_event_atom(<<"MESSAGE_REACTION_REMOVE_ALL">>) ->
|
||||
message_reaction_remove_all;
|
||||
dispatch_event_atom(<<"MESSAGE_REACTION_REMOVE_EMOJI">>) ->
|
||||
message_reaction_remove_emoji;
|
||||
dispatch_event_atom(<<"MESSAGE_ACK">>) ->
|
||||
message_ack;
|
||||
dispatch_event_atom(<<"TYPING_START">>) ->
|
||||
typing_start;
|
||||
dispatch_event_atom(<<"WEBHOOKS_UPDATE">>) ->
|
||||
webhooks_update;
|
||||
dispatch_event_atom(<<"RELATIONSHIP_ADD">>) ->
|
||||
relationship_add;
|
||||
dispatch_event_atom(<<"RELATIONSHIP_UPDATE">>) ->
|
||||
relationship_update;
|
||||
dispatch_event_atom(<<"RELATIONSHIP_REMOVE">>) ->
|
||||
relationship_remove;
|
||||
dispatch_event_atom(<<"VOICE_STATE_UPDATE">>) ->
|
||||
voice_state_update;
|
||||
dispatch_event_atom(<<"VOICE_SERVER_UPDATE">>) ->
|
||||
voice_server_update;
|
||||
dispatch_event_atom(<<"FAVORITE_MEME_CREATE">>) ->
|
||||
favorite_meme_create;
|
||||
dispatch_event_atom(<<"FAVORITE_MEME_UPDATE">>) ->
|
||||
favorite_meme_update;
|
||||
dispatch_event_atom(<<"FAVORITE_MEME_DELETE">>) ->
|
||||
favorite_meme_delete;
|
||||
dispatch_event_atom(<<"CALL_CREATE">>) ->
|
||||
call_create;
|
||||
dispatch_event_atom(<<"CALL_UPDATE">>) ->
|
||||
call_update;
|
||||
dispatch_event_atom(<<"CALL_DELETE">>) ->
|
||||
call_delete;
|
||||
dispatch_event_atom(<<"GUILD_MEMBER_LIST_UPDATE">>) ->
|
||||
guild_member_list_update;
|
||||
dispatch_event_atom(<<"GUILD_SYNC">>) ->
|
||||
guild_sync;
|
||||
dispatch_event_atom(ready) ->
|
||||
<<"READY">>;
|
||||
dispatch_event_atom(resumed) ->
|
||||
<<"RESUMED">>;
|
||||
dispatch_event_atom(sessions_replace) ->
|
||||
<<"SESSIONS_REPLACE">>;
|
||||
dispatch_event_atom(user_update) ->
|
||||
<<"USER_UPDATE">>;
|
||||
dispatch_event_atom(user_settings_update) ->
|
||||
<<"USER_SETTINGS_UPDATE">>;
|
||||
dispatch_event_atom(user_guild_settings_update) ->
|
||||
<<"USER_GUILD_SETTINGS_UPDATE">>;
|
||||
dispatch_event_atom(user_pinned_dms_update) ->
|
||||
<<"USER_PINNED_DMS_UPDATE">>;
|
||||
dispatch_event_atom(user_note_update) ->
|
||||
<<"USER_NOTE_UPDATE">>;
|
||||
dispatch_event_atom(recent_mention_delete) ->
|
||||
<<"RECENT_MENTION_DELETE">>;
|
||||
dispatch_event_atom(saved_message_create) ->
|
||||
<<"SAVED_MESSAGE_CREATE">>;
|
||||
dispatch_event_atom(saved_message_delete) ->
|
||||
<<"SAVED_MESSAGE_DELETE">>;
|
||||
dispatch_event_atom(auth_session_change) ->
|
||||
<<"AUTH_SESSION_CHANGE">>;
|
||||
dispatch_event_atom(presence_update) ->
|
||||
<<"PRESENCE_UPDATE">>;
|
||||
dispatch_event_atom(guild_create) ->
|
||||
<<"GUILD_CREATE">>;
|
||||
dispatch_event_atom(guild_update) ->
|
||||
<<"GUILD_UPDATE">>;
|
||||
dispatch_event_atom(guild_delete) ->
|
||||
<<"GUILD_DELETE">>;
|
||||
dispatch_event_atom(guild_member_add) ->
|
||||
<<"GUILD_MEMBER_ADD">>;
|
||||
dispatch_event_atom(guild_member_update) ->
|
||||
<<"GUILD_MEMBER_UPDATE">>;
|
||||
dispatch_event_atom(guild_member_remove) ->
|
||||
<<"GUILD_MEMBER_REMOVE">>;
|
||||
dispatch_event_atom(guild_role_create) ->
|
||||
<<"GUILD_ROLE_CREATE">>;
|
||||
dispatch_event_atom(guild_role_update) ->
|
||||
<<"GUILD_ROLE_UPDATE">>;
|
||||
dispatch_event_atom(guild_role_update_bulk) ->
|
||||
<<"GUILD_ROLE_UPDATE_BULK">>;
|
||||
dispatch_event_atom(guild_role_delete) ->
|
||||
<<"GUILD_ROLE_DELETE">>;
|
||||
dispatch_event_atom(guild_emojis_update) ->
|
||||
<<"GUILD_EMOJIS_UPDATE">>;
|
||||
dispatch_event_atom(guild_stickers_update) ->
|
||||
<<"GUILD_STICKERS_UPDATE">>;
|
||||
dispatch_event_atom(guild_ban_add) ->
|
||||
<<"GUILD_BAN_ADD">>;
|
||||
dispatch_event_atom(guild_ban_remove) ->
|
||||
<<"GUILD_BAN_REMOVE">>;
|
||||
dispatch_event_atom(guild_members_chunk) ->
|
||||
<<"GUILD_MEMBERS_CHUNK">>;
|
||||
dispatch_event_atom(channel_create) ->
|
||||
<<"CHANNEL_CREATE">>;
|
||||
dispatch_event_atom(channel_update) ->
|
||||
<<"CHANNEL_UPDATE">>;
|
||||
dispatch_event_atom(channel_update_bulk) ->
|
||||
<<"CHANNEL_UPDATE_BULK">>;
|
||||
dispatch_event_atom(passive_updates) ->
|
||||
<<"PASSIVE_UPDATES">>;
|
||||
dispatch_event_atom(channel_delete) ->
|
||||
<<"CHANNEL_DELETE">>;
|
||||
dispatch_event_atom(channel_recipient_add) ->
|
||||
<<"CHANNEL_RECIPIENT_ADD">>;
|
||||
dispatch_event_atom(channel_recipient_remove) ->
|
||||
<<"CHANNEL_RECIPIENT_REMOVE">>;
|
||||
dispatch_event_atom(channel_pins_update) ->
|
||||
<<"CHANNEL_PINS_UPDATE">>;
|
||||
dispatch_event_atom(channel_pins_ack) ->
|
||||
<<"CHANNEL_PINS_ACK">>;
|
||||
dispatch_event_atom(invite_create) ->
|
||||
<<"INVITE_CREATE">>;
|
||||
dispatch_event_atom(invite_delete) ->
|
||||
<<"INVITE_DELETE">>;
|
||||
dispatch_event_atom(message_create) ->
|
||||
<<"MESSAGE_CREATE">>;
|
||||
dispatch_event_atom(message_update) ->
|
||||
<<"MESSAGE_UPDATE">>;
|
||||
dispatch_event_atom(message_delete) ->
|
||||
<<"MESSAGE_DELETE">>;
|
||||
dispatch_event_atom(message_delete_bulk) ->
|
||||
<<"MESSAGE_DELETE_BULK">>;
|
||||
dispatch_event_atom(message_reaction_add) ->
|
||||
<<"MESSAGE_REACTION_ADD">>;
|
||||
dispatch_event_atom(message_reaction_remove) ->
|
||||
<<"MESSAGE_REACTION_REMOVE">>;
|
||||
dispatch_event_atom(message_reaction_remove_all) ->
|
||||
<<"MESSAGE_REACTION_REMOVE_ALL">>;
|
||||
dispatch_event_atom(message_reaction_remove_emoji) ->
|
||||
<<"MESSAGE_REACTION_REMOVE_EMOJI">>;
|
||||
dispatch_event_atom(message_ack) ->
|
||||
<<"MESSAGE_ACK">>;
|
||||
dispatch_event_atom(typing_start) ->
|
||||
<<"TYPING_START">>;
|
||||
dispatch_event_atom(webhooks_update) ->
|
||||
<<"WEBHOOKS_UPDATE">>;
|
||||
dispatch_event_atom(relationship_add) ->
|
||||
<<"RELATIONSHIP_ADD">>;
|
||||
dispatch_event_atom(relationship_update) ->
|
||||
<<"RELATIONSHIP_UPDATE">>;
|
||||
dispatch_event_atom(relationship_remove) ->
|
||||
<<"RELATIONSHIP_REMOVE">>;
|
||||
dispatch_event_atom(voice_state_update) ->
|
||||
<<"VOICE_STATE_UPDATE">>;
|
||||
dispatch_event_atom(voice_server_update) ->
|
||||
<<"VOICE_SERVER_UPDATE">>;
|
||||
dispatch_event_atom(favorite_meme_create) ->
|
||||
<<"FAVORITE_MEME_CREATE">>;
|
||||
dispatch_event_atom(favorite_meme_update) ->
|
||||
<<"FAVORITE_MEME_UPDATE">>;
|
||||
dispatch_event_atom(favorite_meme_delete) ->
|
||||
<<"FAVORITE_MEME_DELETE">>;
|
||||
dispatch_event_atom(call_create) ->
|
||||
<<"CALL_CREATE">>;
|
||||
dispatch_event_atom(call_update) ->
|
||||
<<"CALL_UPDATE">>;
|
||||
dispatch_event_atom(call_delete) ->
|
||||
<<"CALL_DELETE">>;
|
||||
dispatch_event_atom(guild_member_list_update) ->
|
||||
<<"GUILD_MEMBER_LIST_UPDATE">>;
|
||||
dispatch_event_atom(guild_sync) ->
|
||||
<<"GUILD_SYNC">>;
|
||||
dispatch_event_atom(EventBinary) when is_binary(EventBinary) -> EventBinary;
|
||||
dispatch_event_atom(EventAtom) when is_atom(EventAtom) ->
|
||||
list_to_binary(string:uppercase(atom_to_list(EventAtom))).
|
||||
-spec dispatch_event_atom(atom() | binary()) -> atom() | binary().
|
||||
dispatch_event_atom(Event) when is_atom(Event) ->
|
||||
list_to_binary(string:uppercase(atom_to_list(Event)));
|
||||
dispatch_event_atom(EventBinary) when is_binary(EventBinary) ->
|
||||
event_atoms:normalize(EventBinary).
|
||||
|
||||
-spec status_type_atom(binary() | atom()) -> atom() | binary().
|
||||
status_type_atom(<<"online">>) -> online;
|
||||
status_type_atom(<<"dnd">>) -> dnd;
|
||||
status_type_atom(<<"idle">>) -> idle;
|
||||
@@ -352,17 +106,89 @@ status_type_atom(idle) -> <<"idle">>;
|
||||
status_type_atom(invisible) -> <<"invisible">>;
|
||||
status_type_atom(offline) -> <<"offline">>.
|
||||
|
||||
-spec max_payload_size() -> pos_integer().
|
||||
max_payload_size() -> 4096.
|
||||
|
||||
-spec heartbeat_interval() -> pos_integer().
|
||||
heartbeat_interval() -> 41250.
|
||||
|
||||
-spec heartbeat_timeout() -> pos_integer().
|
||||
heartbeat_timeout() -> 45000.
|
||||
|
||||
-spec random_session_bytes() -> pos_integer().
|
||||
random_session_bytes() -> 16.
|
||||
|
||||
-spec view_channel_permission() -> pos_integer().
|
||||
view_channel_permission() -> 1024.
|
||||
|
||||
-spec administrator_permission() -> pos_integer().
|
||||
administrator_permission() -> 8.
|
||||
|
||||
-spec manage_roles_permission() -> pos_integer().
|
||||
manage_roles_permission() -> 268435456.
|
||||
|
||||
-spec manage_channels_permission() -> pos_integer().
|
||||
manage_channels_permission() -> 16.
|
||||
|
||||
-spec connect_permission() -> pos_integer().
|
||||
connect_permission() -> 1048576.
|
||||
|
||||
-spec speak_permission() -> pos_integer().
|
||||
speak_permission() -> 2097152.
|
||||
|
||||
-spec stream_permission() -> pos_integer().
|
||||
stream_permission() -> 512.
|
||||
|
||||
-spec use_vad_permission() -> pos_integer().
|
||||
use_vad_permission() -> 33554432.
|
||||
|
||||
-spec read_message_history_permission() -> pos_integer().
|
||||
read_message_history_permission() -> 65536.
|
||||
|
||||
-spec kick_members_permission() -> pos_integer().
|
||||
kick_members_permission() -> 2.
|
||||
|
||||
-spec ban_members_permission() -> pos_integer().
|
||||
ban_members_permission() -> 4.
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
gateway_opcode_test() ->
|
||||
?assertEqual(dispatch, gateway_opcode(0)),
|
||||
?assertEqual(heartbeat, gateway_opcode(1)),
|
||||
?assertEqual(identify, gateway_opcode(2)),
|
||||
?assertEqual(unknown, gateway_opcode(999)).
|
||||
|
||||
opcode_to_num_test() ->
|
||||
?assertEqual(0, opcode_to_num(dispatch)),
|
||||
?assertEqual(1, opcode_to_num(heartbeat)),
|
||||
?assertEqual(2, opcode_to_num(identify)).
|
||||
|
||||
close_code_to_num_test() ->
|
||||
?assertEqual(4000, close_code_to_num(unknown_error)),
|
||||
?assertEqual(4004, close_code_to_num(authentication_failed)),
|
||||
?assertEqual(4008, close_code_to_num(rate_limited)),
|
||||
?assertEqual(4013, close_code_to_num(ack_backpressure)).
|
||||
|
||||
status_type_atom_binary_to_atom_test() ->
|
||||
?assertEqual(online, status_type_atom(<<"online">>)),
|
||||
?assertEqual(dnd, status_type_atom(<<"dnd">>)),
|
||||
?assertEqual(idle, status_type_atom(<<"idle">>)),
|
||||
?assertEqual(invisible, status_type_atom(<<"invisible">>)),
|
||||
?assertEqual(offline, status_type_atom(<<"offline">>)).
|
||||
|
||||
status_type_atom_atom_to_binary_test() ->
|
||||
?assertEqual(<<"online">>, status_type_atom(online)),
|
||||
?assertEqual(<<"dnd">>, status_type_atom(dnd)),
|
||||
?assertEqual(<<"idle">>, status_type_atom(idle)).
|
||||
|
||||
constants_values_test() ->
|
||||
?assertEqual(4096, max_payload_size()),
|
||||
?assertEqual(41250, heartbeat_interval()),
|
||||
?assertEqual(45000, heartbeat_timeout()),
|
||||
?assertEqual(16, random_session_bytes()),
|
||||
?assertEqual(1024, view_channel_permission()),
|
||||
?assertEqual(8, administrator_permission()).
|
||||
|
||||
-endif.
|
||||
|
||||
@@ -17,17 +17,16 @@
|
||||
|
||||
-module(custom_status_validation).
|
||||
|
||||
-export([
|
||||
validate/2
|
||||
]).
|
||||
-export([validate/2]).
|
||||
|
||||
-spec validate(integer(), map() | null) -> {ok, map()} | {error, term()}.
|
||||
-spec validate(integer(), map() | null) -> {ok, map() | null} | {error, term()}.
|
||||
validate(_UserId, null) ->
|
||||
{ok, null};
|
||||
validate(UserId, CustomStatus) when is_map(CustomStatus) ->
|
||||
Request = build_request(UserId, CustomStatus),
|
||||
rpc_client:call(Request).
|
||||
|
||||
-spec build_request(integer(), map()) -> map().
|
||||
build_request(UserId, CustomStatus) ->
|
||||
#{
|
||||
<<"type">> => <<"validate_custom_status">>,
|
||||
@@ -35,29 +34,56 @@ build_request(UserId, CustomStatus) ->
|
||||
<<"custom_status">> => build_custom_status_payload(CustomStatus)
|
||||
}.
|
||||
|
||||
-spec build_custom_status_payload(map()) -> map().
|
||||
build_custom_status_payload(CustomStatus) ->
|
||||
Field1 = put_optional_field(
|
||||
maps:new(),
|
||||
<<"text">>,
|
||||
maps:get(<<"text">>, CustomStatus, undefined)
|
||||
),
|
||||
Field2 = put_optional_field(
|
||||
Field1,
|
||||
<<"expires_at">>,
|
||||
maps:get(<<"expires_at">>, CustomStatus, undefined)
|
||||
),
|
||||
Field3 = put_optional_field(
|
||||
Field2,
|
||||
<<"emoji_id">>,
|
||||
maps:get(<<"emoji_id">>, CustomStatus, undefined)
|
||||
),
|
||||
put_optional_field(
|
||||
Field3,
|
||||
<<"emoji_name">>,
|
||||
maps:get(<<"emoji_name">>, CustomStatus, undefined)
|
||||
Fields = [
|
||||
{<<"text">>, maps:get(<<"text">>, CustomStatus, undefined)},
|
||||
{<<"expires_at">>, maps:get(<<"expires_at">>, CustomStatus, undefined)},
|
||||
{<<"emoji_id">>, maps:get(<<"emoji_id">>, CustomStatus, undefined)},
|
||||
{<<"emoji_name">>, maps:get(<<"emoji_name">>, CustomStatus, undefined)}
|
||||
],
|
||||
lists:foldl(
|
||||
fun
|
||||
({_Key, undefined}, Acc) -> Acc;
|
||||
({Key, Value}, Acc) -> maps:put(Key, Value, Acc)
|
||||
end,
|
||||
#{},
|
||||
Fields
|
||||
).
|
||||
|
||||
put_optional_field(Map, _Key, undefined) ->
|
||||
Map;
|
||||
put_optional_field(Map, Key, Value) ->
|
||||
maps:put(Key, Value, Map).
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
validate_null_test() ->
|
||||
?assertEqual({ok, null}, validate(123, null)).
|
||||
|
||||
build_custom_status_payload_all_fields_test() ->
|
||||
Input = #{
|
||||
<<"text">> => <<"Hello">>,
|
||||
<<"expires_at">> => <<"2024-01-01T00:00:00Z">>,
|
||||
<<"emoji_id">> => <<"123">>,
|
||||
<<"emoji_name">> => <<"smile">>
|
||||
},
|
||||
Result = build_custom_status_payload(Input),
|
||||
?assertEqual(<<"Hello">>, maps:get(<<"text">>, Result)),
|
||||
?assertEqual(<<"2024-01-01T00:00:00Z">>, maps:get(<<"expires_at">>, Result)),
|
||||
?assertEqual(<<"123">>, maps:get(<<"emoji_id">>, Result)),
|
||||
?assertEqual(<<"smile">>, maps:get(<<"emoji_name">>, Result)).
|
||||
|
||||
build_custom_status_payload_partial_test() ->
|
||||
Input = #{<<"text">> => <<"Hello">>},
|
||||
Result = build_custom_status_payload(Input),
|
||||
?assertEqual(1, maps:size(Result)),
|
||||
?assertEqual(<<"Hello">>, maps:get(<<"text">>, Result)).
|
||||
|
||||
build_custom_status_payload_empty_test() ->
|
||||
?assertEqual(#{}, build_custom_status_payload(#{})).
|
||||
|
||||
build_request_test() ->
|
||||
CustomStatus = #{<<"text">> => <<"Test">>},
|
||||
Result = build_request(123, CustomStatus),
|
||||
?assertEqual(<<"validate_custom_status">>, maps:get(<<"type">>, Result)),
|
||||
?assertEqual(<<"123">>, maps:get(<<"user_id">>, Result)),
|
||||
?assert(is_map(maps:get(<<"custom_status">>, Result))).
|
||||
|
||||
-endif.
|
||||
|
||||
50
fluxer_gateway/src/utils/event_atoms.erl
Normal file
50
fluxer_gateway/src/utils/event_atoms.erl
Normal file
@@ -0,0 +1,50 @@
|
||||
%% 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(event_atoms).
|
||||
|
||||
-export([normalize/1]).
|
||||
|
||||
-spec normalize(binary() | atom()) -> atom() | binary().
|
||||
normalize(Event) when is_atom(Event) ->
|
||||
Event;
|
||||
normalize(EventBinary) when is_binary(EventBinary) ->
|
||||
Lowercase = string:lowercase(EventBinary),
|
||||
try
|
||||
binary_to_existing_atom(Lowercase, utf8)
|
||||
catch
|
||||
error:badarg ->
|
||||
EventBinary
|
||||
end.
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
normalize_atom_test() ->
|
||||
?assertEqual(test_event, normalize(test_event)),
|
||||
?assertEqual(message_create, normalize(message_create)).
|
||||
|
||||
normalize_binary_existing_atom_test() ->
|
||||
_ = message_create,
|
||||
?assertEqual(message_create, normalize(<<"MESSAGE_CREATE">>)),
|
||||
?assertEqual(message_create, normalize(<<"message_create">>)).
|
||||
|
||||
normalize_binary_unknown_test() ->
|
||||
Result = normalize(<<"UNKNOWN_EVENT_XYZ_12345">>),
|
||||
?assertEqual(<<"UNKNOWN_EVENT_XYZ_12345">>, Result).
|
||||
|
||||
-endif.
|
||||
56
fluxer_gateway/src/utils/snowflake_util.erl
Normal file
56
fluxer_gateway/src/utils/snowflake_util.erl
Normal file
@@ -0,0 +1,56 @@
|
||||
%% 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(snowflake_util).
|
||||
|
||||
-export([extract_timestamp/1]).
|
||||
|
||||
-define(FLUXER_EPOCH, 1420070400000).
|
||||
-define(TIMESTAMP_SHIFT, 22).
|
||||
|
||||
-spec extract_timestamp(binary() | integer()) -> integer().
|
||||
extract_timestamp(SnowflakeBin) when is_binary(SnowflakeBin) ->
|
||||
Snowflake = binary_to_integer(SnowflakeBin),
|
||||
extract_timestamp(Snowflake);
|
||||
extract_timestamp(Snowflake) when is_integer(Snowflake) ->
|
||||
(Snowflake bsr ?TIMESTAMP_SHIFT) + ?FLUXER_EPOCH.
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
extract_timestamp_from_integer_test() ->
|
||||
Timestamp = 1704067200000,
|
||||
RelativeTs = Timestamp - ?FLUXER_EPOCH,
|
||||
Snowflake = RelativeTs bsl ?TIMESTAMP_SHIFT,
|
||||
?assertEqual(Timestamp, extract_timestamp(Snowflake)).
|
||||
|
||||
extract_timestamp_from_binary_test() ->
|
||||
Timestamp = 1704067200000,
|
||||
RelativeTs = Timestamp - ?FLUXER_EPOCH,
|
||||
Snowflake = RelativeTs bsl ?TIMESTAMP_SHIFT,
|
||||
SnowflakeBin = integer_to_binary(Snowflake),
|
||||
?assertEqual(Timestamp, extract_timestamp(SnowflakeBin)).
|
||||
|
||||
extract_timestamp_with_worker_and_sequence_test() ->
|
||||
Timestamp = 1704067200000,
|
||||
RelativeTs = Timestamp - ?FLUXER_EPOCH,
|
||||
WorkerId = 5,
|
||||
Sequence = 100,
|
||||
Snowflake = (RelativeTs bsl ?TIMESTAMP_SHIFT) bor (WorkerId bsl 12) bor Sequence,
|
||||
?assertEqual(Timestamp, extract_timestamp(Snowflake)).
|
||||
|
||||
-endif.
|
||||
@@ -17,10 +17,11 @@
|
||||
|
||||
-module(user_utils).
|
||||
|
||||
-export([normalize_user/1]).
|
||||
-export([normalize_user/1, partial_user_fields/0]).
|
||||
|
||||
normalize_user(User) when is_map(User) ->
|
||||
AllowedKeys = [
|
||||
-spec partial_user_fields() -> [binary()].
|
||||
partial_user_fields() ->
|
||||
[
|
||||
<<"id">>,
|
||||
<<"username">>,
|
||||
<<"discriminator">>,
|
||||
@@ -29,25 +30,82 @@ normalize_user(User) when is_map(User) ->
|
||||
<<"avatar_color">>,
|
||||
<<"bot">>,
|
||||
<<"system">>,
|
||||
<<"flags">>,
|
||||
<<"banner">>,
|
||||
<<"banner_color">>
|
||||
],
|
||||
<<"flags">>
|
||||
].
|
||||
|
||||
-spec normalize_user(map() | term()) -> map().
|
||||
normalize_user(User) when is_map(User) ->
|
||||
CleanPairs =
|
||||
lists:foldl(
|
||||
fun(Key, Acc) ->
|
||||
Value = maps:get(Key, User, undefined),
|
||||
case is_undefined(Value) of
|
||||
true -> Acc;
|
||||
false -> [{Key, Value} | Acc]
|
||||
case maps:get(Key, User, undefined) of
|
||||
undefined -> Acc;
|
||||
Value -> [{Key, Value} | Acc]
|
||||
end
|
||||
end,
|
||||
[],
|
||||
AllowedKeys
|
||||
partial_user_fields()
|
||||
),
|
||||
maps:from_list(lists:reverse(CleanPairs));
|
||||
normalize_user(_) ->
|
||||
#{}.
|
||||
|
||||
is_undefined(undefined) -> true;
|
||||
is_undefined(_) -> false.
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
normalize_user_valid_test() ->
|
||||
User = #{
|
||||
<<"id">> => <<"123">>,
|
||||
<<"username">> => <<"testuser">>,
|
||||
<<"discriminator">> => <<"0001">>,
|
||||
<<"email">> => <<"test@example.com">>
|
||||
},
|
||||
Result = normalize_user(User),
|
||||
?assertEqual(<<"123">>, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"testuser">>, maps:get(<<"username">>, Result)),
|
||||
?assertEqual(<<"0001">>, maps:get(<<"discriminator">>, Result)),
|
||||
?assertEqual(error, maps:find(<<"email">>, Result)).
|
||||
|
||||
normalize_user_all_fields_test() ->
|
||||
User = #{
|
||||
<<"id">> => <<"123">>,
|
||||
<<"username">> => <<"test">>,
|
||||
<<"discriminator">> => <<"0">>,
|
||||
<<"global_name">> => <<"Test User">>,
|
||||
<<"avatar">> => <<"abc123">>,
|
||||
<<"avatar_color">> => <<"#ff0000">>,
|
||||
<<"bot">> => false,
|
||||
<<"system">> => false,
|
||||
<<"flags">> => 0
|
||||
},
|
||||
Result = normalize_user(User),
|
||||
?assertEqual(9, maps:size(Result)).
|
||||
|
||||
normalize_user_undefined_values_test() ->
|
||||
User = #{
|
||||
<<"id">> => <<"123">>,
|
||||
<<"username">> => <<"test">>,
|
||||
<<"avatar">> => undefined
|
||||
},
|
||||
Result = normalize_user(User),
|
||||
?assertEqual(2, maps:size(Result)),
|
||||
?assertEqual(error, maps:find(<<"avatar">>, Result)).
|
||||
|
||||
normalize_user_not_map_test() ->
|
||||
?assertEqual(#{}, normalize_user(not_a_map)),
|
||||
?assertEqual(#{}, normalize_user(123)),
|
||||
?assertEqual(#{}, normalize_user(<<"binary">>)),
|
||||
?assertEqual(#{}, normalize_user(undefined)).
|
||||
|
||||
normalize_user_empty_map_test() ->
|
||||
?assertEqual(#{}, normalize_user(#{})).
|
||||
|
||||
partial_user_fields_test() ->
|
||||
Fields = partial_user_fields(),
|
||||
?assert(is_list(Fields)),
|
||||
?assertEqual(9, length(Fields)),
|
||||
?assert(lists:member(<<"id">>, Fields)),
|
||||
?assert(lists:member(<<"username">>, Fields)),
|
||||
?assert(lists:member(<<"flags">>, Fields)).
|
||||
|
||||
-endif.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
%% along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
-module(utils).
|
||||
-import(type_conv, [to_integer/1]).
|
||||
|
||||
-export([
|
||||
binary_to_integer_safe/1,
|
||||
generate_session_id/0,
|
||||
@@ -25,10 +25,12 @@
|
||||
parse_status/1,
|
||||
safe_json_decode/1,
|
||||
check_user_data_differs/2,
|
||||
partial_user_fields/0,
|
||||
parse_iso8601_to_unix_ms/1
|
||||
]).
|
||||
|
||||
-spec binary_to_integer_safe(binary() | integer() | term()) -> integer() | undefined.
|
||||
binary_to_integer_safe(Int) when is_integer(Int) ->
|
||||
Int;
|
||||
binary_to_integer_safe(Bin) when is_binary(Bin) ->
|
||||
try
|
||||
binary_to_integer(Bin)
|
||||
@@ -40,21 +42,24 @@ binary_to_integer_safe(Bin) when is_binary(Bin) ->
|
||||
_:_ -> undefined
|
||||
end
|
||||
end;
|
||||
binary_to_integer_safe(Int) when is_integer(Int) -> Int;
|
||||
binary_to_integer_safe(_) ->
|
||||
undefined.
|
||||
|
||||
-spec generate_session_id() -> binary().
|
||||
generate_session_id() ->
|
||||
Bytes = crypto:strong_rand_bytes(constants:random_session_bytes()),
|
||||
binary:encode_hex(Bytes).
|
||||
|
||||
-spec generate_resume_token() -> binary().
|
||||
generate_resume_token() ->
|
||||
Bytes = crypto:strong_rand_bytes(32),
|
||||
base64url:encode(Bytes).
|
||||
|
||||
-spec hash_token(binary()) -> binary().
|
||||
hash_token(Token) ->
|
||||
crypto:hash(sha256, Token).
|
||||
|
||||
-spec parse_status(binary() | atom() | term()) -> atom().
|
||||
parse_status(Status) when is_binary(Status) ->
|
||||
constants:status_type_atom(Status);
|
||||
parse_status(Status) when is_atom(Status) ->
|
||||
@@ -62,29 +67,35 @@ parse_status(Status) when is_atom(Status) ->
|
||||
parse_status(_) ->
|
||||
online.
|
||||
|
||||
-spec safe_json_decode(binary()) -> map().
|
||||
safe_json_decode(Bin) ->
|
||||
try
|
||||
jsx:decode(Bin, [{return_maps, true}])
|
||||
json:decode(Bin)
|
||||
catch
|
||||
_:_ -> #{}
|
||||
end.
|
||||
|
||||
-spec parse_iso8601_to_unix_ms(binary() | term()) -> integer() | undefined.
|
||||
parse_iso8601_to_unix_ms(Binary) when is_binary(Binary) ->
|
||||
Pattern =
|
||||
<<"^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,9}))?Z$">>,
|
||||
case re:run(Binary, Pattern, [{capture, [1, 2, 3, 4, 5, 6, 7], list}]) of
|
||||
{match, [YearBin, MonthBin, DayBin, HourBin, MinuteBin, SecondBin, FractionBin]} ->
|
||||
Year = to_integer(YearBin),
|
||||
Month = to_integer(MonthBin),
|
||||
Day = to_integer(DayBin),
|
||||
Hour = to_integer(HourBin),
|
||||
Minute = to_integer(MinuteBin),
|
||||
Second = to_integer(SecondBin),
|
||||
Year = type_conv:to_integer(YearBin),
|
||||
Month = type_conv:to_integer(MonthBin),
|
||||
Day = type_conv:to_integer(DayBin),
|
||||
Hour = type_conv:to_integer(HourBin),
|
||||
Minute = type_conv:to_integer(MinuteBin),
|
||||
Second = type_conv:to_integer(SecondBin),
|
||||
FractionMs = fractional_ms(FractionBin),
|
||||
case {Year, Month, Day, Hour, Minute, Second} of
|
||||
{Y, M, D, H, Min, S} when
|
||||
is_integer(Y) and is_integer(M) and is_integer(D) and is_integer(H) and
|
||||
is_integer(Min) and is_integer(S)
|
||||
is_integer(Y),
|
||||
is_integer(M),
|
||||
is_integer(D),
|
||||
is_integer(H),
|
||||
is_integer(Min),
|
||||
is_integer(S)
|
||||
->
|
||||
Seconds = calendar:datetime_to_gregorian_seconds({{Y, M, D}, {H, Min, S}}),
|
||||
Seconds * 1000 + FractionMs;
|
||||
@@ -97,6 +108,9 @@ parse_iso8601_to_unix_ms(Binary) when is_binary(Binary) ->
|
||||
parse_iso8601_to_unix_ms(_) ->
|
||||
undefined.
|
||||
|
||||
-spec fractional_ms(list()) -> non_neg_integer().
|
||||
fractional_ms([]) ->
|
||||
0;
|
||||
fractional_ms(Fraction) when is_list(Fraction) ->
|
||||
Normalized =
|
||||
case length(Fraction) of
|
||||
@@ -104,41 +118,121 @@ fractional_ms(Fraction) when is_list(Fraction) ->
|
||||
Len when Len > 0 -> Fraction ++ lists:duplicate(3 - Len, $0);
|
||||
_ -> "000"
|
||||
end,
|
||||
case Normalized of
|
||||
[] ->
|
||||
0;
|
||||
_ ->
|
||||
case catch list_to_integer(Normalized) of
|
||||
{'EXIT', _} -> 0;
|
||||
Value -> Value
|
||||
end
|
||||
case catch list_to_integer(Normalized) of
|
||||
{'EXIT', _} -> 0;
|
||||
Value -> Value
|
||||
end;
|
||||
fractional_ms(_) ->
|
||||
0.
|
||||
|
||||
partial_user_fields() ->
|
||||
[
|
||||
<<"id">>,
|
||||
<<"username">>,
|
||||
<<"discriminator">>,
|
||||
<<"global_name">>,
|
||||
<<"avatar">>,
|
||||
<<"avatar_color">>,
|
||||
<<"bot">>,
|
||||
<<"system">>,
|
||||
<<"flags">>,
|
||||
<<"banner">>,
|
||||
<<"banner_color">>
|
||||
].
|
||||
|
||||
-spec check_user_data_differs(map(), map()) -> boolean().
|
||||
check_user_data_differs(CurrentUserData, NewUserData) ->
|
||||
CheckedFields = partial_user_fields(),
|
||||
CheckedFields = user_utils:partial_user_fields(),
|
||||
lists:any(
|
||||
fun(Field) ->
|
||||
CurrentValue = maps:get(Field, CurrentUserData, undefined),
|
||||
NewValue = maps:get(Field, NewUserData, undefined),
|
||||
CurrentValue =/= NewValue orelse
|
||||
(maps:is_key(Field, CurrentUserData) andalso not maps:is_key(Field, NewUserData))
|
||||
case maps:is_key(Field, NewUserData) of
|
||||
false ->
|
||||
false;
|
||||
true ->
|
||||
CurrentValue = maps:get(Field, CurrentUserData, undefined),
|
||||
NewValue = maps:get(Field, NewUserData, undefined),
|
||||
CurrentValue =/= NewValue
|
||||
end
|
||||
end,
|
||||
CheckedFields
|
||||
).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
binary_to_integer_safe_integer_test() ->
|
||||
?assertEqual(42, binary_to_integer_safe(42)),
|
||||
?assertEqual(0, binary_to_integer_safe(0)),
|
||||
?assertEqual(-100, binary_to_integer_safe(-100)).
|
||||
|
||||
binary_to_integer_safe_binary_test() ->
|
||||
?assertEqual(123, binary_to_integer_safe(<<"123">>)),
|
||||
?assertEqual(0, binary_to_integer_safe(<<"0">>)),
|
||||
?assertEqual(-456, binary_to_integer_safe(<<"-456">>)).
|
||||
|
||||
binary_to_integer_safe_invalid_test() ->
|
||||
?assertEqual(undefined, binary_to_integer_safe(<<"not_a_number">>)),
|
||||
?assertEqual(undefined, binary_to_integer_safe(<<"12.34">>)),
|
||||
?assertEqual(undefined, binary_to_integer_safe(<<"">>)),
|
||||
?assertEqual(undefined, binary_to_integer_safe(atom)),
|
||||
?assertEqual(undefined, binary_to_integer_safe(#{})).
|
||||
|
||||
generate_session_id_test() ->
|
||||
SessionId = generate_session_id(),
|
||||
?assert(is_binary(SessionId)),
|
||||
?assertEqual(32, byte_size(SessionId)).
|
||||
|
||||
generate_resume_token_test() ->
|
||||
Token = generate_resume_token(),
|
||||
?assert(is_binary(Token)),
|
||||
?assert(byte_size(Token) > 0).
|
||||
|
||||
hash_token_test() ->
|
||||
Hash = hash_token(<<"test_token">>),
|
||||
?assert(is_binary(Hash)),
|
||||
?assertEqual(32, byte_size(Hash)).
|
||||
|
||||
parse_status_binary_test() ->
|
||||
?assertEqual(online, parse_status(<<"online">>)),
|
||||
?assertEqual(dnd, parse_status(<<"dnd">>)),
|
||||
?assertEqual(idle, parse_status(<<"idle">>)),
|
||||
?assertEqual(invisible, parse_status(<<"invisible">>)),
|
||||
?assertEqual(offline, parse_status(<<"offline">>)).
|
||||
|
||||
parse_status_atom_test() ->
|
||||
?assertEqual(online, parse_status(online)),
|
||||
?assertEqual(dnd, parse_status(dnd)),
|
||||
?assertEqual(idle, parse_status(idle)).
|
||||
|
||||
parse_status_default_test() ->
|
||||
?assertEqual(online, parse_status(123)),
|
||||
?assertEqual(online, parse_status(#{})).
|
||||
|
||||
safe_json_decode_valid_test() ->
|
||||
Result = safe_json_decode(<<"{\"key\": \"value\"}">>),
|
||||
?assertEqual(#{<<"key">> => <<"value">>}, Result).
|
||||
|
||||
safe_json_decode_invalid_test() ->
|
||||
?assertEqual(#{}, safe_json_decode(<<"not json">>)),
|
||||
?assertEqual(#{}, safe_json_decode(<<"">>)).
|
||||
|
||||
parse_iso8601_to_unix_ms_valid_test() ->
|
||||
Result = parse_iso8601_to_unix_ms(<<"2024-01-15T12:30:45Z">>),
|
||||
?assert(is_integer(Result)),
|
||||
?assert(Result > 0).
|
||||
|
||||
parse_iso8601_to_unix_ms_with_fraction_test() ->
|
||||
Result = parse_iso8601_to_unix_ms(<<"2024-01-15T12:30:45.123Z">>),
|
||||
?assert(is_integer(Result)),
|
||||
?assertEqual(123, Result rem 1000).
|
||||
|
||||
parse_iso8601_to_unix_ms_invalid_test() ->
|
||||
?assertEqual(undefined, parse_iso8601_to_unix_ms(<<"invalid">>)),
|
||||
?assertEqual(undefined, parse_iso8601_to_unix_ms(<<"2024-01-15">>)),
|
||||
?assertEqual(undefined, parse_iso8601_to_unix_ms(123)).
|
||||
|
||||
check_user_data_differs_same_test() ->
|
||||
User = #{<<"id">> => <<"123">>, <<"username">> => <<"test">>},
|
||||
?assertEqual(false, check_user_data_differs(User, User)).
|
||||
|
||||
check_user_data_differs_different_test() ->
|
||||
Current = #{<<"id">> => <<"123">>, <<"username">> => <<"test">>},
|
||||
New = #{<<"id">> => <<"123">>, <<"username">> => <<"changed">>},
|
||||
?assertEqual(true, check_user_data_differs(Current, New)).
|
||||
|
||||
check_user_data_differs_missing_field_test() ->
|
||||
Current = #{<<"id">> => <<"123">>, <<"username">> => <<"test">>},
|
||||
New = #{<<"id">> => <<"123">>},
|
||||
?assertEqual(false, check_user_data_differs(Current, New)).
|
||||
|
||||
check_user_data_differs_null_field_test() ->
|
||||
Current = #{<<"id">> => <<"123">>, <<"username">> => <<"test">>},
|
||||
New = #{<<"username">> => null},
|
||||
?assertEqual(true, check_user_data_differs(Current, New)).
|
||||
|
||||
-endif.
|
||||
|
||||
@@ -138,6 +138,8 @@ extract_snowflake(FieldName, Map, Default) ->
|
||||
extract_snowflakes(FieldSpecs, Map) ->
|
||||
extract_snowflakes_loop(FieldSpecs, Map, #{}).
|
||||
|
||||
-spec extract_snowflakes_loop(list({atom(), binary()}), map(), map()) ->
|
||||
{ok, #{atom() => integer()}} | {error, atom(), atom()}.
|
||||
extract_snowflakes_loop([], _Map, Acc) ->
|
||||
{ok, Acc};
|
||||
extract_snowflakes_loop([{KeyAtom, FieldName} | Rest], Map, Acc) ->
|
||||
@@ -192,3 +194,87 @@ error_category_to_close_code(auth_failed) ->
|
||||
constants:close_code_to_num(authentication_failed);
|
||||
error_category_to_close_code(_) ->
|
||||
constants:close_code_to_num(unknown_error).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
validate_snowflake_integer_test() ->
|
||||
?assertEqual({ok, 123}, validate_snowflake(123)),
|
||||
?assertEqual({ok, 0}, validate_snowflake(0)),
|
||||
?assertEqual({ok, -1}, validate_snowflake(-1)).
|
||||
|
||||
validate_snowflake_binary_test() ->
|
||||
?assertEqual({ok, 123}, validate_snowflake(<<"123">>)),
|
||||
?assertEqual({ok, 0}, validate_snowflake(<<"0">>)).
|
||||
|
||||
validate_snowflake_invalid_test() ->
|
||||
?assertMatch({error, _, _}, validate_snowflake(null)),
|
||||
?assertMatch({error, _, _}, validate_snowflake(<<"abc">>)),
|
||||
?assertMatch({error, _, _}, validate_snowflake(1.5)).
|
||||
|
||||
validate_optional_snowflake_test() ->
|
||||
?assertEqual({ok, null}, validate_optional_snowflake(null)),
|
||||
?assertEqual({ok, 123}, validate_optional_snowflake(123)),
|
||||
?assertEqual({ok, 456}, validate_optional_snowflake(<<"456">>)).
|
||||
|
||||
validate_snowflake_list_test() ->
|
||||
?assertEqual({ok, [1, 2, 3]}, validate_snowflake_list([1, 2, 3])),
|
||||
?assertEqual({ok, [1, 2]}, validate_snowflake_list([<<"1">>, <<"2">>])),
|
||||
?assertEqual({ok, []}, validate_snowflake_list([])).
|
||||
|
||||
validate_snowflake_list_invalid_test() ->
|
||||
?assertMatch({error, _, _}, validate_snowflake_list([1, <<"abc">>])),
|
||||
?assertMatch({error, _, _}, validate_snowflake_list(not_a_list)).
|
||||
|
||||
snowflake_or_default_test() ->
|
||||
?assertEqual(123, snowflake_or_default(123, 0)),
|
||||
?assertEqual(456, snowflake_or_default(<<"456">>, 0)),
|
||||
?assertEqual(0, snowflake_or_default(<<"abc">>, 0)),
|
||||
?assertEqual(99, snowflake_or_default(null, 99)).
|
||||
|
||||
get_field_test() ->
|
||||
Map = #{<<"key">> => <<"value">>},
|
||||
?assertEqual({ok, <<"value">>}, get_field(<<"key">>, Map)),
|
||||
?assertMatch({error, _, _}, get_field(<<"missing">>, Map)).
|
||||
|
||||
get_field_with_default_test() ->
|
||||
Map = #{<<"key">> => <<"value">>},
|
||||
?assertEqual(<<"value">>, get_field(<<"key">>, Map, <<"default">>)),
|
||||
?assertEqual(<<"default">>, get_field(<<"missing">>, Map, <<"default">>)),
|
||||
?assertEqual(<<"default">>, get_field(<<"key">>, not_a_map, <<"default">>)).
|
||||
|
||||
extract_snowflake_test() ->
|
||||
Map = #{<<"id">> => <<"123">>},
|
||||
?assertEqual({ok, 123}, extract_snowflake(<<"id">>, Map)),
|
||||
?assertMatch({error, _, _}, extract_snowflake(<<"missing">>, Map)).
|
||||
|
||||
extract_snowflake_with_default_test() ->
|
||||
Map = #{<<"id">> => <<"123">>},
|
||||
?assertEqual(123, extract_snowflake(<<"id">>, Map, 0)),
|
||||
?assertEqual(0, extract_snowflake(<<"missing">>, Map, 0)).
|
||||
|
||||
extract_snowflakes_test() ->
|
||||
Map = #{<<"user_id">> => <<"123">>, <<"guild_id">> => <<"456">>},
|
||||
Specs = [{user, <<"user_id">>}, {guild, <<"guild_id">>}],
|
||||
{ok, Result} = extract_snowflakes(Specs, Map),
|
||||
?assertEqual(123, maps:get(user, Result)),
|
||||
?assertEqual(456, maps:get(guild, Result)).
|
||||
|
||||
get_required_field_test() ->
|
||||
Map = #{<<"id">> => <<"123">>},
|
||||
Validator = fun(V) -> validate_snowflake(V) end,
|
||||
?assertEqual({ok, 123}, get_required_field(<<"id">>, Map, Validator)),
|
||||
?assertMatch({error, _, _}, get_required_field(<<"missing">>, Map, Validator)).
|
||||
|
||||
get_optional_field_test() ->
|
||||
Map = #{<<"id">> => <<"123">>},
|
||||
Validator = fun(V) -> validate_snowflake(V) end,
|
||||
?assertEqual({ok, 123}, get_optional_field(<<"id">>, Map, Validator)),
|
||||
?assertEqual({ok, undefined}, get_optional_field(<<"missing">>, Map, Validator)).
|
||||
|
||||
error_category_to_close_code_test() ->
|
||||
?assertEqual(4008, error_category_to_close_code(rate_limited)),
|
||||
?assertEqual(4004, error_category_to_close_code(auth_failed)),
|
||||
?assertEqual(4000, error_category_to_close_code(unknown)).
|
||||
|
||||
-endif.
|
||||
|
||||
Reference in New Issue
Block a user