Skip to content

Commit

Permalink
unify error codes in service behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
kmakiela committed Nov 5, 2019
1 parent 696377c commit 4d1f46b
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 13 deletions.
15 changes: 7 additions & 8 deletions lib/mongoose_push.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule MongoosePush do

require Logger
alias MongoosePush.Metrics
alias MongoosePush.Service
use Metrics

@typedoc "Available keys in `request` map"
Expand All @@ -35,6 +36,8 @@ defmodule MongoosePush do
@type service :: :fcm | :apns
@type mode :: :dev | :prod

@type error :: :no_matching_pool

@doc """
Push notification defined by `request` to device with `device_id`.
`request` has to define at least `:service` type (`:fcm` or `:apns`) and
Expand All @@ -59,7 +62,7 @@ defmodule MongoosePush do
this feature (please consult APNS documentation for more information).
"""
@timed key: :auto
@spec push(String.t(), request) :: :ok | {:error, term}
@spec push(String.t(), request) :: :ok | {:error, Service.error()} | {:error, MongoosePush.error()}
def push(device_id, %{:service => service} = request) do
mode = Map.get(request, :mode, :prod)
module = MongoosePush.Application.services()[service]
Expand Down Expand Up @@ -90,17 +93,13 @@ defmodule MongoosePush do

defp maybe_log(:ok), do: :ok

defp maybe_log({:error, reason} = return_value) when is_atom(reason) do
Logger.warn(~s"Unable to complete push request due to #{reason}")
defp maybe_log({:error, {type, reason}} = return_value) do
Logger.warn(~s"Unable to complete push request due to service error: #{reason} in category: #{type}")
return_value
end

defp maybe_log({:error, reason} = return_value) do
Logger.warn(
~s"Unable to complete push request due to unknown error: " <>
~s"#{inspect(reason)}"
)

Logger.warn(~s"Unable to complete push request due to #{inspect(reason)}")
return_value
end
end
18 changes: 18 additions & 0 deletions lib/mongoose_push/service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,28 @@ defmodule MongoosePush.Service do
@type notification :: term
@type options :: [Keyword.t()]

@type error_type ::
:invalid_request
| :internal_config
| :auth
| :unregistered
| :too_many_requests
| :unspecified
| :service_internal
| :payload_too_large

@type error_reason :: atom

@typedoc """
Error tuple with unified internal representation and exact reason returned by service
"""
@type error :: {error_type, error_reason}

@callback push(notification(), String.t(), Application.pool_name(), options()) ::
:ok | {:error, term}
@callback prepare_notification(String.t(), MongoosePush.request(), Application.pool_name()) ::
notification()
@callback supervisor_entry([Application.pool_definition()] | nil) :: {module(), term()}
@callback choose_pool(MongoosePush.mode()) :: Application.pool_name() | nil
@callback unify_error(error_reason) :: error
end
10 changes: 8 additions & 2 deletions lib/mongoose_push/service/apns.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule MongoosePush.Service.APNS do
alias MongoosePush.Application
alias MongoosePush.Service
alias MongoosePush.Service.APNS.State
alias MongoosePush.Service.APNS.ErrorHandler

@priority_mapping %{normal: "5", high: "10"}

Expand Down Expand Up @@ -43,14 +44,14 @@ defmodule MongoosePush.Service.APNS do
end

@spec push(Service.notification(), String.t(), Application.pool_name(), Service.options()) ::
:ok | {:error, term}
:ok | {:error, Service.error()}
def push(notification, _device_id, pool, _opts \\ []) do
case APNS.push(pool, notification, is_sync: true) do
:ok ->
:ok

{:error, reason} ->
{:error, reason}
{:error, unify_error(reason)}
end
end

Expand All @@ -64,6 +65,11 @@ defmodule MongoosePush.Service.APNS do
Sparrow.PoolsWarden.choose_pool({:apns, mode}, tags)
end

@spec unify_error(Service.error_reason()) :: Service.error()
def unify_error(reason) do
ErrorHandler.translate_error_reason(reason)
end

defp maybe(notification, :add_mutable_content, true),
do: apply(Notification, :add_mutable_content, [notification])

Expand Down
50 changes: 50 additions & 0 deletions lib/mongoose_push/service/apns/error_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule MongoosePush.Service.APNS.ErrorHandler do
@moduledoc """
Module responsible for handling errors returned by APNS service
"""
alias MongoosePush.Service

# More information about possible return codes is here:
# https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
@spec translate_error_reason(Service.error_reason()) :: Service.error()
def translate_error_reason(:BadCollapseId), do: {:invalid_request, :BadCollapseId}
def translate_error_reason(:BadDeviceToken), do: {:invalid_request, :BadDeviceToken}
def translate_error_reason(:BadExpirationDate), do: {:invalid_request, :BadExpirationDate}

def translate_error_reason(:DeviceTokenNotForTopic),
do: {:invalid_request, :DeviceTokenNotForTopic}

def translate_error_reason(:IdleTimeout), do: {:invalid_request, :IdleTimeout}
def translate_error_reason(:PayloadEmpty), do: {:invalid_request, :PayloadEmpty}
def translate_error_reason(:TopicDisallowed), do: {:invalid_request, :TopicDisallowed}
def translate_error_reason(:Forbidden), do: {:invalid_request, :Forbidden}

def translate_error_reason(:BadMessageId), do: {:internal_config, :BadMessageId}
def translate_error_reason(:BadPriority), do: {:internal_config, :BadPriority}
def translate_error_reason(:BadTopic), do: {:internal_config, :BadTopic}
def translate_error_reason(:DuplicateHeaders), do: {:internal_config, :DuplicateHeaders}
def translate_error_reason(:MissingDeviceToken), do: {:internal_config, :MissingDeviceToken}
def translate_error_reason(:MethodNotAllowed), do: {:internal_config, :MethodNotAllowed}
def translate_error_reason(:BadPath), do: {:internal_config, :BadPath}
def translate_error_reason(:MissingTopic), do: {:internal_config, :MissingTopic}

def translate_error_reason(:ExpiredProviderToken), do: {:auth, :ExpiredProviderToken}
def translate_error_reason(:InvalidProviderToken), do: {:auth, :InvalidProviderToken}
def translate_error_reason(:MissingProviderToken), do: {:auth, :MissingProviderToken}
def translate_error_reason(:BadCertificate), do: {:auth, :BadCertificate}
def translate_error_reason(:BadCertificateEnvironment),
do: {:auth, :BadCertificateEnvironment}

def translate_error_reason(:Unregistered), do: {:unregistered, :Unregistered}

def translate_error_reason(:TooManyProviderTokenUpdates),
do: {:too_many_requests, :TooManyProviderTokenUpdates}

def translate_error_reason(:TooManyRequests), do: {:too_many_requests, :TooManyRequests}

def translate_error_reason(:ServiceUnavailable), do: {:service_internal, :ServiceUnavailable}
def translate_error_reason(:Shutdown), do: {:service_internal, :Shutdown}
def translate_error_reason(:InternalServerError), do: {:service_internal, :InternalServerError}

def translate_error_reason(:PayloadTooLarge), do: {:payload_too_large, :PayloadTooLarge}
end
10 changes: 8 additions & 2 deletions lib/mongoose_push/service/fcm.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule MongoosePush.Service.FCM do
alias MongoosePush.Application
alias MongoosePush.Service
alias MongoosePush.Service.FCM.Pool.Supervisor, as: PoolSupervisor
alias MongoosePush.Service.FCM.ErrorHandler
require Logger

@priority_mapping %{normal: :NORMAL, high: :HIGH}
Expand Down Expand Up @@ -48,14 +49,14 @@ defmodule MongoosePush.Service.FCM do
end

@spec push(Service.notification(), String.t(), Application.pool_name(), Service.options()) ::
:ok | {:error, term}
:ok | {:error, Service.error()}
def push(notification, _device_id, pool, _opts \\ []) do
case FCM.push(pool, notification, is_sync: true) do
:ok ->
:ok

{:error, reason} ->
{:error, reason}
{:error, unify_error(reason)}
end
end

Expand All @@ -71,4 +72,9 @@ defmodule MongoosePush.Service.FCM do

defp maybe(notification, _function, nil), do: notification
defp maybe(notification, function, arg), do: apply(Android, function, [notification, arg])

@spec unify_error(Service.error_reason()) :: Service.error()
def unify_error(reason) do
ErrorHandler.translate_error_reason(reason)
end
end
19 changes: 19 additions & 0 deletions lib/mongoose_push/service/fcm/error_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule MongoosePush.Service.FCM.ErrorHandler do
@moduledoc """
Module responsible for handling errors returned by FCM service.
"""
alias MongoosePush.Service

# More information about possible return codes is here:
# https://firebase.google.com/docs/reference/fcm/rest/v1/ErrorCode
@spec translate_error_reason(Service.error_reason()) :: Service.error()
def translate_error_reason(:INVALID_ARGUMENT), do: {:invalid_request, :INVALID_ARGUMENT}
def translate_error_reason(:SENDER_ID_MISMATCH), do: {:auth, :SENDER_ID_MISMATCH}
def translate_error_reason(:UNREGISTERED), do: {:unregistered, :UNREGISTERED}
def translate_error_reason(:QUOTA_EXCEEDED), do: {:too_many_requests, :QUOTA_EXCEEDED}
def translate_error_reason(:UNSPECIFIED), do: {:unspecified, :UNSPECIFIED}
def translate_error_reason(:APNS_AUTH_ERROR), do: {:auth, :APNS_AUTH_ERROR}
def translate_error_reason(:THIRD_PARTY_AUTH_ERROR), do: {:auth, :THIRD_PARTY_AUTH_ERROR}
def translate_error_reason(:UNAVAILABLE), do: {:service_internal, :UNAVAILABLE}
def translate_error_reason(:INTERNAL), do: {:service_internal, :INTERNAL}
end
2 changes: 1 addition & 1 deletion test/mongoose_push_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ defmodule MongoosePushTest do
%{device_token: "androidtestdeviceid65", status: 404, reason: "UNREGISTERED"}
])

assert {:error, :UNREGISTERED} = push("androidtestdeviceid65", notification)
assert {:error, {:unregistered, :UNREGISTERED}} = push("androidtestdeviceid65", notification)
end

test "check FCM_ENABLED option" do
Expand Down

0 comments on commit 4d1f46b

Please sign in to comment.