packages feed

nbparts-0.1.0.0: src/Nbparts/Types/Error.hs

module Nbparts.Types.Error where

import Commonmark qualified
import Control.Exception qualified as Exception
import Data.Ord qualified as Ord
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Version qualified as Data
import Data.Version qualified as Version
import Data.Yaml qualified as Yaml
import Nbparts.Types.Manifest qualified as Manifest
import Text.Megaparsec qualified as Megaparsec
import Text.Parsec (errorPos)

recommendedNotebookFormat :: (Int, Int)
recommendedNotebookFormat = (4, 5)

data NbpartsError = UnpackError UnpackError | PackError PackError
  deriving (Show, Eq)

data UnpackError
  = UnpackParseNotebookError Text
  | UnpackParseMarkdownError Commonmark.ParseError
  | UnpackUnsupportedNotebookFormat (Int, Int)
  | UnpackMissingCellIdError
  deriving (Show, Eq)

data PackError
  = PackUnsupportedNotebookFormat (Int, Int)
  | PackParseManifestError ParseYamlError
  | PackManifestUnknownVersionError Data.Version
  | PackIllegalFormatError IllegalFormatContext Manifest.Format
  | PackParseYamlSourcesError ParseYamlError
  | PackParseJsonSourcesError Text
  | PackParseMarkdownSourcesError (Megaparsec.ParseErrorBundle Text ParseMarkdownSourcesError)
  | PackParseYamlMetadataError ParseYamlError
  | PackParseJsonMetadataError Text
  | PackParseYamlOutputsError ParseYamlError
  | PackParseJsonOutputsError Text
  | PackMissingCellIdError
  | PackCellMetadataTypeMismatch {expected :: CellMetadataTag, actual :: CellMetadataTag}
  deriving (Show, Eq)

-- Wrapper so that we can make an `Eq` instance.
newtype ParseYamlError = ParseYamlError Yaml.ParseException
  deriving (Show)

-- The author of `yaml` performs equality checks with the `Show` instance as well,
-- so this should be fine. https://github.com/snoyberg/yaml/issues/189
instance Eq ParseYamlError where
  a == b = show a == show b

data IllegalFormatContext = IllegalFormatSources | IllegalFormatMetadata | IllegalFormatOutputs
  deriving (Show, Eq, Ord)

data ParseMarkdownSourcesError
  = ParseMarkdownSourcesJsonError Text
  | ParseMarkdownSourcesMarkdownError Commonmark.ParseError
  deriving (Show, Eq)

instance Ord ParseMarkdownSourcesError where
  compare (ParseMarkdownSourcesMarkdownError mdErr1) (ParseMarkdownSourcesMarkdownError mdErr2) =
    compare (errorPos mdErr1) (errorPos mdErr2)
  compare (ParseMarkdownSourcesJsonError t1) (ParseMarkdownSourcesJsonError t2) = compare t1 t2
  compare _ _ = Ord.EQ

data CellMetadataTag = CodeCellMetadataTag | GenericCellMetadataTag
  deriving (Show, Eq, Ord)

instance Megaparsec.ShowErrorComponent ParseMarkdownSourcesError where
  showErrorComponent (ParseMarkdownSourcesJsonError msg) =
    "Invalid JSON in nbparts cell marker: " <> Text.unpack msg
  showErrorComponent (ParseMarkdownSourcesMarkdownError mdErr) =
    "Failed to parse markdown: " <> show mdErr

renderError :: NbpartsError -> Text
renderError err = case err of
  UnpackError (UnpackParseNotebookError message) -> "Failed to parse notebook: " <> message
  UnpackError (UnpackParseMarkdownError mdErr) -> "Failed to parse markdown: " <> Text.pack (show mdErr)
  UnpackError (UnpackUnsupportedNotebookFormat (major, minor)) ->
    "Unsupported notebook format: "
      <> Text.pack (show major)
      <> "."
      <> Text.pack (show minor)
  UnpackError UnpackMissingCellIdError ->
    "Notebook contains cell(s) without an identifier. Try upgrading your notebook to at least version "
      <> Text.pack (show $ fst recommendedNotebookFormat)
      <> "."
      <> Text.pack (show $ snd recommendedNotebookFormat)
      <> "."
  PackError (PackUnsupportedNotebookFormat (major, minor)) ->
    "Unsupported notebook format: "
      <> Text.pack (show major)
      <> "."
      <> Text.pack (show minor)
  PackError (PackParseManifestError (ParseYamlError ex)) -> "Failed to parse manifest: " <> Text.pack (Exception.displayException ex)
  PackError (PackManifestUnknownVersionError version) -> "Unknown manifest version: " <> Text.pack (Version.showVersion version)
  PackError (PackIllegalFormatError ctx fmt) -> "Illegal format for " <> renderIllegalFormatContext ctx <> ":" <> renderFormat fmt
  PackError (PackParseYamlSourcesError (ParseYamlError ex)) -> "Failed to parse sources: " <> Text.pack (Exception.displayException ex)
  PackError (PackParseJsonSourcesError parseErr) -> "Failed to parse sources: " <> parseErr
  PackError (PackParseMarkdownSourcesError errBundle) -> Text.pack $ Megaparsec.errorBundlePretty errBundle
  PackError (PackParseYamlMetadataError (ParseYamlError ex)) -> "Failed to parse metadata: " <> Text.pack (Exception.displayException ex)
  PackError (PackParseJsonMetadataError parseErr) -> "Failed to parse metadata: " <> parseErr
  PackError (PackParseYamlOutputsError (ParseYamlError ex)) -> "Failed to parse outputs: " <> Text.pack (Exception.displayException ex)
  PackError (PackParseJsonOutputsError parseErr) -> "Failed to parse outputs: " <> parseErr
  PackError PackMissingCellIdError -> "Markdown content contains missing cell ID"
  PackError (PackCellMetadataTypeMismatch expected actual) ->
    "Cell metadata type mismatch. Expected: "
      <> renderCellMetadataTag expected
      <> ", but got: "
      <> renderCellMetadataTag actual

renderFormat :: Manifest.Format -> Text
renderFormat Manifest.FormatYaml = "yaml"
renderFormat Manifest.FormatJson = "json"
renderFormat Manifest.FormatMarkdown = "markdown"

renderIllegalFormatContext :: IllegalFormatContext -> Text
renderIllegalFormatContext IllegalFormatSources = "sources"
renderIllegalFormatContext IllegalFormatMetadata = "metadata"
renderIllegalFormatContext IllegalFormatOutputs = "outputs"

renderCellMetadataTag :: CellMetadataTag -> Text
renderCellMetadataTag CodeCellMetadataTag = "code cell metadata"
renderCellMetadataTag GenericCellMetadataTag = "generic cell metadata"