hurl-workbench-core-0.1.0.0: src/HurlWorkbench/Run/Prepare.hs
-- | Convert pure selections into fully rendered, syntax-checked, bound runs
-- before any Hurl process starts.
module HurlWorkbench.Run.Prepare
( PreparedRun (..),
PreparationError (..),
prepareSelection,
prepareSelectionForSuite,
prepareSelectionForSuiteWith,
prepareSelectionWith,
prepareSelectionWithGenerator,
prepareExpandedForSuiteWith,
prepareSelectionAtFileRootWith,
prepareSelectionForSuiteAtFileRootWith,
prepareExpandedForSuiteAtFileRootWith,
buildBatchCase,
renderPreparationError,
)
where
import Control.Monad (foldM)
import Data.Generics.Labels ()
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import HurlWorkbench.Hurl.Format
import HurlWorkbench.Hurl.Run
import HurlWorkbench.Parameter.Generate (GeneratorEnvironment)
import HurlWorkbench.Parameter.Resolve
import HurlWorkbench.Prelude
import HurlWorkbench.Run.Batch
import HurlWorkbench.Run.FileRoot
import HurlWorkbench.Run.Selection
import HurlWorkbench.Workflow.Render
import HurlWorkbench.Workspace.Context (ValidatedWorkspace, validatedRoot)
import HurlWorkbench.Workspace.Types (Workflow, WorkspaceRoot (..))
data PreparedRun = PreparedRun
{ displayName :: !Text,
artifactStem :: !FilePath,
safety :: !SafetyDisposition,
renderedWorkflow :: !RenderedWorkflow,
bindings :: !ResolvedBindings,
fileRoot :: !FileRoot,
options :: !HurlOptions
}
deriving stock (Generic, Eq, Show)
data PreparationError
= RunFileRootFailed !FileRootError
| SelectionPreparationFailed !SelectionError
| RunRenderFailed !Text !RenderError
| RunFormatFailed !Text !HurlfmtError
| RunBindingFailed !Text !BindingError
deriving stock (Generic, Eq, Show)
data CachedWorkflow
= CachedRenderFailure !RenderError
| CachedFormatFailure !HurlfmtError
| CachedRendered !RenderedWorkflow
prepareSelection :: HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelection = prepareSelectionWith validateRenderedWorkflow
-- | Prepare one selection from suite-wide runtime input. Bindings intended
-- for other selections in the same suite are ignored.
prepareSelectionForSuite :: HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelectionForSuite = prepareSelectionUsing resolveWorkflowBindingSubsetWithLayers validateRenderedWorkflow
prepareSelectionForSuiteWith :: (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelectionForSuiteWith = prepareSelectionUsing resolveWorkflowBindingSubsetWithLayers
prepareSelectionWith :: (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelectionWith = prepareSelectionUsing resolveWorkflowBindingsWithLayers
-- | Deterministic preflight with an injected clock and entropy provider.
prepareSelectionWithGenerator :: GeneratorEnvironment -> (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelectionWithGenerator env = prepareSelectionUsing (\validated workflow -> resolveParametersWithLayersAndGenerator env validated (Set.fromList (workflow ^. #parameters)))
prepareSelectionUsing :: (ValidatedWorkspace -> Workflow -> [BindingLayer] -> BindingInput -> IO (Either BindingError ResolvedBindings)) -> (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelectionUsing resolveBindings validateRendered capabilities validated input hurlOptions selection =
case resolveSelection validated selection of
Left err -> pure (Left (SelectionPreparationFailed err :| []))
Right expanded -> withDefaultRoot validated $ \root -> prepareExpandedUsing resolveBindings root validateRendered capabilities validated hurlOptions (fmap (,input) expanded)
prepareExpandedForSuiteWith :: (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> HurlOptions -> NonEmpty (ExpandedRun, BindingInput) -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareExpandedForSuiteWith validateRendered capabilities validated options runs =
withDefaultRoot validated $ \root -> prepareExpandedForSuiteAtFileRootWith root validateRendered capabilities validated options runs
withDefaultRoot :: ValidatedWorkspace -> (FileRoot -> IO (Either (NonEmpty PreparationError) a)) -> IO (Either (NonEmpty PreparationError) a)
withDefaultRoot validated action = validateFileRoot (unWorkspaceRoot (validatedRoot validated)) Nothing >>= either (pure . Left . (:| []) . RunFileRootFailed) action
prepareSelectionAtFileRootWith :: FileRoot -> (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelectionAtFileRootWith = prepareSelectionAtFileRootUsing resolveWorkflowBindingsWithLayers
prepareSelectionForSuiteAtFileRootWith :: FileRoot -> (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelectionForSuiteAtFileRootWith = prepareSelectionAtFileRootUsing resolveWorkflowBindingSubsetWithLayers
prepareSelectionAtFileRootUsing :: (ValidatedWorkspace -> Workflow -> [BindingLayer] -> BindingInput -> IO (Either BindingError ResolvedBindings)) -> FileRoot -> (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> BindingInput -> HurlOptions -> RunSelection -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareSelectionAtFileRootUsing resolveBindings root validateRendered capabilities validated input options selection =
case resolveSelection validated selection of
Left err -> pure (Left (SelectionPreparationFailed err :| []))
Right expanded -> prepareExpandedUsing resolveBindings root validateRendered capabilities validated options (fmap (,input) expanded)
prepareExpandedForSuiteAtFileRootWith :: FileRoot -> (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> HurlOptions -> NonEmpty (ExpandedRun, BindingInput) -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareExpandedForSuiteAtFileRootWith = prepareExpandedUsing resolveWorkflowBindingSubsetWithLayers
prepareExpandedUsing :: (ValidatedWorkspace -> Workflow -> [BindingLayer] -> BindingInput -> IO (Either BindingError ResolvedBindings)) -> FileRoot -> (HurlfmtCapabilities -> RenderedWorkflow -> IO (Either HurlfmtError ())) -> HurlfmtCapabilities -> ValidatedWorkspace -> HurlOptions -> NonEmpty (ExpandedRun, BindingInput) -> IO (Either (NonEmpty PreparationError) (NonEmpty PreparedRun))
prepareExpandedUsing resolveBindings root validateRendered capabilities validated hurlOptions assignedRuns = do
(_cache, errors, prepared) <- foldM prepareOne (Map.empty, [], []) (NonEmpty.toList assignedRuns)
pure $ case (nonEmpty (reverse errors), nonEmpty (reverse prepared)) of
(Just problems, _) -> Left problems
(Nothing, Just ready) -> Right ready
(Nothing, Nothing) -> error "prepareExpandedUsing: impossible empty expanded selection"
where
prepareOne (cache, errors, prepared) (expanded, input) = do
(nextCache, cached) <- cachedWorkflow cache expanded
case cached of
CachedRenderFailure err -> pure (nextCache, RunRenderFailed (expanded ^. #displayName) err : errors, prepared)
CachedFormatFailure err -> pure (nextCache, RunFormatFailed (expanded ^. #displayName) err : errors, prepared)
CachedRendered rendered -> do
resolved <-
resolveBindings
validated
(expanded ^. #workflow . #workflow)
(expanded ^. #bindingLayers)
input
pure $ case resolved of
Left err -> (nextCache, RunBindingFailed (expanded ^. #displayName) err : errors, prepared)
Right bindings ->
( nextCache,
errors,
PreparedRun
{ displayName = expanded ^. #displayName,
artifactStem = expanded ^. #artifactStem,
safety = expanded ^. #safety,
renderedWorkflow = rendered,
bindings,
fileRoot = root,
options = hurlOptions
}
: prepared
)
cachedWorkflow cache expanded =
let workflowName = expanded ^. #workflow . #workflow . #name
in case Map.lookup workflowName cache of
Just cached -> pure (cache, cached)
Nothing -> do
cached <- renderAndValidate expanded
pure (Map.insert workflowName cached cache, cached)
renderAndValidate expanded =
renderWorkflow (expanded ^. #workflow) >>= \case
Left err -> pure (CachedRenderFailure err)
Right rendered ->
validateRendered capabilities rendered <&> \case
Left err -> CachedFormatFailure err
Right () -> CachedRendered rendered
buildBatchCase :: HurlRunMode -> RunOutputPolicy -> [HurlReportTarget] -> PreparedRun -> BatchCase
buildBatchCase runMode output reports prepared =
BatchCase
{ name = prepared ^. #displayName,
artifactStem = prepared ^. #artifactStem,
request =
RunRequest
{ renderedWorkflow = prepared ^. #renderedWorkflow,
fileRoot = prepared ^. #fileRoot,
mode = runMode,
bindings = prepared ^. #bindings,
options = prepared ^. #options,
outputPolicy = output,
reportTargets = reports
}
}
renderPreparationError :: PreparationError -> Text
renderPreparationError = \case
RunFileRootFailed err -> renderFileRootError err
SelectionPreparationFailed err -> renderSelectionError err
RunRenderFailed name err -> prefix name <> renderRenderError err
RunFormatFailed name err -> prefix name <> renderHurlfmtError err
RunBindingFailed name err -> prefix name <> renderBindingError err
where
prefix name = "run \"" <> name <> "\": "