Skip to content

Commit d81ac60

Browse files
author
Burgy Benjamin
committed
Follow the phoenix recommandations to handle the json rendering, add fallback actions and return the errors by following the RFC 7807.
1 parent 240b21e commit d81ac60

File tree

13 files changed

+158
-143
lines changed

13 files changed

+158
-143
lines changed

.formatter.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[
2-
line_length: 300,
2+
line_length: 150,
33
import_deps: [:phoenix],
44
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
55
]

lib/ksuite_middleware/ksuite_client.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule KsuiteMiddleware.KsuiteClient do
55

66
require Logger
77

8-
plug Tesla.Middleware.BaseUrl, "https://api.infomaniak.com"
8+
plug Tesla.Middleware.BaseUrl, State.get_ksuite_api_server()
99
plug Tesla.Middleware.Logger, debug: false
1010
plug Tesla.Middleware.PathParams
1111
plug Tesla.Middleware.Headers, [{"User-Agent", "ksuite-middleware"}]

lib/ksuite_middleware/state.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ defmodule KsuiteMiddleware.State do
1818
@spec get_timezone() :: Timex.TimezoneInfo.t()
1919
def get_timezone(), do: GenServer.call(State, :get_timezone)
2020

21+
@spec get_ksuite_api_server() :: String.t()
22+
def get_ksuite_api_server(), do: "https://api.infomaniak.com"
23+
2124
# Callbacks
2225

2326
@impl true

lib/ksuite_middleware_web/controllers/calendar_controller.ex

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,22 @@ defmodule KsuiteMiddlewareWeb.CalendarController do
77

88
require Logger
99

10+
action_fallback KsuiteMiddleware.FallbackController
11+
1012
def get_events(conn, %{"from" => from, "to" => to, "calendar_id" => calendar_id}),
1113
do:
1214
parse_params(from, to, calendar_id)
1315
|> then(&read_events/1)
1416
|> then(&translate_to_events/1)
1517
|> then(&send_response(conn, &1))
1618

19+
def get_events(conn, _),
20+
do:
21+
conn
22+
|> put_status(:bad_request)
23+
|> put_resp_content_type("application/problem+json")
24+
|> render(:"400", reason: "The arguments were missing.")
25+
1726
# Private
1827

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

31-
defp send_response(conn, {:ok, events}), do: json(conn, events)
40+
defp send_response(conn, {:ok, events}),
41+
do: render(conn, :events, events: events)
3242

33-
defp send_response(conn, {:error, error, reason}),
43+
defp send_response(conn, {:error, :bad_request, reason}),
3444
do:
3545
conn
36-
|> put_status(error)
37-
|> json(%{reason: reason})
46+
|> put_status(:bad_request)
47+
|> put_resp_content_type("application/problem+json")
48+
|> render(:"400", reason: reason)
49+
50+
defp send_response(conn, {:error, :not_found, reason}),
51+
do:
52+
conn
53+
|> put_status(:not_found)
54+
|> put_resp_content_type("application/problem+json")
55+
|> render(:"404", reason: reason)
3856

3957
defp send_response(conn, {:error, reason}),
4058
do:
4159
conn
42-
|> put_status(500)
43-
|> json(%{reason: reason})
60+
|> put_status(:internal_server_error)
61+
|> render(:"500", reason: reason)
4462

4563
defp parse_params(from, to, calendar_id) do
4664
Logger.info("Parsing the arguments ...")
@@ -50,28 +68,31 @@ defmodule KsuiteMiddlewareWeb.CalendarController do
5068
{:ok, calendar_id} <- parse_calendar_id(calendar_id) do
5169
{:ok, from, to, calendar_id}
5270
else
53-
:invalid_to -> {:error, :bad_request, "The argument 'to' was invalid."}
54-
:invalid_from -> {:error, :bad_request, "The argument 'from' was invalid."}
71+
{:invalid_to, reason} -> {:error, :bad_request, "The argument 'to' was invalid. #{reason}"}
72+
{:invalid_from, reason} -> {:error, :bad_request, "The argument 'from' was invalid. #{reason}"}
5573
{:invalid_calendar_id, reason} -> {:error, :bad_request, reason}
74+
_ -> {:error, :internal_server_error, "Unhandled error occured"}
5675
end
5776
end
5877

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

6281
defp parse_datetime(error_atom, datetime) when is_atom(error_atom) and is_bitstring(datetime) do
63-
case Timex.parse!(datetime, "{ISO:Extended:Z}") do
64-
%DateTime{} = x -> {:ok, x}
65-
_ -> error_atom
82+
case Timex.parse(datetime, "{ISO:Extended:Z}") do
83+
{:ok, %DateTime{} = x} -> {:ok, x}
84+
{:error, reason} -> {error_atom, reason}
6685
end
6786
end
6887

69-
defp translate_to_events({:error, :unauthorized}), do: {:error, :unauthorized, "Unauthorized access to the CalDAV server, double check your credentials."}
88+
defp translate_to_events({:error, :unauthorized}),
89+
do: {:error, :unauthorized, "Unauthorized access to the CalDAV server, double check your credentials."}
90+
7091
defp translate_to_events({:error, :not_found}), do: {:error, :not_found, "The given calendar_id was not found in the given server CalDAV."}
7192
defp translate_to_events({:error, reason}), do: {:error, reason}
7293
defp translate_to_events({:error, error, reason}), do: {:error, error, reason}
7394
defp translate_to_events({:ok, []}), do: {:ok, []}
74-
defp translate_to_events({:ok, icalendar_events}), do: translate_to_events(icalendar_events, [])
95+
defp translate_to_events({:ok, [%CalDAVClient.Event{} | _] = icalendar_events}), do: translate_to_events(icalendar_events, [])
7596
defp translate_to_events([], acc) when is_list(acc), do: {:ok, acc}
7697

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

86-
new_event = %KsuiteCalendarEvent{subject: summary, from: Timex.format!(from, "{ISO:Extended:Z}"), to: Timex.format!(to, "{ISO:Extended:Z}"), description: description}
107+
new_event = %KsuiteCalendarEvent{
108+
subject: summary,
109+
from: Timex.format!(from, "{ISO:Extended:Z}"),
110+
to: Timex.format!(to, "{ISO:Extended:Z}"),
111+
description: description
112+
}
87113

88114
translate_to_events(tail, [new_event | acc])
89115
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule KsuiteMiddlewareWeb.CalendarJSON do
2+
alias KsuiteMiddlewareWeb.Models.KsuiteCalendarEvent
3+
4+
def events(%{events: [%KsuiteCalendarEvent{} | _] = events}), do: events
5+
6+
def render("400.json", %{reason: reason}),
7+
do: %{title: "invalid argument", status: 400, detail: reason}
8+
9+
def render("404.json", %{reason: reason}),
10+
do: %{
11+
title: "not found",
12+
status: 404,
13+
detail: reason,
14+
description:
15+
"Oh snap! It seems we've hit a hiccup in our treasure hunt—what you seek is playing hide and seek in the " <>
16+
"digital jungle! Keep those eagle eyes peeled, and perhaps try a different map or a clever keyword dance " <>
17+
"to coax the elusive information out of hiding."
18+
}
19+
20+
def render("500.json", %{reason: reason}),
21+
do: %{title: "internal server error", status: 500, detail: reason}
22+
23+
def render(template, _assigns),
24+
do: %{detail: Phoenix.Controller.status_message_from_template(template)}
25+
end

lib/ksuite_middleware_web/controllers/error_controller.ex

Lines changed: 0 additions & 9 deletions
This file was deleted.

lib/ksuite_middleware_web/controllers/error_json.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ defmodule KsuiteMiddlewareWeb.ErrorJSON do
66
# %{errors: %{detail: "Internal Server Error"}}
77
# end
88

9+
def render("404.json", _),
10+
do: %{
11+
title: "not found",
12+
status: 404,
13+
detail: "The resource requested was not found.",
14+
description:
15+
"Oh snap! It seems we've hit a hiccup in our treasure hunt—what you seek is playing hide and seek in the " <>
16+
"digital jungle! Keep those eagle eyes peeled, and perhaps try a different map or a clever keyword dance " <>
17+
"to coax the elusive information out of hiding."
18+
}
19+
920
# By default, Phoenix returns the status message from
1021
# the template name. For example, "404.json" becomes
1122
# "Not Found".
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
defmodule KsuiteMiddlewareWeb.FallbackController do
2+
@moduledoc """
3+
Translates controller action results into valid `Plug.Conn` responses.
4+
5+
See `Phoenix.Controller.action_fallback/1` for more details.
6+
"""
7+
use KsuiteMiddlewareWeb, :controller
8+
9+
# This clause is an example of how to handle resources that cannot be found.
10+
def call(conn, {:error, :not_found}) do
11+
conn
12+
|> put_status(:not_found)
13+
|> put_view(json: KsuiteMiddlewareWeb.ErrorJSON)
14+
|> render(:"404")
15+
end
16+
end

lib/ksuite_middleware_web/controllers/kdrive_controller.ex

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ defmodule KsuiteMiddlewareWeb.KdriveController do
33

44
alias KsuiteMiddleware.KsuiteClient
55

6+
action_fallback KsuiteMiddleware.FallbackController
7+
68
def pass_thru(conn, %{"file_id" => id}) when is_integer(id) do
79
with {:ok, response} <- KsuiteClient.download(id) do
810
conn |> put_tesla_response(response)
911
else
10-
_ -> conn |> resp(500, "an unknown error occurred.")
12+
_ ->
13+
conn
14+
|> put_status(:bad_gateway)
15+
|> render(:"500")
1116
end
1217
end
1318

@@ -16,21 +21,41 @@ defmodule KsuiteMiddlewareWeb.KdriveController do
1621
{:ok, response} <- KsuiteClient.download(file_id) do
1722
conn |> put_tesla_response(response)
1823
else
24+
:error ->
25+
conn
26+
|> put_status(:bad_request)
27+
|> put_resp_content_type("application/problem+json")
28+
|> render(:"400", reason: :invalid_integer)
29+
30+
{:error, _} ->
31+
conn
32+
|> put_status(:bad_gateway)
33+
|> put_resp_content_type("application/problem+json")
34+
|> render(:"502", reason: :invalid_response_from_api)
35+
1936
_ ->
20-
conn |> resp(500, "an unknown error occurred.")
37+
conn
38+
|> put_status(:internal_server_error)
39+
|> put_resp_content_type("application/problem+json")
40+
|> render(:"500", reason: :unknown)
2141
end
2242
end
2343

2444
def pass_thru(conn, _params),
25-
do: conn |> resp(400, "The file id was missing.")
45+
do:
46+
conn
47+
|> put_status(:bad_request)
48+
|> put_resp_content_type("application/problem+json")
49+
|> render(:"400", reason: :missing_file_id)
2650

2751
# Private
2852

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

3560
defp put_tesla_response(%Plug.Conn{} = conn, %Tesla.Env{} = response) do
3661
%Tesla.Env{status: status, body: body} = response
@@ -42,7 +67,8 @@ defmodule KsuiteMiddlewareWeb.KdriveController do
4267
else
4368
_ ->
4469
conn
45-
|> resp(status, body)
70+
|> put_status(status)
71+
|> render(status |> Integer.to_string() |> String.to_atom())
4672
end
4773
end
4874

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
defmodule KsuiteMiddlewareWeb.KdriveJSON do
2+
alias KsuiteMiddleware.State
3+
4+
def render("400.json", %{reason: :invalid_integer}),
5+
do: %{title: "invalid argument", status: 400, detail: "The given file_id was invalid."}
6+
7+
def render("400.json", %{reason: :missing_file_id}),
8+
do: %{title: "missing argument", status: 400, detail: "The file_id was missing."}
9+
10+
def render("400.json", %{reason: :bad_api_key}),
11+
do: %{
12+
title: "bad api key",
13+
status: 400,
14+
detail: "The API key is invalid, please verify the 'KSUITE_API_TOKEN'.",
15+
description:
16+
"Whoopsie-daisy! Looks like we got a little lost in API land. Our secret handshake seems a bit off. " <>
17+
"Double-check that magic spell in the enchanted scroll labeled `KSUITE_API_TOKEN` and make sure it's been " <>
18+
"properly whispered into the winds of the environment variable realm."
19+
}
20+
21+
def render("502.json", %{reason: :invalid_response_from_api}),
22+
do: %{title: "bad gateway", status: 502, detail: "The upstream api returned an invalid response.", server: State.get_ksuite_api_server()}
23+
24+
def render("500.json", %{reason: :unknown}),
25+
do: %{title: "internal server error", status: 500, detail: "An unhandled error was returned, please contact the administrator."}
26+
27+
def render(template, _assigns),
28+
do: %{detail: Phoenix.Controller.status_message_from_template(template)}
29+
end

0 commit comments

Comments
 (0)