Skip to content

Commit

Permalink
Use JuvixError instead of Text for errors in Package file loading
Browse files Browse the repository at this point in the history
  • Loading branch information
paulcadman committed Oct 23, 2023
1 parent 37de293 commit 559e0f2
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 46 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))
2 changes: 2 additions & 0 deletions tests/smoke/Commands/compile-dependencies.smoke.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,8 @@ tests:
# compile project
juvix compile HelloWorld.juvix
stdout:
contains: ""
stderr:
contains: duplicate
exit-status: 1

Expand Down

0 comments on commit 559e0f2

Please sign in to comment.