packages feed

todoist-sdk-0.1.2.1: src/Web/Todoist/Runner.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

{- |
Module      : Web.Todoist.Runner
Description : Entry points for executing Todoist operations
Copyright   : (c) 2025 Sam S. Almahri
License     : MIT
Maintainer  : sam.salmahri@gmail.com

This module provides the main entry points for executing Todoist operations.
It defines the 'TodoistRunner' type class and provides convenience functions
for running operations with different interpreters.

= Usage Example

@
import Web.Todoist.Runner
import Web.Todoist.Domain.Project

main :: IO ()
main = do
    let config = newTodoistConfig "your-api-token"
    result <- todoist config getAllProjects
    case result of
        Left err -> print err
        Right projects -> print projects
@
-}
module Web.Todoist.Runner
    ( todoist
    , newTodoistConfig
    , todoistTraceRunner
    , runTodoistWith
    , MonadTodoist
    ) where

import Web.Todoist.Domain.Comment (TodoistCommentM)
import Web.Todoist.Domain.Label (TodoistLabelM)
import Web.Todoist.Domain.Project (TodoistProjectM)
import Web.Todoist.Domain.Section (TodoistSectionM)
import Web.Todoist.Domain.Task (TodoistTaskM)
import Web.Todoist.Internal.Config (Token (..))
import Web.Todoist.Internal.Error (TodoistError)
import Web.Todoist.Runner.IO
    ( TodoistConfig (..)
    , TodoistIO (unTodoist)
    )
import Web.Todoist.Runner.Trace (Op, Trace (runTrace))

import Control.Applicative (Applicative (pure))
import Control.Monad.Trans.Except (runExceptT)
import Control.Monad.Trans.Reader (ReaderT (runReaderT))
import Control.Monad.Trans.Writer (execWriter)
import Data.Either (Either)
import Data.Function ((.))
import Data.Text (Text)
import System.IO (IO)

{- | Create a new Todoist configuration with an API token

The token can be obtained from Todoist Settings → Integrations → Developer.
This configuration is used to authenticate all API requests.

Example:

@
let config = newTodoistConfig "your-api-token-here"
@
-}
newTodoistConfig :: Text -> TodoistConfig
newTodoistConfig token = TodoistConfig {authToken = Token token}

{- | Constraint synonym for operations requiring all domain capabilities

This type alias combines 'TodoistProjectM', 'TodoistTaskM', 'TodoistCommentM',
'TodoistSectionM', and 'TodoistLabelM' constraints, allowing functions to work
with all Todoist resources without listing all individual constraints.
-}
class
    (TodoistProjectM m, TodoistTaskM m, TodoistCommentM m, TodoistSectionM m, TodoistLabelM m) =>
    MonadTodoist m
instance
    (TodoistProjectM m, TodoistTaskM m, TodoistCommentM m, TodoistSectionM m, TodoistLabelM m) =>
    MonadTodoist m

{- | Execute Todoist operations using the TodoistIO interpreter

This is the primary function for making real HTTP requests to the Todoist API.
It handles authentication, error handling, and returns results in an Either type.

Example:

@
result <- todoist config getAllProjects
case result of
    Left err -> putStrLn $ "Error: " ++ show err
    Right projects -> mapM_ print projects
@
-}
todoist :: TodoistConfig -> TodoistIO a -> IO (Either TodoistError a)
todoist env operations = runExceptT (runReaderT (unTodoist operations) env)

{- | Execute operations with the Trace interpreter for testing

The Trace interpreter records operations without executing them, useful for
testing and debugging. Returns a list of recorded operations.

Example:

@
ops <- todoistTraceRunner config getAllProjects
print ops  -- [GetAllProjects]
@
-}
todoistTraceRunner :: TodoistConfig -> Trace a -> IO (Either TodoistError [Op])
todoistTraceRunner _ = pure . pure . execWriter . runTrace

{- | Type class for running operations with different interpreters

This class allows you to run Todoist operations with any interpreter
that implements the required type class methods. The 'Output' type family
determines what type of result is returned.

Use 'runTodoistWith' for custom interpreters or when you need explicit
interpreter selection.
-}
class (MonadTodoist r) => TodoistRunner r where
    type Output r a -- Associated Type Families

    {- | Execute operations with a custom interpreter

    This function allows you to run operations with any interpreter that implements
    'TodoistRunner'. Use this for testing with the Trace interpreter or implementing
    custom interpreters.

    Example with Trace:

    @
    ops <- runTodoistWith config (getAllProjects :: Trace [Project])
    print ops  -- Shows recorded operations without executing them
    @
    -}
    runTodoistWith :: TodoistConfig -> r a -> IO (Either TodoistError (Output r a))

instance TodoistRunner TodoistIO where
    type Output TodoistIO a = a

    runTodoistWith :: TodoistConfig -> TodoistIO a -> IO (Either TodoistError a)
    runTodoistWith = todoist

instance TodoistRunner Trace where
    type Output Trace a = [Op]

    runTodoistWith :: TodoistConfig -> Trace a -> IO (Either TodoistError [Op])
    runTodoistWith = todoistTraceRunner