packages feed

colorless-2.2.5: library/Colorless/Endpoint.hs

{-# LANGUAGE DataKinds #-}
module Colorless.Endpoint
  ( runColorless
  , runColorlessSingleton
  ) where

import qualified Data.Map as Map
import qualified Data.HashMap.Lazy as HML
import Control.Monad.IO.Class (MonadIO(..))
import Control.Concurrent.Async.Lifted ()
import Data.Aeson
import Data.Aeson.Types (parseMaybe)
import Data.Text (Text)
import Data.Map (Map)

import Colorless.Types
import Colorless.Server.Exchange

runColorlessSingleton
  :: (MonadIO m, RuntimeThrower m)
  => Version
  -> (Request -> m Response)
  -> Value
  -> m Value
runColorlessSingleton Version{major,minor} handleRequest = runColorless (Map.singleton major (minor, handleRequest))

runColorless
  :: (MonadIO m, RuntimeThrower m)
  => Map Major (Minor, Request -> m Response)
  -> Value
  -> m Value
runColorless handleRequestMap v = do
  colorlessVersion <- getColorlessVersion v
  assertColorlessVersionCompatiability colorlessVersion
  apiVersion <- getApiVersion v
  let apiMajor = major apiVersion
  case Map.lookup apiMajor handleRequestMap of
    Nothing -> case leastAndGreatest (Map.keys handleRequestMap) of
      Nothing -> runtimeThrow RuntimeError'NoImplementation
      Just (minMajor, maxMajor) ->
        if minMajor > apiMajor
          then runtimeThrow RuntimeError'ApiMajorVersionTooLow
          else if maxMajor < apiMajor
            then runtimeThrow RuntimeError'ApiMajorVersionTooHigh
            else runtimeThrow RuntimeError'NoImplementation
    Just (maxMinor, handleRequest) -> if minor apiVersion > maxMinor
      then runtimeThrow RuntimeError'ApiMinorVersionTooHigh
      else case parseRequest v of
        Nothing -> runtimeThrow RuntimeError'UnparsableFormat
        Just req -> toJSON <$> handleRequest req

leastAndGreatest :: Ord a => [a] -> Maybe (a,a)
leastAndGreatest [] = Nothing
leastAndGreatest xs = Just (minimum xs, maximum xs)

assertColorlessVersionCompatiability :: RuntimeThrower m => Version -> m ()
assertColorlessVersionCompatiability Version{major,minor}
  | major > mustMajor = runtimeThrow RuntimeError'ColorlessMajorVersionTooHigh
  | major < mustMajor = runtimeThrow RuntimeError'ColorlessMajorVersionTooLow
  | minor > maxMinor = runtimeThrow RuntimeError'ColorlessMinorVersionTooHigh
  | otherwise = return ()
  where
    mustMajor = 0
    maxMinor = 0

getApiVersion :: RuntimeThrower m => Value -> m Version
getApiVersion = getVersion "version" RuntimeError'NoApiVersion

getColorlessVersion :: RuntimeThrower m => Value -> m Version
getColorlessVersion = getVersion "colorless" RuntimeError'NoColorlessVersion

getVersion :: RuntimeThrower m => Text -> RuntimeError -> Value -> m Version
getVersion name err (Object o) = case HML.lookup name o of
  Just x -> maybe (runtimeThrow err) return (parseMaybe parseJSON x)
  Nothing -> runtimeThrow err
getVersion _ err _ = runtimeThrow err

parseRequest :: Value -> Maybe Request
parseRequest = parseMaybe parseJSON