From 1c0f89f4cadba4957e95fc80b25b1b8b6dd1a3b3 Mon Sep 17 00:00:00 2001 From: sterni Date: Mon, 5 Apr 2021 01:53:21 +0200 Subject: feat(web/bubblegum): report some errors to the user via HTTP We can actually catch some errors that may be generated in bubblegum applications where we can report them to the user in a way that doesn't require curl -vv: * Type errors in the status argument: By removing yants completely we not only (presumably) gain some performance, but also the ability to return an internal server error on an unexpected type instead of throwing. * User generated evaluation errors: by using builtins.tryEval we can catch throws and asserts the user inserted when generating the body and report to the user that something went wrong. To do: also support for the headers. Change-Id: I8363b9825c6c730e624eb8016a5482d63cbc1890 Reviewed-on: https://cl.tvl.fyi/c/depot/+/2849 Tested-by: BuildkiteCI Reviewed-by: sterni --- web/bubblegum/default.nix | 73 +++++++++++++++++++++------------------- web/bubblegum/examples/hello.nix | 12 +++++++ 2 files changed, 51 insertions(+), 34 deletions(-) (limited to 'web') diff --git a/web/bubblegum/default.nix b/web/bubblegum/default.nix index 81e41cfbdfbd..393ac75d4803 100644 --- a/web/bubblegum/default.nix +++ b/web/bubblegum/default.nix @@ -2,23 +2,11 @@ let - inherit (depot.nix.yants) - defun - restrict - struct - string - int - attrs - enum - ; - inherit (depot.nix) runExecline getBins ; - headers = attrs string; - statusCodes = { # 1xx "Continue" = 100; @@ -90,9 +78,6 @@ let "Network Authentication Required" = 511; }; - status = enum "bubblegum.status" - (builtins.attrNames statusCodes); - /* Generate a CGI response. Takes three arguments: 1. Status of the response as a string which is @@ -104,21 +89,42 @@ let See the [README](./README.md) for an example. - Type: Status -> Headers -> Body -> string + Type: string -> attrs string -> string -> string */ - respond = defun [ status headers string string ] - (s: hs: body: - let - code = status.match s statusCodes; - renderedHeaders = lib.concatStrings - (lib.mapAttrsToList (n: v: "${n}: ${v}\r\n") hs); - in - lib.concatStrings [ - "Status: ${toString code} ${s}\r\n" - renderedHeaders - "\r\n" - body - ]); + respond = + # response status as the textual representation in the + # HTTP protocol. See `statusCodes` for a list of valid + # options. + statusArg: + # headers as an attribute set of strings + headers: + # response body as a string + bodyArg: + let + status = + if builtins.isString statusArg then { + code = statusCodes."${statusArg}" or null; + line = statusArg; + } else { + code = null; line = null; + }; + renderedHeaders = lib.concatStrings + (lib.mapAttrsToList (n: v: "${n}: ${toString v}\r\n") headers); + internalError = msg: respond 500 { + Content-type = "text/plain"; + } "bubblegum error: ${msg}"; + body = builtins.tryEval bodyArg; + in + if status.code == null || status.line == null + then internalError "Invalid status ${lib.generators.toPretty {} statusArg}." + else if !body.success + then internalError "Unknown evaluation error in user code" + else lib.concatStrings [ + "Status: ${toString status.code} ${status.line}\r\n" + renderedHeaders + "\r\n" + body.value + ]; /* Returns the value of the `SCRIPT_NAME` environment variable used by CGI. @@ -147,11 +153,10 @@ let Type: string -> string */ - absolutePath = defun [ string string ] - (path: - if builtins.substring 0 1 path == "/" - then "${scriptName}${path}" - else "${scriptName}/${path}"); + absolutePath = path: + if builtins.substring 0 1 path == "/" + then "${scriptName}${path}" + else "${scriptName}/${path}"; bins = getBins pkgs.coreutils [ "env" "tee" "cat" "printf" "chmod" ] // getBins depot.users.sterni.nint [ "nint" ]; diff --git a/web/bubblegum/examples/hello.nix b/web/bubblegum/examples/hello.nix index 881426bd1212..f6706eb2177e 100644 --- a/web/bubblegum/examples/hello.nix +++ b/web/bubblegum/examples/hello.nix @@ -33,6 +33,18 @@ let No coffee, I'm afraid ''; }; + "/type-error" = { + status = 666; + title = "bad usage"; + content = '' + Never gonna see this. + ''; + }; + "/eval-error" = { + status = "OK"; + title = "evaluation error"; + content = builtins.throw "lol"; + }; }; notFound = { -- cgit 1.4.1