packages feed

webgear-openapi-1.3.0: src/WebGear/OpenApi/Trait/Status.hs

{-# OPTIONS_GHC -Wno-orphans #-}

-- | OpenApi implementation of 'Status' trait.
module WebGear.OpenApi.Trait.Status where

import Control.Applicative ((<|>))
import Control.Lens (at, mapped, (%~), (&), (.~), (?~), (^.))
import qualified Data.HashMap.Strict.InsOrd as Map
import Data.Maybe (fromMaybe)
import Data.OpenApi (
  Operation,
  PathItem,
  Referenced (..),
  Response,
  delete,
  description,
  get,
  head_,
  options,
  patch,
  paths,
  post,
  put,
  responses,
  trace,
 )
import qualified Network.HTTP.Types as HTTP
import WebGear.Core.Handler (Description (..))
import qualified WebGear.Core.Response as WG
import WebGear.Core.Trait (Set, With, setTrait)
import WebGear.Core.Trait.Status (Status (..))
import WebGear.OpenApi.Handler (OpenApiHandler (..), addRootPath, consumeDescription)

instance Set (OpenApiHandler m) Status where
  {-# INLINE setTrait #-}
  setTrait ::
    Status ->
    (WG.Response `With` ts -> WG.Response -> HTTP.Status -> WG.Response `With` (Status : ts)) ->
    OpenApiHandler m (WG.Response `With` ts, HTTP.Status) (WG.Response `With` (Status : ts))
  setTrait status _ = OpenApiHandler $ \doc -> do
    desc <- consumeDescription
    let doc' = if Map.null (doc ^. paths) then addRootPath doc else doc
    pure $ doc' & paths . mapped %~ setOperation desc status

setOperation :: Maybe Description -> Status -> PathItem -> PathItem
setOperation desc (Status status) item =
  item
    & delete %~ updateOperation
    & get %~ updateOperation
    & head_ %~ updateOperation
    & options %~ updateOperation
    & patch %~ updateOperation
    & post %~ updateOperation
    & put %~ updateOperation
    & trace %~ updateOperation
  where
    httpCode = HTTP.statusCode status

    updateOperation :: Maybe Operation -> Maybe Operation
    updateOperation Nothing = Just $ mempty @Operation & at httpCode ?~ addDescription emptyResp
    updateOperation (Just op) =
      let resp = addDescription $ fromMaybe emptyResp $ (op ^. at httpCode) <|> (op ^. at 0)
       in Just $ op & responses . responses %~ Map.insert httpCode resp . Map.delete 0

    emptyResp :: Referenced Response
    emptyResp = Inline mempty

    addDescription :: Referenced Response -> Referenced Response
    addDescription (Ref r) = Ref r
    addDescription (Inline r) =
      case desc of
        Nothing -> Inline r
        Just (Description d) -> Inline (r & description .~ d)