packages feed

imm-2.0.0.0: src/lib/Imm/Link.hs

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms   #-}
{-# LANGUAGE TypeApplications  #-}
-- | Simplified model for RFC 8288 links.
--
-- Cf <https://tools.ietf.org/html/rfc8288> . .
module Imm.Link
  ( Link(..)
  , Relation(..)
  , parseRelation
  , MediaType(..)
  , parseMediaType
  , pattern MediaTypeRSS
  , pattern MediaTypeAtom
  , pattern MediaTypeApplicationXML
  , pattern MediaTypeTextXML
  , pattern MediaTypeHTML
  ) where

import           Data.Aeson
import           Text.Parsec             (Stream, parse)
import           Text.Parser.Char
import           URI.ByteString.Extended


data Link = Link
  { _linkRelation :: Maybe Relation
  , _linkTitle    :: Text
  , _linkType     :: Maybe MediaType
  , _linkURI      :: AnyURI
  } deriving(Eq, Generic, Ord, Show, Typeable)

linkOptions :: Options
linkOptions = defaultOptions
  { fieldLabelModifier = camelTo2 '_' . drop (length @[] "_link")
  , omitNothingFields = True
  }

instance ToJSON Link where
  toJSON     = genericToJSON linkOptions
  toEncoding = genericToEncoding linkOptions

instance FromJSON Link where
  parseJSON = genericParseJSON linkOptions


-- | Cf <https://www.iana.org/assignments/link-relations/link-relations.xhtml> .
data Relation = Alternate | Edit | Next | NoFollow | Replies | Self | OtherRelation Text
  deriving (Eq, Generic, Ord, Read, Show, Typeable)

instance ToJSON Relation where
  toEncoding = genericToEncoding defaultOptions

instance FromJSON Relation

parseRelation :: Text -> Maybe Relation
parseRelation "alternate" = pure Alternate
parseRelation "edit"      = pure Edit
parseRelation "next"      = pure Next
parseRelation "nofollow"  = pure NoFollow
parseRelation "replies"   = pure Replies
parseRelation "self"      = pure Self
parseRelation ""          = Nothing
parseRelation t           = pure $ OtherRelation t


-- | https://tools.ietf.org/html/rfc6838
-- https://en.wikipedia.org/wiki/Media_type
data MediaType = MediaType
  { _mediaType       :: Text
  , _mediaSubtype    :: Text
  , _mediaSuffix     :: Text
  , _mediaParameters :: [Text]
  } deriving (Eq, Generic, Ord, Read, Show, Typeable)

mediaTypeOptions :: Options
mediaTypeOptions = defaultOptions
  { fieldLabelModifier = camelTo2 '_' . drop (length @[] "_media")
  , omitNothingFields = True
  }

instance ToJSON MediaType where
  toJSON     = genericToJSON mediaTypeOptions
  toEncoding = genericToEncoding mediaTypeOptions

instance FromJSON MediaType where
  parseJSON = genericParseJSON mediaTypeOptions


parseMediaType :: Stream s Identity Char => s -> Maybe MediaType
parseMediaType = either (const Nothing) pure . parse parser "" where
  parser = do
    t <- some $ noneOf "/"
    char '/'
    subtype <- some $ noneOf "+;"
    suffix <- optional $ char '+' >> some (noneOf ";")
    parameters <- many $ char ';' >> some (noneOf ";")

    return $ MediaType (toText t) (toText subtype) (toText $ fromMaybe mempty suffix) $ map toText parameters


pattern MediaTypeRSS :: [Text] -> MediaType
pattern MediaTypeRSS parameters = MediaType "application" "rss" "xml" parameters

pattern MediaTypeAtom :: [Text] -> MediaType
pattern MediaTypeAtom parameters = MediaType "application" "atom" "xml" parameters

pattern MediaTypeApplicationXML :: Text -> [Text] -> MediaType
pattern MediaTypeApplicationXML suffix parameters = MediaType "application" "xml" suffix parameters

pattern MediaTypeTextXML :: Text -> [Text] -> MediaType
pattern MediaTypeTextXML suffix parameters = MediaType "text" "xml" suffix parameters

pattern MediaTypeHTML :: Text -> [Text] -> MediaType
pattern MediaTypeHTML suffix parameters = MediaType "text" "html" suffix parameters