extern fn serve(port, handler) explain { Start a server and handle requests. };
extern fn str_join(parts, sep) explain { Join strings with separator. };
extern fn str_len(s) explain { Returns UTF-8 byte length. };
extern fn int_to_str(n) explain { Converts Int to Str. };
data Request = Request { method, path, query, raw };
data LinkItem = LinkItem { href, label };
fn join(parts) = str_join(parts, "");
fn reason(status) =
match status {
200 => "OK";
201 => "Created";
204 => "No Content";
301 => "Moved Permanently";
302 => "Found";
400 => "Bad Request";
401 => "Unauthorized";
403 => "Forbidden";
404 => "Not Found";
405 => "Method Not Allowed";
500 => "Internal Server Error";
503 => "Service Unavailable";
_ => "OK";
};
fn content_length(body) = int_to_str(str_len(body));
fn http_response(status, reason, content_type, body) =
join(
[
"HTTP/1.1 ",
int_to_str(status),
" ",
reason,
"\r\n",
"Content-Type: ",
content_type,
"\r\n",
"Content-Length: ",
content_length(body),
"\r\n",
"Connection: close\r\n",
"\r\n",
body
]
);
fn response(status, content_type, body) =
http_response(status, reason(status), content_type, body);
fn text(body) = response(200, "text/plain; charset=utf-8", body);
fn html(body) = response(200, "text/html; charset=utf-8", body);
fn json(body) = response(200, "application/json; charset=utf-8", body);
fn not_found() = response(404, "text/plain; charset=utf-8", "not found\n");
fn method_not_allowed() = response(405, "text/plain; charset=utf-8", "method not allowed\n");
fn link(href, label) = join(["<a href=\"", href, "\">", label, "</a>"]);
fn h1(body) = join(["<h1>", body, "</h1>"]);
fn h2(body) = join(["<h2>", body, "</h2>"]);
fn p(body) = join(["<p>", body, "</p>"]);
fn code(body) = join(["<code>", body, "</code>"]);
fn li(body) = join(["<li>", body, "</li>"]);
fn ul(items) = join(["<ul>", str_join(items, ""), "</ul>"]);
let nav_links =
[
LinkItem { href: "/", label: "Home" },
LinkItem { href: "/docs", label: "Docs" },
LinkItem { href: "/api", label: "API" }
];
fn render_nav_link(item) =
match item {
LinkItem { href, label } => link(href, label);
_ => "";
};
fn nav() =
join(
[
"<nav>",
str_join(for item in nav_links { render_nav_link(item) }, " | "),
"</nav>"
]
);
fn layout(title, body) =
join(
[
"<!doctype html>\n",
"<html><head><meta charset=\"utf-8\">",
"<title>",
title,
"</title>",
"<style>body{font-family:sans-serif;margin:40px;}nav{margin-bottom:20px;}code{background:#eee;padding:2px 4px;}</style>",
"</head><body>",
nav(),
body,
"</body></html>\n"
]
);
let home_page =
layout(
"IF mini web",
join(
[
h1("IF mini web"),
p("This page is rendered by IF logic; Rust only handles HTTP I/O."),
h2("Highlights"),
ul(
[
li("Routes and pages are defined in IF"),
li("Full HTTP responses are built in IF"),
li("API responses are easy to extend")
]
)
]
)
);
let docs_page =
layout(
"Docs",
join(
[
h1("Usage"),
p("Handler returns a full HTTP response string."),
p(
join(
["Example ports: ", str_join(for p in [8080..8082] { int_to_str(p) }, ", ")]
)
),
p(
join(
["Example: ", code("extern fn serve(port, handler) explain { Start server. };")]
)
),
h2("Routing"),
ul(
[li("GET requests match the path"), li("Other methods return 405 by default")]
)
]
)
);
let api_page =
layout(
"API",
join(
[
h1("API list"),
ul(
for path in ["/api/info", "/api/features", "/api/routes"] { li(link(path, path)) }
)
]
)
);
let api_info =
json(
join(
[
"{",
"\"name\":\"IF mini web\",",
"\"version\":\"0.1\",",
"\"language\":\"IF Lang\"",
"}"
]
)
);
let api_features = json(join(["{", "\"features\":[\"routes\",\"html\",\"json\",\"bytes\"]", "}"]));
let api_routes =
json(
join(
[
"{",
"\"routes\":[\"/\",\"/docs\",\"/api\",\"/api/info\",\"/api/features\",\"/api/routes\",\"/health\",\"/raw\"]",
"}"
]
)
);
fn get_route(path) =
match path {
"/" => html(home_page);
"/docs" => html(docs_page);
"/api" => html(api_page);
"/api/info" => api_info;
"/api/features" => api_features;
"/api/routes" => api_routes;
"/health" => text("ok\n");
"/raw" => response(200, "application/octet-stream", "raw bytes\n");
_ => not_found();
};
fn handle(req) =
match req {
Request { method, path, query, raw } =>
if method == "GET" {
get_route(path)
} else {
method_not_allowed()
};
_ => response(500, "text/plain; charset=utf-8", "invalid request\n");
};
let started = serve(8080, handle);