-- | The shapes the rest of the app talks in. module Todo.Model ( ListId (..) , TodoId (..) , Todo (..) , Filter (..) , parseFilter , filterName , filterPath , visible , parseTodoId , todoIdText ) where import Data.Int (Int64) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Read qualified as T -- | One visitor's list. Every query about todos is scoped by one. newtype ListId = ListId Int64 deriving (Eq, Show) newtype TodoId = TodoId Int64 deriving (Eq, Show) data Todo = Todo { todoId :: !TodoId , todoTitle :: !Text , todoCompleted :: !Bool } deriving (Eq, Show) data Filter = All | Active | Completed deriving (Eq, Show, Enum, Bounded) -- | The @filter@ form field. Anything missing or unknown means 'All'. parseFilter :: Maybe Text -> Filter parseFilter = \case Just "active" -> Active Just "completed" -> Completed _ -> All filterName :: Filter -> Text filterName = \case All -> "all" Active -> "active" Completed -> "completed" filterPath :: Filter -> Text filterPath = \case All -> "/" Active -> "/active" Completed -> "/completed" visible :: Filter -> Todo -> Bool visible = \case All -> const True Active -> not . todoCompleted Completed -> todoCompleted -- | A todo id as it appears in a path or a query: plain decimal digits, -- short enough to fit an 'Int64'. parseTodoId :: Text -> Maybe TodoId parseTodoId t | T.null t || T.length t > 18 || not (T.all (\c -> c >= '0' && c <= '9') t) = Nothing | otherwise = case T.decimal t of Right (n, rest) | T.null rest -> Just (TodoId n) _ -> Nothing todoIdText :: TodoId -> Text todoIdText (TodoId n) = T.pack (show n)