module Data.Apiary.Document.Internal where
import Control.Applicative((<$>))
import Data.Typeable(TypeRep)
import Data.Maybe(mapMaybe)
import Data.List(groupBy)
import Data.Function(on)
import Data.Apiary.Param(StrategyRep, QueryRep)
import Data.Apiary.Method(Method)
import Text.Blaze.Html(Html)
import qualified Data.Text as T
import qualified Data.ByteString as S
data Doc
= DocPath T.Text Doc
| DocRoot Doc
| DocFetch T.Text TypeRep (Maybe Html) Doc
| DocRest T.Text (Maybe Html) Doc
| DocAny Doc
| DocDropNext Doc
| DocMethod Method Doc
| DocQuery T.Text StrategyRep QueryRep (Maybe Html) Doc
| DocPrecondition Html Doc
| DocAccept S.ByteString Doc
| DocGroup T.Text Doc
| Document T.Text Doc
| Action
data Route
= Path T.Text Route
| Fetch T.Text TypeRep (Maybe Html) Route
| Rest T.Text (Maybe Html)
| Any
| End
instance Eq Route where
Path a d == Path b d' = a == b && d == d'
Fetch k a _ d == Fetch l b _ d' = k == l && a == b && d == d'
Rest k _ == Rest l _ = k == l
Any == Any = True
End == End = True
_ == _ = False
data Documents = Documents
{ noGroup :: [PathDoc]
, groups :: [(T.Text, [PathDoc])]
}
data PathDoc = PathDoc
{ path :: Route
, methods :: [(Method, [MethodDoc])]
}
data QueryDoc = QueryDoc
{ queryName :: T.Text
, queryStrategy :: StrategyRep
, queryRep :: QueryRep
, queryDocument :: (Maybe Html)
}
data MethodDoc = MethodDoc
{ queries :: [QueryDoc]
, preconditions :: [Html]
, accept :: Maybe S.ByteString
, document :: T.Text
}
data ToDocumentState = ToDocumentState
{ toDocumentPath :: Route -> Route
, toDocumentMethodDoc :: [MethodDoc] -> [(Method, [MethodDoc])]
, toDocumentQueries :: [QueryDoc] -> [QueryDoc]
, toDocumentPreconds :: [Html] -> [Html]
, toDocumentAccept :: Maybe S.ByteString
, toDocumentDocument :: Maybe T.Text
}
initialToDocumentState :: ToDocumentState
initialToDocumentState = ToDocumentState id (\md -> [("*", md)]) id id Nothing Nothing
docToDocument :: Doc -> Maybe (Maybe T.Text, PathDoc)
docToDocument = \case
(DocGroup "" d') -> (Nothing,) <$> loop initialToDocumentState d'
(DocGroup g d') -> (Just g,) <$> loop initialToDocumentState d'
d' -> (Nothing,) <$> loop initialToDocumentState d'
where
loop st (DocDropNext d) = loop st (dropNext d)
loop st (DocPath p d) = loop st { toDocumentPath = toDocumentPath st . Path p } d
loop st (DocRoot d) = loop st { toDocumentPath = const $ Path "" End } d
loop st (DocFetch k t h d) = loop st { toDocumentPath = toDocumentPath st . Fetch k t h } d
loop st (DocRest k h d) = loop st { toDocumentPath = toDocumentPath st . const (Rest k h) } d
loop st (DocAny d) = loop st { toDocumentPath = toDocumentPath st . const Any } d
loop st (DocMethod m d) = loop st { toDocumentMethodDoc = (\md -> [(m, md)]) } d
loop st (DocQuery p s q t d) = loop st { toDocumentQueries = toDocumentQueries st . (QueryDoc p s q t:) } d
loop st (DocPrecondition h d) = loop st { toDocumentPreconds = toDocumentPreconds st . (h:) } d
loop st (DocGroup _ d) = loop st d
loop st (DocAccept a d) = loop st { toDocumentAccept = Just a } d
loop st (Document t d) = loop st { toDocumentDocument = Just t} d
loop st Action = case toDocumentDocument st of
Nothing -> Nothing
Just doc -> Just . PathDoc (toDocumentPath st End) $ toDocumentMethodDoc st
[MethodDoc (toDocumentQueries st []) (toDocumentPreconds st []) (toDocumentAccept st) doc]
dropNext (DocPath _ d) = d
dropNext (DocRoot d) = d
dropNext (DocFetch _ _ _ d) = d
dropNext (DocRest _ _ d) = d
dropNext (DocAny d) = d
dropNext (DocDropNext d) = dropNext d
dropNext (DocMethod _ d) = d
dropNext (DocQuery _ _ _ _ d) = d
dropNext (DocPrecondition _ d) = d
dropNext (DocGroup _ d) = d
dropNext (DocAccept _ d) = d
dropNext (Document _ d) = d
dropNext Action = Action
mergePathDoc :: [PathDoc] -> [PathDoc]
mergePathDoc [] = []
mergePathDoc (pd:pds) = merge (filter (same pd) pds) : mergePathDoc (filter (not . same pd) pds)
where
same = (==) `on` path
merge pds' = PathDoc (path pd) (mergeMethods $ methods pd ++ concatMap methods pds')
mergeMethods :: [(Method, [MethodDoc])] -> [(Method, [MethodDoc])]
mergeMethods [] = []
mergeMethods (m:ms) = merge (filter (same m) ms) : mergeMethods (filter (not . same m) ms)
where
same = (==) `on` fst
merge ms' = (fst m, snd m ++ concatMap snd ms')
docsToDocuments :: [Doc] -> Documents
docsToDocuments doc =
let gds = mapMaybe docToDocument doc
ngs = mergePathDoc . map snd $ filter ((Nothing ==) . fst) gds
gs = map upGroup . groupBy ((==) `on` fst) $ mapMaybe trav gds
in Documents ngs gs
where
upGroup ((g,d):ig) = (g, mergePathDoc $ d : map snd ig)
upGroup [] = error "docsToDocuments: unknown error."
trav (Nothing, _) = Nothing
trav (Just a, b) = Just (a, b)