Created
June 25, 2025 16:19
-
-
Save BashkaMen/3412cc15306201f1b0dc06e0558ae3bf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MiddlewareResult do | |
@type t :: {:pass, Plug.Conn.t()} | :reject | |
end | |
defmodule NextMiddleware do | |
@type t :: (Plug.Conn.t() -> MiddlewareResult.t()) | |
@spec return :: t() | |
def return, do: fn %Plug.Conn{} = conn -> {:pass, conn} end | |
@spec skip :: t() | |
def skip, do: fn %Plug.Conn{} = _conn -> :reject end | |
end | |
defmodule Middleware do | |
@type t :: (Plug.Conn.t(), NextMiddleware.t() -> MiddlewareResult.t()) | |
@spec combine([t()]) :: t() | |
def combine(middlewares) do | |
fn %Plug.Conn{} = conn, next -> | |
next = | |
middlewares | |
|> Enum.reverse() | |
|> Enum.reduce(next, fn middleware, next -> | |
fn conn -> middleware.(conn, next) end | |
end) | |
next.(conn) | |
end | |
end | |
@spec choose([t()]) :: t() | |
def choose([]), do: NextMiddleware.skip() | |
def choose([middleware | rest]) do | |
fn %Plug.Conn{} = conn, next -> | |
case middleware.(conn, next) do | |
{:pass, conn} -> {:pass, conn} | |
:reject -> choose(rest).(conn, next) | |
end | |
end | |
end | |
@spec method(String.t()) :: t() | |
def method(name) do | |
fn %Plug.Conn{} = conn, next -> | |
if conn.method != name, do: :reject, else: next.(conn) | |
end | |
end | |
@spec route(String.t()) :: t() | |
def route(path) do | |
fn %Plug.Conn{} = conn, next -> | |
if conn.request_path != path, do: :reject, else: next.(conn) | |
end | |
end | |
@spec route(String.t(), String.t()) :: t() | |
def route(method, path) do | |
combine([ | |
method(method), | |
route(path) | |
]) | |
end | |
def set_header(key, value) do | |
fn %Plug.Conn{} = conn, next -> | |
conn = Plug.Conn.put_resp_header(conn, key, value) | |
next.(conn) | |
end | |
end | |
def set_code(code) do | |
fn %Plug.Conn{} = conn, next -> | |
conn = Plug.Conn.put_status(conn, code) | |
next.(conn) | |
end | |
end | |
def set_body(body) do | |
fn %Plug.Conn{} = conn, next -> | |
next.(%{conn | resp_body: body, state: :set}) | |
end | |
end | |
@spec set_json_body(any()) :: t() | |
def set_json_body(body) do | |
json = Jason.encode!(body) | |
combine([ | |
set_header("Content-Type", "application/json"), | |
set_body(json) | |
]) | |
end | |
@spec call(Plug.Conn.t(), Middleware.t()) :: Plug.Conn.t() | |
def call(%Plug.Conn{} = conn, web_app) do | |
{:pass, conn} = web_app.(conn, NextMiddleware.return()) | |
Plug.Conn.send_resp(conn) | |
end | |
end | |
defmodule Router do | |
require Logger | |
import Middleware | |
@spec not_found :: Middleware.t() | |
def not_found(), | |
do: | |
combine([ | |
set_header("Content-Type", "text/plain"), | |
set_code(404), | |
set_body("Not found") | |
]) | |
@spec error_handler :: Middleware.t() | |
def error_handler() do | |
fn %Plug.Conn{} = conn, next -> | |
# SUPERVISOR???????? | |
try do | |
next.(conn) | |
rescue | |
e -> | |
Logger.error("Error: #{inspect(e)}") | |
combine([ | |
set_code(500), | |
set_json_body(%{message: "Internal server error"}) | |
]).(conn, NextMiddleware.return()) | |
end | |
end | |
end | |
@spec raw_hello :: Middleware.t() | |
def raw_hello() do | |
combine([ | |
method("GET"), | |
route("/hello"), | |
set_header("Content-Type", "text/plain"), | |
set_code(200), | |
set_body("Hello world") | |
]) | |
end | |
@spec html :: Middleware.t() | |
def html() do | |
combine([ | |
method("GET"), | |
route("/html"), | |
set_header("Content-Type", "text/html"), | |
set_code(200), | |
set_body("<div>Hello world</div>") | |
]) | |
end | |
@spec error_trigger :: Middleware.t() | |
def error_trigger() do | |
combine([ | |
method("GET"), | |
route("/error"), | |
fn _conn, _next -> | |
raise("error, supervisor help me!!!") | |
end | |
]) | |
end | |
def json_handler(handler) do | |
fn %Plug.Conn{} = conn, next -> | |
res = handler.(conn) | |
combine([ | |
set_code(200), | |
set_json_body(res) | |
]).(conn, next) | |
end | |
end | |
def create_user() do | |
combine([ | |
route("GET", "/:create-user"), | |
json_handler(fn conn -> | |
%{"name" => name} = conn.params | |
# todo create user | |
%{id: "created-id", name: name} | |
end) | |
]) | |
end | |
@behaviour Plug | |
def init(opts) do | |
opts | |
end | |
def call(conn, _opts) do | |
Middleware.call(conn, web_app()) | |
end | |
def web_app() do | |
combine([ | |
error_handler(), | |
choose([ | |
# match /hello | |
raw_hello(), | |
# match /custom | |
html(), | |
error_trigger(), | |
create_user(), | |
# everything else to 404 | |
not_found() | |
]) | |
]) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment