From a2a558992a6ef0686ff8c545e6050102a7abb1af Mon Sep 17 00:00:00 2001 From: Dillon Kearns Date: Mon, 2 Jan 2023 11:02:22 -0800 Subject: [PATCH] Handle port-data-source errors in BackendTasks. --- generator/src/request-cache.js | 58 +++++++++++++--- src/BackendTask/Port.elm | 119 ++++++++++++++++++++++++++++----- 2 files changed, 152 insertions(+), 25 deletions(-) diff --git a/generator/src/request-cache.js b/generator/src/request-cache.js index 919585b1..cf5e106e 100644 --- a/generator/src/request-cache.js +++ b/generator/src/request-cache.js @@ -75,20 +75,60 @@ function lookupOrPerform(portsFile, mode, rawRequest, hasFsAccess, useCache) { if (!portBackendTask[portName]) { if (portBackendTaskImportError === null) { - throw `BackendTask.Port.send "${portName}" was called, but I couldn't find a function with that name in the port definitions file. Is it exported correctly?`; + resolve({ + kind: "response-json", + value: jsonResponse({ + "elm-pages-internal-error": "PortNotDefined", + }), + }); } else if (portBackendTaskImportError === "missing") { - throw `BackendTask.Port.send "${portName}" was called, but I couldn't find the port definitions file. Be sure to create a 'port-data-source.ts' or 'port-data-source.js' file and maybe restart the dev server.`; + resolve({ + kind: "response-json", + value: jsonResponse({ + "elm-pages-internal-error": "MissingPortsFile", + }), + }); } else { - throw `BackendTask.Port.send "${portName}" was called, but I couldn't import the port definitions file, because of this exception: \`${portBackendTaskImportError}\` Are there syntax errors or expections thrown during import?`; + resolve({ + kind: "response-json", + value: jsonResponse({ + "elm-pages-internal-error": "ErrorInPortsFile", + error: + (portBackendTaskImportError && + portBackendTaskImportError.stack) || + "", + }), + }); } } else if (typeof portBackendTask[portName] !== "function") { - throw `BackendTask.Port.send "${portName}" was called, but it is not a function. Be sure to export a function with that name from port-data-source.js`; + resolve({ + kind: "response-json", + value: jsonResponse({ + "elm-pages-internal-error": "ExportIsNotFunction", + error: typeof portBackendTask[portName], + }), + }); + } else { + try { + const portFunctionResult = await portBackendTask[portName](input); + await fs.promises.writeFile( + responsePath, + JSON.stringify(jsonResponse(portFunctionResult)) + ); + resolve({ + kind: "cache-response-path", + value: responsePath, + }); + } catch (portCallError) { + resolve({ + kind: "response-json", + value: jsonResponse({ + "elm-pages-internal-error": "PortCallError", + error: portCallError, + }), + }); + } } - await fs.promises.writeFile( - responsePath, - JSON.stringify(jsonResponse(await portBackendTask[portName](input))) - ); - resolve({ kind: "cache-response-path", value: responsePath }); } catch (error) { console.trace(error); reject({ diff --git a/src/BackendTask/Port.elm b/src/BackendTask/Port.elm index 8b830978..073c50c9 100644 --- a/src/BackendTask/Port.elm +++ b/src/BackendTask/Port.elm @@ -15,7 +15,7 @@ import BackendTask import BackendTask.Http import BackendTask.Internal.Request import Exception exposing (Catchable) -import Json.Decode exposing (Decoder) +import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode import TerminalText @@ -92,24 +92,111 @@ get portName input decoder = ] |> BackendTask.Http.jsonBody , expect = - decoder + Decode.oneOf + [ Decode.field "elm-pages-internal-error" Decode.string + |> Decode.andThen + (\errorKind -> + if errorKind == "PortNotDefined" then + Exception.Catchable (PortNotDefined { name = portName }) + { title = "Port Error" + , body = + [ TerminalText.text "Something went wrong in a call to BackendTask.Port.get. I expected to find a port named `" + , TerminalText.yellow portName + , TerminalText.text "` but I couldn't find it. Is the function exported in your port-data-source file?" + ] + |> TerminalText.toString + } + |> Decode.succeed + + else if errorKind == "ExportIsNotFunction" then + Decode.field "error" Decode.string + |> Decode.maybe + |> Decode.map (Maybe.withDefault "") + |> Decode.map + (\incorrectPortType -> + Exception.Catchable ExportIsNotFunction + { title = "Port Error" + , body = + [ TerminalText.text "Something went wrong in a call to BackendTask.Port.get. I found an export called `" + , TerminalText.yellow portName + , TerminalText.text "` but I expected its type to be function, but instead its type was: " + , TerminalText.red incorrectPortType + ] + |> TerminalText.toString + } + ) + + else if errorKind == "MissingPortsFile" then + Exception.Catchable MissingPortsFile + { title = "Port Error" + , body = + [ TerminalText.text "Something went wrong in a call to BackendTask.Port.get. I couldn't find your port-data-source file. Be sure to create a 'port-data-source.ts' or 'port-data-source.js' file." + ] + |> TerminalText.toString + } + |> Decode.succeed + + else if errorKind == "ErrorInPortsFile" then + Decode.field "error" Decode.string + |> Decode.maybe + |> Decode.map (Maybe.withDefault "") + |> Decode.map + (\errorMessage -> + Exception.Catchable + ErrorInPortsFile + { title = "Port Error" + , body = + [ TerminalText.text "Something went wrong in a call to BackendTask.Port.get. I couldn't import the port definitions file, because of this exception:\n\n" + , TerminalText.red errorMessage + , TerminalText.text "\n\nAre there syntax errors or exceptions thrown during import?" + ] + |> TerminalText.toString + } + ) + + else if errorKind == "PortCallError" then + Decode.field "error" Decode.value + |> Decode.maybe + |> Decode.map (Maybe.withDefault Encode.null) + |> Decode.map + (\portCallError -> + Exception.Catchable + (PortCallError portCallError) + { title = "Port Error" + , body = + [ TerminalText.text "Something went wrong in a call to BackendTask.Port.get. I couldn't import the port definitions file, because of this exception:\n\n" + , TerminalText.red (Encode.encode 2 portCallError) + , TerminalText.text "\n\nAre there syntax errors or exceptions thrown during import?" + ] + |> TerminalText.toString + } + ) + + else + Exception.Catchable ErrorInPortsFile + { title = "Port Error" + , body = + [ TerminalText.text "Something went wrong in a call to BackendTask.Port.get. I expected to find a port named `" + , TerminalText.yellow portName + , TerminalText.text "`." + ] + |> TerminalText.toString + } + |> Decode.succeed + ) + |> Decode.map Err + , decoder |> Decode.map Ok + ] |> BackendTask.Http.expectJson } - |> BackendTask.onError - (\_ -> - BackendTask.fail - (Exception.Catchable Error - { title = "Port Error" - , body = - [ TerminalText.text "Something went wrong in a call to BackendTask.Port.get." - ] - |> TerminalText.toString - } - ) - ) + |> BackendTask.andThen BackendTask.fromResult {-| -} type Error - = -- TODO include additional context about error or better name to reflect the error state - Error + = Error + | ErrorInPortsFile + | MissingPortsFile + | PortNotDefined { name : String } + | PortCallError Decode.Value + | ExportIsNotFunction