Skip to content

Commit

Permalink
Follow the phoenix recommandations to handle the json rendering, add …
Browse files Browse the repository at this point in the history
…fallback actions and return the errors by following the RFC 7807.
  • Loading branch information
Burgy Benjamin committed Dec 18, 2023
1 parent 240b21e commit d81ac60
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 143 deletions.
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[
line_length: 300,
line_length: 150,
import_deps: [:phoenix],
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
]
2 changes: 1 addition & 1 deletion lib/ksuite_middleware/ksuite_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule KsuiteMiddleware.KsuiteClient do

require Logger

plug Tesla.Middleware.BaseUrl, "https://api.infomaniak.com"
plug Tesla.Middleware.BaseUrl, State.get_ksuite_api_server()
plug Tesla.Middleware.Logger, debug: false
plug Tesla.Middleware.PathParams
plug Tesla.Middleware.Headers, [{"User-Agent", "ksuite-middleware"}]
Expand Down
3 changes: 3 additions & 0 deletions lib/ksuite_middleware/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ defmodule KsuiteMiddleware.State do
@spec get_timezone() :: Timex.TimezoneInfo.t()
def get_timezone(), do: GenServer.call(State, :get_timezone)

@spec get_ksuite_api_server() :: String.t()
def get_ksuite_api_server(), do: "https://api.infomaniak.com"

# Callbacks

@impl true
Expand Down
54 changes: 40 additions & 14 deletions lib/ksuite_middleware_web/controllers/calendar_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@ defmodule KsuiteMiddlewareWeb.CalendarController do

require Logger

action_fallback KsuiteMiddleware.FallbackController

def get_events(conn, %{"from" => from, "to" => to, "calendar_id" => calendar_id}),
do:
parse_params(from, to, calendar_id)
|> then(&read_events/1)
|> then(&translate_to_events/1)
|> then(&send_response(conn, &1))

def get_events(conn, _),
do:
conn
|> put_status(:bad_request)
|> put_resp_content_type("application/problem+json")
|> render(:"400", reason: "The arguments were missing.")

# Private

defp read_events({:error, error, reason}), do: {:error, error, reason}
Expand All @@ -28,19 +37,28 @@ defmodule KsuiteMiddlewareWeb.CalendarController do
client |> CalDAVClient.Event.get_events(calendar_url, from, to)
end

defp send_response(conn, {:ok, events}), do: json(conn, events)
defp send_response(conn, {:ok, events}),
do: render(conn, :events, events: events)

defp send_response(conn, {:error, error, reason}),
defp send_response(conn, {:error, :bad_request, reason}),
do:
conn
|> put_status(error)
|> json(%{reason: reason})
|> put_status(:bad_request)
|> put_resp_content_type("application/problem+json")
|> render(:"400", reason: reason)

defp send_response(conn, {:error, :not_found, reason}),
do:
conn
|> put_status(:not_found)
|> put_resp_content_type("application/problem+json")
|> render(:"404", reason: reason)

defp send_response(conn, {:error, reason}),
do:
conn
|> put_status(500)
|> json(%{reason: reason})
|> put_status(:internal_server_error)
|> render(:"500", reason: reason)

defp parse_params(from, to, calendar_id) do
Logger.info("Parsing the arguments ...")
Expand All @@ -50,28 +68,31 @@ defmodule KsuiteMiddlewareWeb.CalendarController do
{:ok, calendar_id} <- parse_calendar_id(calendar_id) do
{:ok, from, to, calendar_id}
else
:invalid_to -> {:error, :bad_request, "The argument 'to' was invalid."}
:invalid_from -> {:error, :bad_request, "The argument 'from' was invalid."}
{:invalid_to, reason} -> {:error, :bad_request, "The argument 'to' was invalid. #{reason}"}
{:invalid_from, reason} -> {:error, :bad_request, "The argument 'from' was invalid. #{reason}"}
{:invalid_calendar_id, reason} -> {:error, :bad_request, reason}
_ -> {:error, :internal_server_error, "Unhandled error occured"}
end
end

defp parse_calendar_id(calendar_id) when byte_size(calendar_id) <= 100, do: {:ok, calendar_id}
defp parse_calendar_id(_), do: {:invalid_calendar_id, "The calendar_id was too long."}

defp parse_datetime(error_atom, datetime) when is_atom(error_atom) and is_bitstring(datetime) do
case Timex.parse!(datetime, "{ISO:Extended:Z}") do
%DateTime{} = x -> {:ok, x}
_ -> error_atom
case Timex.parse(datetime, "{ISO:Extended:Z}") do
{:ok, %DateTime{} = x} -> {:ok, x}
{:error, reason} -> {error_atom, reason}
end
end

defp translate_to_events({:error, :unauthorized}), do: {:error, :unauthorized, "Unauthorized access to the CalDAV server, double check your credentials."}
defp translate_to_events({:error, :unauthorized}),
do: {:error, :unauthorized, "Unauthorized access to the CalDAV server, double check your credentials."}

defp translate_to_events({:error, :not_found}), do: {:error, :not_found, "The given calendar_id was not found in the given server CalDAV."}
defp translate_to_events({:error, reason}), do: {:error, reason}
defp translate_to_events({:error, error, reason}), do: {:error, error, reason}
defp translate_to_events({:ok, []}), do: {:ok, []}
defp translate_to_events({:ok, icalendar_events}), do: translate_to_events(icalendar_events, [])
defp translate_to_events({:ok, [%CalDAVClient.Event{} | _] = icalendar_events}), do: translate_to_events(icalendar_events, [])
defp translate_to_events([], acc) when is_list(acc), do: {:ok, acc}

defp translate_to_events([head | tail], acc) when is_list(acc) do
Expand All @@ -83,7 +104,12 @@ defmodule KsuiteMiddlewareWeb.CalendarController do
from = translate_date_to_utc(from)
to = translate_date_to_utc(to)

new_event = %KsuiteCalendarEvent{subject: summary, from: Timex.format!(from, "{ISO:Extended:Z}"), to: Timex.format!(to, "{ISO:Extended:Z}"), description: description}
new_event = %KsuiteCalendarEvent{
subject: summary,
from: Timex.format!(from, "{ISO:Extended:Z}"),
to: Timex.format!(to, "{ISO:Extended:Z}"),
description: description
}

translate_to_events(tail, [new_event | acc])
end
Expand Down
25 changes: 25 additions & 0 deletions lib/ksuite_middleware_web/controllers/calendar_json.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule KsuiteMiddlewareWeb.CalendarJSON do
alias KsuiteMiddlewareWeb.Models.KsuiteCalendarEvent

def events(%{events: [%KsuiteCalendarEvent{} | _] = events}), do: events

def render("400.json", %{reason: reason}),
do: %{title: "invalid argument", status: 400, detail: reason}

def render("404.json", %{reason: reason}),
do: %{
title: "not found",
status: 404,
detail: reason,
description:
"Oh snap! It seems we've hit a hiccup in our treasure hunt—what you seek is playing hide and seek in the " <>
"digital jungle! Keep those eagle eyes peeled, and perhaps try a different map or a clever keyword dance " <>
"to coax the elusive information out of hiding."
}

def render("500.json", %{reason: reason}),
do: %{title: "internal server error", status: 500, detail: reason}

def render(template, _assigns),
do: %{detail: Phoenix.Controller.status_message_from_template(template)}
end
9 changes: 0 additions & 9 deletions lib/ksuite_middleware_web/controllers/error_controller.ex

This file was deleted.

11 changes: 11 additions & 0 deletions lib/ksuite_middleware_web/controllers/error_json.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ defmodule KsuiteMiddlewareWeb.ErrorJSON do
# %{errors: %{detail: "Internal Server Error"}}
# end

def render("404.json", _),
do: %{
title: "not found",
status: 404,
detail: "The resource requested was not found.",
description:
"Oh snap! It seems we've hit a hiccup in our treasure hunt—what you seek is playing hide and seek in the " <>
"digital jungle! Keep those eagle eyes peeled, and perhaps try a different map or a clever keyword dance " <>
"to coax the elusive information out of hiding."
}

# By default, Phoenix returns the status message from
# the template name. For example, "404.json" becomes
# "Not Found".
Expand Down
16 changes: 16 additions & 0 deletions lib/ksuite_middleware_web/controllers/fallback_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule KsuiteMiddlewareWeb.FallbackController do
@moduledoc """
Translates controller action results into valid `Plug.Conn` responses.
See `Phoenix.Controller.action_fallback/1` for more details.
"""
use KsuiteMiddlewareWeb, :controller

# This clause is an example of how to handle resources that cannot be found.
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> put_view(json: KsuiteMiddlewareWeb.ErrorJSON)
|> render(:"404")
end
end
38 changes: 32 additions & 6 deletions lib/ksuite_middleware_web/controllers/kdrive_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ defmodule KsuiteMiddlewareWeb.KdriveController do

alias KsuiteMiddleware.KsuiteClient

action_fallback KsuiteMiddleware.FallbackController

def pass_thru(conn, %{"file_id" => id}) when is_integer(id) do
with {:ok, response} <- KsuiteClient.download(id) do
conn |> put_tesla_response(response)
else
_ -> conn |> resp(500, "an unknown error occurred.")
_ ->
conn
|> put_status(:bad_gateway)
|> render(:"500")
end
end

Expand All @@ -16,21 +21,41 @@ defmodule KsuiteMiddlewareWeb.KdriveController do
{:ok, response} <- KsuiteClient.download(file_id) do
conn |> put_tesla_response(response)
else
:error ->
conn
|> put_status(:bad_request)
|> put_resp_content_type("application/problem+json")
|> render(:"400", reason: :invalid_integer)

{:error, _} ->
conn
|> put_status(:bad_gateway)
|> put_resp_content_type("application/problem+json")
|> render(:"502", reason: :invalid_response_from_api)

_ ->
conn |> resp(500, "an unknown error occurred.")
conn
|> put_status(:internal_server_error)
|> put_resp_content_type("application/problem+json")
|> render(:"500", reason: :unknown)
end
end

def pass_thru(conn, _params),
do: conn |> resp(400, "The file id was missing.")
do:
conn
|> put_status(:bad_request)
|> put_resp_content_type("application/problem+json")
|> render(:"400", reason: :missing_file_id)

# Private

defp put_tesla_response(%Plug.Conn{} = conn, %Tesla.Env{status: 401}),
do:
conn
|> put_resp_header("content-type", "text/html; charset=utf-8")
|> send_file(400, Application.app_dir(:ksuite_middleware, "priv/static/bad-api-key.html"))
|> put_status(:bad_request)
|> put_resp_content_type("application/problem+json")
|> render(:"400", reason: :bad_api_key)

defp put_tesla_response(%Plug.Conn{} = conn, %Tesla.Env{} = response) do
%Tesla.Env{status: status, body: body} = response
Expand All @@ -42,7 +67,8 @@ defmodule KsuiteMiddlewareWeb.KdriveController do
else
_ ->
conn
|> resp(status, body)
|> put_status(status)
|> render(status |> Integer.to_string() |> String.to_atom())
end
end

Expand Down
29 changes: 29 additions & 0 deletions lib/ksuite_middleware_web/controllers/kdrive_json.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule KsuiteMiddlewareWeb.KdriveJSON do
alias KsuiteMiddleware.State

def render("400.json", %{reason: :invalid_integer}),
do: %{title: "invalid argument", status: 400, detail: "The given file_id was invalid."}

def render("400.json", %{reason: :missing_file_id}),
do: %{title: "missing argument", status: 400, detail: "The file_id was missing."}

def render("400.json", %{reason: :bad_api_key}),
do: %{
title: "bad api key",
status: 400,
detail: "The API key is invalid, please verify the 'KSUITE_API_TOKEN'.",
description:
"Whoopsie-daisy! Looks like we got a little lost in API land. Our secret handshake seems a bit off. " <>
"Double-check that magic spell in the enchanted scroll labeled `KSUITE_API_TOKEN` and make sure it's been " <>
"properly whispered into the winds of the environment variable realm."
}

def render("502.json", %{reason: :invalid_response_from_api}),
do: %{title: "bad gateway", status: 502, detail: "The upstream api returned an invalid response.", server: State.get_ksuite_api_server()}

def render("500.json", %{reason: :unknown}),
do: %{title: "internal server error", status: 500, detail: "An unhandled error was returned, please contact the administrator."}

def render(template, _assigns),
do: %{detail: Phoenix.Controller.status_message_from_template(template)}
end
6 changes: 0 additions & 6 deletions lib/ksuite_middleware_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,4 @@ defmodule KsuiteMiddlewareWeb.Router do
live_dashboard "/dashboard", metrics: KsuiteMiddlewareWeb.Telemetry
end
end

scope "/", KsuiteMiddlewareWeb do
pipe_through :browser

get "/*path", ErrorController, :not_found
end
end
53 changes: 0 additions & 53 deletions priv/static/bad-api-key.html

This file was deleted.

Loading

0 comments on commit d81ac60

Please sign in to comment.