gitoriaLog in with ident

ident

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commitcc063ea2cc063ea2deploy.sh: never send .git or .gitignore to Byrodinmrecc063ea2/api.hl

3.8 KB

  1. // api.hl — the answers of the function routes: JSON, redirects, the HTML error page, and
  2. // the STRICT JSON body. Statics only; the routes are in project.hl.
  3. //
  4. // STRICT: a body must be a JSON object; an unknown field, a missing field and a field of
  5. // the wrong type are each a 400 whose `error` names the field. hl:json's JSON.parse
  6. // aborts the request (500) on invalid input and the language has no catch (hybriel #12),
  7. // so the body's syntax is checked first by jsoncheck.hl (copied from tickets).
  8. import { Response } from 'hl:http1'
  9. import { jsonErrorAt } from './jsoncheck.hl'
  10. static jsonHeaders = { 'Content-Type' = 'application/json; charset=utf-8' 'Cache-Control' = 'no-store' }
  11. static htmlHeaders = { 'Content-Type' = 'text/html; charset=utf-8' 'Cache-Control' = 'no-store' }
  12. static reply = (status, value) => { return new Response(JSON.stringify(value), { status = status headers = jsonHeaders }) }
  13. static fail = (status, message) => { return reply(status, { error = message }) }
  14. static redirect = (url) => { return new Response('', { status = 302 headers = { 'Location' = url 'Cache-Control' = 'no-store' } }) }
  15. // THE STRICT BODY: `spec` maps each allowed field to { type ('String' | 'Number' |
  16. // 'Boolean' | 'List'), required }. Answers { error } or { body }.
  17. static strictBody = (req, spec) => {
  18. let raw = req.body
  19. if (raw == null || raw.trim() == '') { return { error = 'a JSON object body is required' } }
  20. let at = jsonErrorAt(raw)
  21. if (at >= 0) { return { error = 'the body is not valid JSON (at character ' + at + ')' } }
  22. let t = raw.trim()
  23. if (t[0] != '{') { return { error = 'the body must be a JSON object' } }
  24. let b = JSON.parse(raw)
  25. for (k of b.keys()) {
  26. if (spec[k] == null) { return { error = 'unknown field: ' + k } }
  27. }
  28. for (k of spec.keys()) {
  29. let f = spec[k]
  30. let v = b[k]
  31. if (v == null) {
  32. if (f.required) { return { error = 'missing field: ' + k } }
  33. } else if (f.type == 'List') {
  34. if (hlTypeName(v) != 'Hybrid' || v.length == null) { return { error = 'field ' + k + ' must be a list' } }
  35. } else if (hlTypeName(v) != f.type) {
  36. return { error = 'field ' + k + ' must be a ' + f.type.toLowerCase() }
  37. }
  38. }
  39. return { body = b }
  40. }
  41. // ---- the HTML error page of the login button (never a redirect) ------------------------
  42. static escapeHtml = (s) => { return ('' + s).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;') }
  43. static errorPage = (status, title, message) => {
  44. let html = '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>ident | ' + escapeHtml(title) + '</title><style>:root{--dark:rgb(25, 30, 35);--darker:rgb(15, 20, 25);--light:rgb(195, 200, 205);--lighter:rgb(245, 250, 255);--red:#f44747;--color-background:var(--dark);--color-surface:rgb(35, 40, 45);--color-border:rgb(70, 75, 80);--color-text:var(--light);--color-danger:var(--red);--color-accent:#ce9178}html{color-scheme:dark}body{margin:0;font:16px/1.5 system-ui,sans-serif;color:var(--color-text);background:var(--color-background)}application-header{display:block;padding:.6rem 1rem;background:var(--darker);border-bottom:1px solid var(--color-border)}application-header a{font-weight:800;font-size:1.15rem;color:var(--color-accent);text-decoration:none}main{padding:2rem 1rem}ident-error{display:block;max-width:34rem;margin:0 auto;padding:1rem;background:var(--color-surface);border:1px solid var(--color-border);border-radius:.5rem}h1{margin:0 0 .5rem;font-size:1.2rem;color:var(--color-danger)}p{margin:0;overflow-wrap:anywhere}</style></head><body><application-header><a href="/">ident</a></application-header><main><ident-error id="identerror"><h1>' + escapeHtml(title) + '</h1><p id="errormessage">' + escapeHtml(message) + '</p></ident-error></main></body></html>'
  45. return new Response(html, { status = status headers = htmlHeaders })
  46. }

Branches

Latest commits

  • cc063ea2deploy.sh: never send .git or .gitignore to Byrodinmre
  • 81b15b7bState of 2026-09-27, before the move to gitoriamre