|
#!/usr/bin/env dotnet run |
|
#:sdk Cadenza.Web@1.0.15 |
|
|
|
using System.Globalization; |
|
using Microsoft.AspNetCore.Http; |
|
|
|
const string HtmlPage = """ |
|
<!doctype html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8" /> |
|
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|
<title>Cadenza Calculator</title> |
|
<style> |
|
:root { |
|
--bg: #f4f7ff; |
|
--panel: #ffffff; |
|
--ink: #1d2433; |
|
--muted: #5f697d; |
|
--accent: #0b7a57; |
|
--line: #d4ddef; |
|
} |
|
* { box-sizing: border-box; } |
|
body { |
|
margin: 0; |
|
min-height: 100vh; |
|
display: grid; |
|
place-items: center; |
|
padding: 20px; |
|
color: var(--ink); |
|
font-family: "Segoe UI", Tahoma, sans-serif; |
|
background: |
|
radial-gradient(circle at 10% 10%, #e9f0ff 0%, transparent 40%), |
|
radial-gradient(circle at 90% 90%, #def6ea 0%, transparent 35%), |
|
var(--bg); |
|
} |
|
main { |
|
width: min(760px, 100%); |
|
background: var(--panel); |
|
border: 1px solid var(--line); |
|
border-radius: 14px; |
|
box-shadow: 0 14px 30px rgba(16, 34, 74, 0.12); |
|
overflow: hidden; |
|
} |
|
header { |
|
padding: 18px 20px; |
|
border-bottom: 1px solid var(--line); |
|
background: linear-gradient(135deg, #f8fbff, #eefaf4); |
|
} |
|
h1 { margin: 0; font-size: 1.35rem; } |
|
p { margin: 8px 0 0; color: var(--muted); } |
|
.content { padding: 20px; display: grid; gap: 12px; } |
|
.row { display: grid; gap: 10px; grid-template-columns: 1fr 130px 1fr auto; } |
|
input, select, button { font: inherit; } |
|
input, select { |
|
width: 100%; |
|
border: 1px solid var(--line); |
|
border-radius: 10px; |
|
padding: 10px 12px; |
|
background: #fff; |
|
} |
|
button { |
|
border: 0; |
|
border-radius: 10px; |
|
padding: 10px 14px; |
|
background: var(--accent); |
|
color: #fff; |
|
font-weight: 600; |
|
cursor: pointer; |
|
} |
|
pre { |
|
margin: 0; |
|
min-height: 130px; |
|
border: 1px solid var(--line); |
|
border-radius: 10px; |
|
padding: 12px; |
|
background: #fff; |
|
overflow: auto; |
|
} |
|
.links { display: flex; gap: 12px; flex-wrap: wrap; } |
|
.links a { color: var(--accent); text-underline-offset: 3px; } |
|
@media (max-width: 640px) { .row { grid-template-columns: 1fr; } } |
|
</style> |
|
</head> |
|
<body> |
|
<main> |
|
<header> |
|
<h1>Arithmetic Calculator</h1> |
|
<p>Cadenza.Web single-file app with OpenAPI JSON endpoint.</p> |
|
</header> |
|
|
|
<section class="content"> |
|
<div class="row"> |
|
<input id="a" type="number" step="any" value="12" aria-label="a" /> |
|
<select id="op" aria-label="op"> |
|
<option value="add">add (+)</option> |
|
<option value="sub">sub (-)</option> |
|
<option value="mul">mul (*)</option> |
|
<option value="div">div (/)</option> |
|
</select> |
|
<input id="b" type="number" step="any" value="3" aria-label="b" /> |
|
<button id="run" type="button">Calculate</button> |
|
</div> |
|
|
|
<pre id="result">No calculation yet.</pre> |
|
|
|
<div class="links"> |
|
<a href="/openapi/v1.json" target="_blank" rel="noreferrer">OpenAPI JSON</a> |
|
<a href="/api/calc?a=12&b=3&op=div" target="_blank" rel="noreferrer">Sample API call</a> |
|
</div> |
|
</section> |
|
</main> |
|
|
|
<script> |
|
const resultEl = document.getElementById('result'); |
|
const runBtn = document.getElementById('run'); |
|
|
|
runBtn.addEventListener('click', async () => { |
|
const a = document.getElementById('a').value; |
|
const b = document.getElementById('b').value; |
|
const op = document.getElementById('op').value; |
|
const url = `/api/calc?a=${encodeURIComponent(a)}&b=${encodeURIComponent(b)}&op=${encodeURIComponent(op)}`; |
|
|
|
resultEl.textContent = 'Calculating...'; |
|
|
|
try { |
|
const response = await fetch(url); |
|
const data = await response.json(); |
|
resultEl.textContent = JSON.stringify(data, null, 2); |
|
} catch (err) { |
|
resultEl.textContent = String(err); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |
|
"""; |
|
|
|
const string OpenApiJson = """ |
|
{ |
|
"openapi": "3.0.3", |
|
"info": { |
|
"title": "Cadenza Arithmetic Calculator API", |
|
"version": "1.0.0", |
|
"description": "Simple arithmetic operations: add, sub, mul, div" |
|
}, |
|
"servers": [ |
|
{ "url": "http://localhost:8080" } |
|
], |
|
"paths": { |
|
"/api/calc": { |
|
"get": { |
|
"summary": "Calculate with query parameters", |
|
"parameters": [ |
|
{ "name": "a", "in": "query", "required": true, "schema": { "type": "number", "format": "double" } }, |
|
{ "name": "b", "in": "query", "required": true, "schema": { "type": "number", "format": "double" } }, |
|
{ "name": "op", "in": "query", "required": true, "schema": { "type": "string", "enum": ["add", "sub", "mul", "div"] } } |
|
], |
|
"responses": { |
|
"200": { "description": "Calculation succeeded" }, |
|
"400": { "description": "Invalid input" } |
|
} |
|
} |
|
} |
|
} |
|
} |
|
"""; |
|
|
|
Get("/", () => Results.Content(HtmlPage, "text/html; charset=utf-8")); |
|
|
|
Get("/api/calc", (string a, string b, string op) => |
|
{ |
|
if (!double.TryParse(a, NumberStyles.Float, CultureInfo.InvariantCulture, out var left)) |
|
return Results.BadRequest(new { error = "Parameter 'a' must be a valid number." }); |
|
|
|
if (!double.TryParse(b, NumberStyles.Float, CultureInfo.InvariantCulture, out var right)) |
|
return Results.BadRequest(new { error = "Parameter 'b' must be a valid number." }); |
|
|
|
var normalized = NormalizeOp(op); |
|
if (!TryCalculate(left, right, normalized, out var result, out var error)) |
|
return Results.BadRequest(new { error }); |
|
|
|
return Results.Ok(new |
|
{ |
|
a = left, |
|
b = right, |
|
op = normalized, |
|
expression = $"{left} {OpSymbol(normalized)} {right}", |
|
result |
|
}); |
|
}); |
|
|
|
Get("/openapi/v1.json", () => Results.Content(OpenApiJson, "application/json; charset=utf-8")); |
|
|
|
await Run(); |
|
|
|
static bool TryCalculate(double a, double b, string op, out double value, out string error) |
|
{ |
|
value = 0; |
|
error = string.Empty; |
|
|
|
switch (op) |
|
{ |
|
case "add": |
|
value = a + b; |
|
return true; |
|
case "sub": |
|
value = a - b; |
|
return true; |
|
case "mul": |
|
value = a * b; |
|
return true; |
|
case "div": |
|
if (b == 0) |
|
{ |
|
error = "Division by zero is not allowed."; |
|
return false; |
|
} |
|
value = a / b; |
|
return true; |
|
default: |
|
error = "Parameter 'op' must be one of: add, sub, mul, div."; |
|
return false; |
|
} |
|
} |
|
|
|
static string NormalizeOp(string? op) => |
|
(op ?? string.Empty).Trim().ToLowerInvariant() switch |
|
{ |
|
"+" => "add", |
|
"-" => "sub", |
|
"*" => "mul", |
|
"/" => "div", |
|
var x => x, |
|
}; |
|
|
|
static string OpSymbol(string op) => op switch |
|
{ |
|
"add" => "+", |
|
"sub" => "-", |
|
"mul" => "*", |
|
"div" => "/", |
|
_ => "?" |
|
}; |