about summary refs log tree commit diff
path: root/web/bubblegum
diff options
context:
space:
mode:
authorsterni <sternenseemann@systemli.org>2021-04-04T23·53+0200
committersterni <sternenseemann@systemli.org>2021-04-05T10·54+0000
commit1c0f89f4cadba4957e95fc80b25b1b8b6dd1a3b3 (patch)
tree3c0e209f13a3d8b3fb19c9bacde5b617458ae2d4 /web/bubblegum
parentcbd6f5bbae95c26b1e67993ac609e83fa4b117fb (diff)
feat(web/bubblegum): report some errors to the user via HTTP r/2438
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 <sternenseemann@systemli.org>
Diffstat (limited to 'web/bubblegum')
-rw-r--r--web/bubblegum/default.nix73
-rw-r--r--web/bubblegum/examples/hello.nix12
2 files changed, 51 insertions, 34 deletions
diff --git a/web/bubblegum/default.nix b/web/bubblegum/default.nix
index 81e41cfbdf..393ac75d48 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 881426bd12..f6706eb217 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 = {