packages feed

pinboard-0.10.3.0: src/Pinboard/ApiTypes.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}

-- |
-- Module      : Pinboard.ApiTypes
-- Copyright   : (c) Jon Schoning, 2015
-- Maintainer  : jonschoning@gmail.com
-- Stability   : experimental
-- Portability : POSIX
module Pinboard.ApiTypes where

import Data.Aeson
import Data.Aeson.Types (Parser)
import qualified Data.Aeson.KeyMap as A
import qualified Data.Aeson.Key as A
import Data.HashMap.Strict (HashMap)
import Data.Data (Data, Typeable)
import Data.Text (Text, words, unwords, unpack, pack)
import Data.Time (UTCTime, parseTimeM)
import Data.Time.Calendar (Day)
import GHC.Generics (Generic)

import qualified Data.HashMap.Strict as HM
#if !MIN_VERSION_aeson(1,0,0)
import qualified Data.Vector as V
#endif
import Data.Time.Format (formatTime, defaultTimeLocale)
import Control.Applicative
import Prelude hiding (words, unwords)

-- * Posts
data Posts = Posts
  { postsDate :: !UTCTime
  , postsUser :: !Text
  , postsPosts :: [Post]
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON Posts where
  parseJSON (Object o) = Posts <$> o .: "date" <*> o .: "user" <*> o .: "posts"
  parseJSON _ = fail "bad parse"

instance ToJSON Posts where
  toJSON Posts {..} =
    object
      [ "date" .= toJSON postsDate
      , "user" .= toJSON postsUser
      , "posts" .= toJSON postsPosts
      ]

data Post = Post
  { postHref :: !Text
  , postDescription :: !Text
  , postExtended :: !Text
  , postMeta :: !Text
  , postHash :: !Text
  , postTime :: !UTCTime
  , postShared :: !Bool
  , postToRead :: !Bool
  , postTags :: [Tag]
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON Post where
  parseJSON (Object o) =
    Post <$> o .: "href" <*> o .: "description" <*> o .: "extended" <*>
    o .: "meta" <*>
    o .: "hash" <*>
    o .: "time" <*>
    (boolFromYesNo <$> o .: "shared") <*>
    (boolFromYesNo <$> o .: "toread") <*>
    (words <$> o .: "tags")
  parseJSON _ = fail "bad parse"

instance ToJSON Post where
  toJSON Post {..} =
    object
      [ "href" .= toJSON postHref
      , "description" .= toJSON postDescription
      , "extended" .= toJSON postExtended
      , "meta" .= toJSON postMeta
      , "hash" .= toJSON postHash
      , "time" .= toJSON postTime
      , "shared" .= boolToYesNo postShared
      , "toread" .= boolToYesNo postToRead
      , "tags" .= unwords postTags
      ]

boolFromYesNo :: Text -> Bool
boolFromYesNo "yes" = True
boolFromYesNo _ = False

boolToYesNo :: Bool -> Text
boolToYesNo True = "yes"
boolToYesNo _ = "no"

data PostDates = PostDates
  { postDatesUser :: !Text
  , postDatesTag :: !Text
  , postDatesCount :: [(Day, Int)]
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON PostDates where
  parseJSON (Object o) =
    PostDates <$> o .: "user" <*> o .: "tag" <*> (parseDates <$> o .: "dates")
    where
      parseDates :: Value -> [DateCount]
      parseDates (Object o') = do
        (dateStr, String countStr) <- A.toList o'
        return (read (A.toString dateStr), read (unpack countStr))
      parseDates _ = []
  parseJSON _ = fail "bad parse"

instance ToJSON PostDates where
  toJSON PostDates {..} =
    object
      [ "user" .= toJSON postDatesUser
      , "tag" .= toJSON postDatesTag
      , "dates" .= object (dateCountToPair <$> postDatesCount)
      ]
    where
      dateCountToPair (day, count) =
        ((A.fromString . show) day, String $ (pack . show) count)

type DateCount = (Day, Int)

-- * Notes
data NoteList = NoteList
  { noteListCount :: !Int
  , noteListItems :: [NoteListItem]
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON NoteList where
  parseJSON (Object o) = NoteList <$> o .: "count" <*> o .: "notes"
  parseJSON _ = fail "bad parse"

instance ToJSON NoteList where
  toJSON NoteList {..} =
    object ["count" .= toJSON noteListCount, "notes" .= toJSON noteListItems]

data NoteListItem = NoteListItem
  { noteListItemId :: !Text
  , noteListItemHash :: !Text
  , noteListItemTitle :: !Text
  , noteListItemLength :: !Int
  , noteListItemCreatedAt :: !UTCTime
  , noteListItemUpdatedAt :: !UTCTime
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON NoteListItem where
  parseJSON (Object o) =
    NoteListItem <$> o .: "id" <*> o .: "hash" <*> o .: "title" <*>
    (read <$> (o .: "length")) <*>
    (readNoteTime =<< o .: "created_at") <*>
    (readNoteTime =<< o .: "updated_at")
  parseJSON _ = fail "bad parse"

instance ToJSON NoteListItem where
  toJSON NoteListItem {..} =
    object
      [ "id" .= toJSON noteListItemId
      , "hash" .= toJSON noteListItemHash
      , "title" .= toJSON noteListItemTitle
      , "length" .= toJSON (show noteListItemLength)
      , "created_at" .= toJSON (showNoteTime noteListItemCreatedAt)
      , "updated_at" .= toJSON (showNoteTime noteListItemUpdatedAt)
      ]

data Note = Note
  { noteId :: !Text
  , noteHash :: !Text
  , noteTitle :: !Text
  , noteText :: !Text
  , noteLength :: !Int
  , noteCreatedAt :: !UTCTime
  , noteUpdatedAt :: !UTCTime
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON Note where
  parseJSON (Object o) =
    Note <$> o .: "id" <*> o .: "hash" <*> o .: "title" <*> o .: "text" <*>
    o .: "length" <*>
    (readNoteTime =<< o .: "created_at") <*>
    (readNoteTime =<< o .: "updated_at")
  parseJSON _ = fail "bad parse"

instance ToJSON Note where
  toJSON Note {..} =
    object
      [ "id" .= toJSON noteId
      , "hash" .= toJSON noteHash
      , "title" .= toJSON noteTitle
      , "text" .= toJSON noteText
      , "length" .= toJSON noteLength
      , "created_at" .= toJSON (showNoteTime noteCreatedAt)
      , "updated_at" .= toJSON (showNoteTime noteUpdatedAt)
      ]

readNoteTime
  :: MonadFail m
  => String -> m UTCTime
readNoteTime = parseTimeM True defaultTimeLocale "%F %T"

showNoteTime :: UTCTime -> String
showNoteTime = formatTime defaultTimeLocale "%F %T"

-- * Tags
type TagMap = HashMap Tag Int

newtype JsonTagMap = ToJsonTagMap
  { fromJsonTagMap :: TagMap
  } deriving (Show, Eq, Data, Typeable, Generic)

instance FromJSON JsonTagMap where
  parseJSON = toTags
    where
      toTags (Object o) =
        return . ToJsonTagMap $ HM.map (\(String s) -> read (unpack s)) $ A.toHashMapText o
      toTags _ = fail "bad parse"

instance ToJSON JsonTagMap where
  toJSON (ToJsonTagMap o) = toJSON $ show <$> o

data Suggested
  = Popular [Text]
  | Recommended [Text]
  deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON Suggested where
  parseJSON (Object o)
    | A.member "popular" o = Popular <$> (o .: "popular")
    | A.member "recommended" o = Recommended <$> (o .: "recommended")
    | otherwise = fail "bad parse"
  parseJSON _ = fail "bad parse"

#if !MIN_VERSION_aeson(1,0,0)
instance ToJSON [Suggested] where
  toJSON xs = Array $ toJSON <$> V.fromList xs
#endif

instance ToJSON Suggested where
  toJSON (Popular tags) = object ["popular" .= toJSON tags]
  toJSON (Recommended tags) = object ["recommended" .= toJSON tags]

-- * Scalars
newtype DoneResult = ToDoneResult
  { fromDoneResult :: ()
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON DoneResult where
  parseJSON (Object o) = parseDone =<< (o .: "result" <|> o .: "result_code")
    where
      parseDone :: Text -> Parser DoneResult
      parseDone "done" = return $ ToDoneResult ()
      parseDone msg = (fail . unpack) msg
  parseJSON _ = fail "bad parse"

newtype TextResult = ToTextResult
  { fromTextResult :: Text
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON TextResult where
  parseJSON (Object o) = ToTextResult <$> (o .: "result")
  parseJSON _ = fail "bad parse"

newtype UpdateTime = ToUpdateTime
  { fromUpdateTime :: UTCTime
  } deriving (Show, Eq, Data, Typeable, Generic, Ord)

instance FromJSON UpdateTime where
  parseJSON (Object o) = ToUpdateTime <$> (o .: "update_time")
  parseJSON _ = error "bad parse"

-- prettyString :: String -> String
-- prettyString s = case parseExp s of
--     ParseOk x -> prettyPrint x
--     ParseFailed{} -> s
-- pretty :: Show a => a -> String
-- pretty = prettyString . show
-- * Aliases
-- | as defined by RFC 3986. Allowed schemes are http, https, javascript, mailto, ftp and file. The Safari-specific feed scheme is allowed but will be treated as a synonym for http.
type Url = Text

-- | up to 255 characters long
type Description = Text

-- | up to 65536 characters long. Any URLs will be auto-linkified when displayed.
type Extended = Text

-- | up to 255 characters. May not contain commas or whitespace.
type Tag = Text

type Old = Tag

type New = Tag

type Count = Int

type NumResults = Int

type StartOffset = Int

type Shared = Bool

type Replace = Bool

type ToRead = Bool

-- | UTC date in this format: 2010-12-11. Same range as datetime above
type Date = Day

-- | UTC timestamp in this format: 2010-12-11T19:48:02Z. Valid date range is Jan 1, 1 AD to January 1, 2100 (but see note below about future timestamps).
type DateTime = UTCTime

type FromDateTime = DateTime

type ToDateTime = DateTime

type Meta = Int

type NoteId = Text