Link Search Menu Expand Document

REPL

napkin repl starts GHCI session that is preconfigured for Spec development. It can be used to run Spec and Hook programs interactively for easier debugging. In addition to configuring GHCI session with appropriate language extensions, search path and packages, Napkin will define helper macros that make running custom spec and hook programs easier.

runSpecProgram

:runSpecProgram macro can be used to run any SpecProgram' backend returnValue interactively. Note that programs used to create tables have a type of SpecProgram backend which forces return type to be (). However, for interactive runs, one may return any value. Returned value will be bound to result This macro accepts the target table name and the program as arguments.

napkin-repl

import SomeModule
:runSpecProgram "target.table.name" SomeModule.myProgram

Program can be inlined as well (make sure to use quotes or $ operator)

napkin-repl

:runSpecProgram "target.table.name" (logInfo "I am a spec program" >> pure "some return value")

or

napkin-repl

:runSpecProgram "target.table.name" $ logInfo "I am a spec program" >> pure "some return value"
napkin-repl> :runSpecProgram "target.table.name" $ logInfo "I am a spec program" >> pure "some return value"
[2022-01-13 11:46:31][Info] I am a spec program
Right "some return value"
napkin-repl> :type result
result
  :: Either
       Napkin.Run.Effects.Languages.NapkinError.NapkinEffectError [Char]
napkin-repl> result
Right "some return value"

runHookProgram

:runHookProgram macro allows to run arbitrary HookProgram' backend returnValue. Similarly to runSpecProgram, REPL helper allows to return value.

napkin-repl

:runHookProgram (logInfo "I am a hook program" >> pure 0x1234)
napkin-repl> :runHookProgram (logInfo "I am a hook program" >> pure 0x1234)
[2022-01-13 11:52:14][Info] I am a hook program
Right (Right ([],4660))
napkin-repl> :type result
result
  :: Either
       Napkin.Run.Effects.Languages.NapkinError.NapkinEffectError
       (Either
          (GHC.Base.NonEmpty
             Napkin.Run.Effects.Languages.Assertion.AssertionEntry)
          ([Napkin.Run.Effects.Languages.Assertion.AssertionEntry], Integer))
napkin-repl> result
Right (Right ([],4660))

runCustomSpec and runCustomHook

:runCustomSpec and :runCustomHook macros allow to run SpecProgramWithArgParser and HookProgramWithArgParser respectively:

  • :runCustomSpec accepts SpecProgramWithArgParser backend, target table name, and a Map Text Aeson.Value as arguments.
  • :runCustomHook accepts HookProgramWithArgParser backend, target table name, and a Map Text Aeson.Value as arguments.

napkin-repl

:runCustomSpec customSpec "target.table.name" [("arg", A.String "foo")]

napkin-repl

:runCustomHook customHook [("arg", A.String "foo")]
:{
 let customHook :: HookProgramWithArgParser b
     customHook = HookProgramWithArgParser $ \obj -> do
        arg <- obj .: "arg"
        pure $ do
          logInfo $ "The arg was: " <> arg
:}
napkin-repl> :runCustomHook customHook [("arg" .= "Hello world!")]
[2022-01-13 12:00:25][Info] The arg was: Hello world!
Right (Right ([],()))

Example

We can define a HookProgram with corresponding argument parser and test it directly from the REPL. For production use, we’d define them in a Haskell module and import them to REPL or reference in the Spec.

:{
 let checkManyTablesExistHook :: [Ref Table] -> HookProgram b
     checkManyTablesExistHook = mapM_ $ \table -> do
        exists <- checkTableExists table
        assertTrue ("check if table " <> refText table <> " exists") exists
     customHookWithArgs :: HookProgramWithArgParser b
     customHookWithArgs = HookProgramWithArgParser $ \obj -> do
        tables <- obj .: "tables"
        pure $ checkManyTablesExistHook tables
:}

:runCustomHook customHookWithArgs [("tables" .= ["chinook.Artist", "does_not.exist"])]
napkin-repl> :runCustomHook customHookWithArgs [("tables" .= ["chinook.Artist", "does_not.exist"])]
... some logs omited for brevity ...
[2022-01-13 12:10:03][Debug] Executing query: SELECT
    exists((SELECT 1 AS "aaac"
            FROM "pg_catalog"."pg_class" AS "aaaa"
            INNER JOIN "pg_catalog"."pg_namespace" AS "aaab" ON ("aaab"."oid" = "aaaa"."relnamespace")
            WHERE (("aaab"."nspname" = 'chinook')) and (("aaaa"."relname" = 'Artist')))) AS "check"
[2022-01-13 12:10:03][Debug] table chinook.Artist exists
[2022-01-13 12:10:03][Debug] Executing query: SELECT
    exists((SELECT 1 AS "aaac"
            FROM "pg_catalog"."pg_class" AS "aaaa"
            INNER JOIN "pg_catalog"."pg_namespace" AS "aaab" ON ("aaab"."oid" = "aaaa"."relnamespace")
            WHERE (("aaab"."nspname" = 'does_not')) and (("aaaa"."relname" = 'exist')))) AS "check"
[2022-01-13 12:10:03][Error] table does_not.exist exists
Right (Left (AssertionEntry {assertionGroup = AssertionGroup [], assertionMessage = "table chinook.Artist exists", assertionStatus = Success, assertionSeverity = FailNow} :| [AssertionEntry {assertionGroup = AssertionGroup [], assertionMessage = "table does_not.exist exists", assertionStatus = Failure Nothing, assertionSeverity = FailNow}]))
...