Skip to content

Commit

Permalink
Use JuvixError instead of Text for errors in Package file loading (#2459
Browse files Browse the repository at this point in the history
)

Depends on:
*  #2458

This PR is part of a series implementing:
* #2336

In attempt to make the main PR:
* #2434
easier to review.

This PR introduces standard JuvixError handling for errors related to
the loading of the juvix.yaml file. Before this PR errors were thrown as
Text and then communicated to the user using the `error` function.
  • Loading branch information
paulcadman authored Oct 23, 2023
1 parent 7b7f06f commit 8e6c1c8
Show file tree
Hide file tree
Showing 19 changed files with 320 additions and 47 deletions.
2 changes: 1 addition & 1 deletion app/Commands/Repl.hs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ loadDefaultPrelude = whenJustM defaultPreludeEntryPoint $ \e -> do
. runM
. evalInternet hasInternet
. runFilesIO
. runError @Text
. runError @JuvixError
. runReader e
. runLogIO
. runProcessIO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import Juvix.Prelude

mkPackage ::
forall r.
(Members '[Files, Error Text, Reader ResolverEnv, GitClone] r) =>
(Members '[Files, Error JuvixError, Reader ResolverEnv, GitClone] r) =>
Maybe EntryPoint ->
Path Abs Dir ->
Sem r Package
Expand All @@ -43,7 +43,7 @@ mkPackage mpackageEntry _packageRoot = do

mkPackageInfo ::
forall r.
(Members '[Files, Error Text, Reader ResolverEnv, Error DependencyError, GitClone] r) =>
(Members '[Files, Error JuvixError, Reader ResolverEnv, Error DependencyError, GitClone] r) =>
Maybe EntryPoint ->
Path Abs Dir ->
Package ->
Expand Down Expand Up @@ -162,7 +162,7 @@ resolveDependency i = case i ^. packageDepdendencyInfoDependency of

registerDependencies' ::
forall r.
(Members '[Reader EntryPoint, State ResolverState, Reader ResolverEnv, Files, Error Text, Error DependencyError, GitClone] r) =>
(Members '[Reader EntryPoint, State ResolverState, Reader ResolverEnv, Files, Error JuvixError, Error DependencyError, GitClone] r) =>
DependenciesConfig ->
Sem r ()
registerDependencies' conf = do
Expand All @@ -186,7 +186,7 @@ registerDependencies' conf = do

addRootDependency ::
forall r.
(Members '[State ResolverState, Reader ResolverEnv, Files, Error Text, Error DependencyError, GitClone] r) =>
(Members '[State ResolverState, Reader ResolverEnv, Files, Error JuvixError, Error DependencyError, GitClone] r) =>
DependenciesConfig ->
EntryPoint ->
Path Abs Dir ->
Expand All @@ -207,7 +207,7 @@ addRootDependency conf e root = do

addDependency ::
forall r.
(Members '[State ResolverState, Reader ResolverEnv, Files, Error Text, Error DependencyError, GitClone] r) =>
(Members '[State ResolverState, Reader ResolverEnv, Files, Error JuvixError, Error DependencyError, GitClone] r) =>
Maybe EntryPoint ->
PackageDependencyInfo ->
Sem r LockfileDependency
Expand All @@ -224,7 +224,7 @@ addDependency me d = do

addDependency' ::
forall r.
(Members '[State ResolverState, Reader ResolverEnv, Files, Error Text, Error DependencyError, GitClone] r) =>
(Members '[State ResolverState, Reader ResolverEnv, Files, Error JuvixError, Error DependencyError, GitClone] r) =>
Package ->
Maybe EntryPoint ->
ResolvedDependency ->
Expand Down Expand Up @@ -319,7 +319,7 @@ expectedPath' actualPath m = do

re ::
forall r a.
(Members '[Reader EntryPoint, Files, Error Text, Error DependencyError, GitClone] r) =>
(Members '[Reader EntryPoint, Files, Error JuvixError, Error DependencyError, GitClone] r) =>
Sem (PathResolver ': r) a ->
Sem (Reader ResolverEnv ': State ResolverState ': r) a
re = reinterpret2H helper
Expand All @@ -342,13 +342,13 @@ re = reinterpret2H helper
Right (r, _) -> r
raise (evalPathResolver' st' root' (a' x'))

evalPathResolver' :: (Members '[Reader EntryPoint, Files, Error Text, Error DependencyError, GitClone] r) => ResolverState -> Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r a
evalPathResolver' :: (Members '[Reader EntryPoint, Files, Error JuvixError, Error DependencyError, GitClone] r) => ResolverState -> Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r a
evalPathResolver' st root = fmap snd . runPathResolver' st root

runPathResolver :: (Members '[Reader EntryPoint, Files, Error Text, Error DependencyError, GitClone] r) => Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolver :: (Members '[Reader EntryPoint, Files, Error JuvixError, Error DependencyError, GitClone] r) => Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolver = runPathResolver' iniResolverState

runPathResolver' :: (Members '[Reader EntryPoint, Files, Error Text, Error DependencyError, GitClone] r) => ResolverState -> Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolver' :: (Members '[Reader EntryPoint, Files, Error JuvixError, Error DependencyError, GitClone] r) => ResolverState -> Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolver' st root x = do
e <- ask
let _envSingleFile :: Maybe (Path Abs File)
Expand All @@ -364,15 +364,15 @@ runPathResolver' st root x = do
}
runState st (runReader env (re x))

runPathResolverPipe' :: (Members '[Files, Reader EntryPoint, Error DependencyError, GitClone] r) => ResolverState -> Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolverPipe' :: (Members '[Files, Reader EntryPoint, Error DependencyError, GitClone, Error JuvixError] r) => ResolverState -> Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolverPipe' iniState a = do
r <- asks (^. entryPointResolverRoot)
runError (runPathResolver' iniState r (raiseUnder a)) >>= either error return
runPathResolver' iniState r a

runPathResolverPipe :: (Members '[Files, Reader EntryPoint, Error DependencyError, GitClone] r) => Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolverPipe :: (Members '[Files, Reader EntryPoint, Error DependencyError, GitClone, Error JuvixError] r) => Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolverPipe a = do
r <- asks (^. entryPointResolverRoot)
runError (runPathResolver r (raiseUnder a)) >>= either error return
runPathResolver r a

evalPathResolverPipe :: (Members '[Files, Reader EntryPoint, Error DependencyError, GitClone] r) => Sem (PathResolver ': r) a -> Sem r a
evalPathResolverPipe :: (Members '[Files, Reader EntryPoint, Error DependencyError, GitClone, Error JuvixError] r) => Sem (PathResolver ': r) a -> Sem r a
evalPathResolverPipe = fmap snd . runPathResolverPipe
2 changes: 1 addition & 1 deletion src/Juvix/Compiler/Pipeline/Artifacts/PathResolver.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import Juvix.Compiler.Pipeline.EntryPoint
import Juvix.Data.Effect.Git
import Juvix.Prelude

runPathResolverArtifacts :: (Members '[Files, Reader EntryPoint, State Artifacts, Error DependencyError, GitClone] r) => Sem (PathResolver ': r) a -> Sem r a
runPathResolverArtifacts :: (Members '[Files, Reader EntryPoint, State Artifacts, Error DependencyError, GitClone, Error JuvixError] r) => Sem (PathResolver ': r) a -> Sem r a
runPathResolverArtifacts = runStateLikeArtifacts runPathResolverPipe' artifactResolver
19 changes: 16 additions & 3 deletions src/Juvix/Compiler/Pipeline/Lockfile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Data.String.Interpolate (i)
import Data.Yaml
import Data.Yaml.Pretty
import Juvix.Compiler.Pipeline.Package.Dependency
import Juvix.Compiler.Pipeline.Package.Loader.Error
import Juvix.Extra.Paths
import Juvix.Extra.Strings qualified as Str
import Juvix.Extra.Version
Expand Down Expand Up @@ -84,21 +85,33 @@ mkPackageLockfilePath = (<//> juvixLockfile)

mayReadLockfile ::
forall r.
(Members '[Files, Error Text] r) =>
(Members '[Files, Error PackageLoaderError] r) =>
Path Abs Dir ->
Sem r (Maybe LockfileInfo)
mayReadLockfile root = do
let lockfilePath = mkPackageLockfilePath root
lockfileExists <- fileExists' lockfilePath
if
| lockfileExists -> do
bs <- readFileBS' lockfilePath
either (throw . pack . prettyPrintParseException) ((return . Just) . mkLockfileInfo lockfilePath) (decodeEither' @Lockfile bs)
either (throwErr . pack . prettyPrintParseException) ((return . Just) . mkLockfileInfo lockfilePath) (decodeEither' @Lockfile bs)
| otherwise -> return Nothing
where
mkLockfileInfo :: Path Abs File -> Lockfile -> LockfileInfo
mkLockfileInfo _lockfileInfoPath _lockfileInfoLockfile = LockfileInfo {..}

lockfilePath :: Path Abs File
lockfilePath = mkPackageLockfilePath root

throwErr :: Text -> Sem r a
throwErr e =
throw
PackageLoaderError
{ _packageLoaderErrorPath = lockfilePath,
_packageLoaderErrorCause =
ErrLockfileYamlParseError
LockfileYamlParseError {_lockfileYamlParseErrorError = e}
}

lockfileEncodeConfig :: Config
lockfileEncodeConfig = setConfCompare keyCompare defConfig
where
Expand Down
63 changes: 37 additions & 26 deletions src/Juvix/Compiler/Pipeline/Package.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import Data.Versions
import Data.Yaml
import Juvix.Compiler.Pipeline.Lockfile
import Juvix.Compiler.Pipeline.Package.Base
import Juvix.Compiler.Pipeline.Package.Loader.Error
import Juvix.Extra.Paths
import Juvix.Prelude

processPackage :: forall r. (Members '[Error Text] r) => Path Abs File -> BuildDir -> Maybe LockfileInfo -> RawPackage -> Sem r Package
processPackage :: forall r. (Members '[Error PackageLoaderError] r) => Path Abs File -> BuildDir -> Maybe LockfileInfo -> RawPackage -> Sem r Package
processPackage _packageFile buildDir lockfile pkg = do
let _packageName = fromMaybe defaultPackageName (pkg ^. packageName)
_packageDependencies = resolveDependencies
Expand All @@ -35,23 +36,32 @@ processPackage _packageFile buildDir lockfile pkg = do
Nothing -> return defaultVersion
Just ver -> case semver ver of
Right v -> return v
Left err -> throw (pack (errorBundlePretty err))
Left err ->
throw
PackageLoaderError
{ _packageLoaderErrorCause =
ErrVersionParseError
VersionParseError {_versionParseErrorError = (pack (errorBundlePretty err))},
_packageLoaderErrorPath = _packageFile
}

checkNoDuplicateDepNames :: [Dependency] -> Sem r ()
checkNoDuplicateDepNames deps = go HashSet.empty (deps ^.. traversed . _GitDependency . gitDependencyName)
where
go :: HashSet Text -> [Text] -> Sem r ()
go _ [] = return ()
go s (x : xs)
| x `HashSet.member` s = throw (errMsg x)
| x `HashSet.member` s =
throw
PackageLoaderError
{ _packageLoaderErrorPath = _packageFile,
_packageLoaderErrorCause =
ErrDuplicateDependencyError
DuplicateDependencyError
{ _duplicateDependencyErrorName = x
}
}
| otherwise = go (HashSet.insert x s) xs
where
errMsg :: Text -> Text
errMsg dupName =
"Juvix package file at: "
<> pack (toFilePath _packageFile)
<> " contains the duplicate dependency name: "
<> dupName

resolveDependencies :: [Dependency]
resolveDependencies = fromMaybe [stdlib] (pkg ^. packageDependencies)
Expand All @@ -62,36 +72,37 @@ processPackage _packageFile buildDir lockfile pkg = do
-- | Given some directory d it tries to read the file d/juvix.yaml and parse its contents
readPackage ::
forall r.
(Members '[Files, Error Text] r) =>
(Members '[Files, Error JuvixError] r) =>
Path Abs Dir ->
BuildDir ->
Sem r Package
readPackage root buildDir = do
readPackage root buildDir = mapError (JuvixError @PackageLoaderError) $ do
bs <- readFileBS' yamlPath
mLockfile <- mayReadLockfile root
if
| ByteString.null bs -> return (emptyPackage buildDir yamlPath)
| otherwise -> either (throw . pack . prettyPrintParseException) (processPackage yamlPath buildDir mLockfile) (decodeEither' bs)
| otherwise -> either (throwErr . pack . prettyPrintParseException) (processPackage yamlPath buildDir mLockfile) (decodeEither' bs)
where
yamlPath = mkPackageFilePath root

throwErr e =
throw
PackageLoaderError
{ _packageLoaderErrorPath = yamlPath,
_packageLoaderErrorCause =
ErrPackageYamlParseError
PackageYamlParseError
{ _packageYamlParseErrorError = e
}
}

readPackageIO :: Path Abs Dir -> BuildDir -> IO Package
readPackageIO root buildDir = do
let x :: Sem '[Error Text, Files, Embed IO] Package
x = readPackage root buildDir
m <- runM $ runFilesIO (runError x)
case m of
Left err -> putStrLn err >> exitFailure
Right r -> return r
readPackageIO root buildDir = runM (runFilesIO (runErrorIO' @JuvixError (readPackage root buildDir)))

readGlobalPackageIO :: IO Package
readGlobalPackageIO = do
m <- runM . runFilesIO . runError $ readGlobalPackage
case m of
Left err -> putStrLn err >> exitFailure
Right r -> return r
readGlobalPackageIO = runM (runFilesIO . runErrorIO' @JuvixError $ readGlobalPackage)

readGlobalPackage :: (Members '[Error Text, Files] r) => Sem r Package
readGlobalPackage :: (Members '[Error JuvixError, Files] r) => Sem r Package
readGlobalPackage = do
yamlPath <- globalYaml
unlessM (fileExists' yamlPath) writeGlobalPackage
Expand Down
85 changes: 85 additions & 0 deletions src/Juvix/Compiler/Pipeline/Package/Loader/Error.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module Juvix.Compiler.Pipeline.Package.Loader.Error where

import Juvix.Data.CodeAnn
import Juvix.Prelude

data PackageLoaderError = PackageLoaderError
{ _packageLoaderErrorPath :: Path Abs File,
_packageLoaderErrorCause :: PackageLoaderErrorCause
}

data PackageLoaderErrorCause
= ErrPackageYamlParseError PackageYamlParseError
| ErrLockfileYamlParseError LockfileYamlParseError
| ErrVersionParseError VersionParseError
| ErrDuplicateDependencyError DuplicateDependencyError

newtype PackageYamlParseError = PackageYamlParseError
{ _packageYamlParseErrorError :: Text
}

newtype LockfileYamlParseError = LockfileYamlParseError
{ _lockfileYamlParseErrorError :: Text
}

newtype VersionParseError = VersionParseError
{ _versionParseErrorError :: Text
}

newtype DuplicateDependencyError = DuplicateDependencyError
{ _duplicateDependencyErrorName :: Text
}

makeLenses ''PackageLoaderError
makeLenses ''PackageYamlParseError
makeLenses ''LockfileYamlParseError
makeLenses ''VersionParseError
makeLenses ''DuplicateDependencyError

instance ToGenericError PackageLoaderError where
genericError e = do
let msg = mkAnsiText (ppCodeAnn e)
return
GenericError
{ _genericErrorMessage = msg,
_genericErrorLoc = i,
_genericErrorIntervals = [i]
}
where
i = getLoc e

instance PrettyCodeAnn PackageLoaderError where
ppCodeAnn e = ppCodeAnn (e ^. packageLoaderErrorCause)

instance PrettyCodeAnn PackageLoaderErrorCause where
ppCodeAnn = \case
ErrPackageYamlParseError e -> ppCodeAnn e
ErrLockfileYamlParseError e -> ppCodeAnn e
ErrVersionParseError e -> ppCodeAnn e
ErrDuplicateDependencyError e -> ppCodeAnn e

instance PrettyCodeAnn PackageYamlParseError where
ppCodeAnn e =
"The package file is invalid"
<> line
<+> pretty (e ^. packageYamlParseErrorError)

instance PrettyCodeAnn LockfileYamlParseError where
ppCodeAnn e =
"The lock file is invalid"
<> line
<+> pretty (e ^. lockfileYamlParseErrorError)

instance PrettyCodeAnn VersionParseError where
ppCodeAnn e =
"The package version is invalid"
<> line
<+> pretty (e ^. versionParseErrorError)

instance PrettyCodeAnn DuplicateDependencyError where
ppCodeAnn e =
"Juvix package file contains the duplicate dependency name:"
<+> pretty (e ^. duplicateDependencyErrorName)

instance HasLoc PackageLoaderError where
getLoc e = singletonInterval (mkInitialLoc (e ^. packageLoaderErrorPath))
4 changes: 3 additions & 1 deletion test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Examples qualified
import Format qualified
import Formatter qualified
import Internal qualified
import Package qualified
import Parsing qualified
import Reachability qualified
import Runtime qualified
Expand Down Expand Up @@ -43,7 +44,8 @@ fastTests =
Typecheck.allTests,
Reachability.allTests,
Format.allTests,
Formatter.allTests
Formatter.allTests,
Package.allTests
]

main :: IO ()
Expand Down
11 changes: 11 additions & 0 deletions test/Package.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Package
( allTests,
)
where

import Base
import Package.Negative qualified as N
import Package.Positive qualified as P

allTests :: TestTree
allTests = testGroup "Package loading tests" [N.allTests, P.allTests]
Loading

0 comments on commit 8e6c1c8

Please sign in to comment.