packages feed

compaREST-0.1.0.0: src/Data/OpenApi/Compare/Validate/Responses.hs

{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}

module Data.OpenApi.Compare.Validate.Responses
  ( Behave (..),
  )
where

import Data.HList
import Data.HashMap.Strict.InsOrd as IOHM
import Data.Map.Strict as M
import Data.Maybe
import Data.OpenApi
import Data.OpenApi.Compare.Behavior
import Data.OpenApi.Compare.References
import Data.OpenApi.Compare.Subtree
import Data.OpenApi.Compare.Validate.Header ()
import Data.OpenApi.Compare.Validate.Link ()
import Data.OpenApi.Compare.Validate.MediaTypeObject
import Data.OpenApi.Compare.Validate.Products
import Data.OpenApi.Compare.Validate.Schema ()
import Data.OpenApi.Compare.Validate.Sums
import qualified Data.Text as T
import Network.HTTP.Media (MediaType)
import Text.Pandoc.Builder

tracedResponses :: Traced Responses -> IOHM.InsOrdHashMap HttpStatusCode (Traced (Referenced Response))
tracedResponses resp =
  IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseCodeStep k))) $
    _responsesResponses $ extract resp

instance Subtree Responses where
  type SubtreeLevel Responses = 'OperationLevel
  type
    CheckEnv Responses =
      '[ ProdCons (Traced (Definitions Response))
       , ProdCons (Traced (Definitions Header))
       , ProdCons (Traced (Definitions Schema))
       , ProdCons (Traced (Definitions Link))
       ]

  checkStructuralCompatibility env pc = do
    structuralMaybe env $ sequence . stepTraced ResponseDefaultStep . fmap _responsesDefault <$> pc
    iohmStructural env $ stepTraced ResponsesStep . fmap _responsesResponses <$> pc
    pure ()

  -- Roles are already swapped. Producer is a server and consumer is a
  -- client. Response codes are sum-like entity because we can answer with only
  -- one element
  checkSemanticCompatibility env beh prodCons = do
    let defs = getH @(ProdCons (Traced (Definitions Response))) env
        check code resps = checkCompatibility @Response (beh >>> step (WithStatusCode code)) env resps
        elements = getEls <$> defs <*> prodCons
        getEls respDef resps = M.fromList $ do
          (code, respRef) <- IOHM.toList $ tracedResponses resps
          pure (code, dereference respDef respRef)
    checkSums beh ConsumerDoesntHaveResponseCode check elements

tracedContent :: Traced Response -> IOHM.InsOrdHashMap MediaType (Traced MediaTypeObject)
tracedContent resp =
  IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseMediaObject k))) $
    _responseContent $ extract resp

tracedHeaders :: Traced Response -> IOHM.InsOrdHashMap HeaderName (Traced (Referenced Header))
tracedHeaders resp =
  IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseHeader k))) $
    _responseHeaders $ extract resp

instance Issuable 'ResponseLevel where
  data Issue 'ResponseLevel
    = ConsumerDoesntHaveMediaType MediaType
    | ProducerDoesntHaveResponseHeader HeaderName
    deriving stock (Eq, Ord, Show)
  issueKind = \case
    _ -> CertainIssue
  describeIssue Forward (ConsumerDoesntHaveMediaType t) =
    para $ "Media type was removed: " <> (code . T.pack . show $ t) <> "."
  describeIssue Backward (ConsumerDoesntHaveMediaType t) =
    para $ "Media type was added: " <> (code . T.pack . show $ t) <> "."
  describeIssue Forward (ProducerDoesntHaveResponseHeader h) =
    para $ "New header was added " <> code h <> "."
  describeIssue Backward (ProducerDoesntHaveResponseHeader h) =
    para $ "Header was removed " <> code h <> "."

instance Behavable 'ResponseLevel 'PayloadLevel where
  data Behave 'ResponseLevel 'PayloadLevel
    = ResponsePayload
    deriving stock (Eq, Ord, Show)

  describeBehavior ResponsePayload = "Payload"

instance Behavable 'ResponseLevel 'HeaderLevel where
  data Behave 'ResponseLevel 'HeaderLevel
    = InHeader HeaderName
    deriving stock (Eq, Ord, Show)

  describeBehavior (InHeader name) = "Header " <> code name

instance Subtree Response where
  type SubtreeLevel Response = 'ResponseLevel
  type
    CheckEnv Response =
      '[ ProdCons (Traced (Definitions Header))
       , ProdCons (Traced (Definitions Schema))
       , ProdCons (Traced (Definitions Link))
       ]
  checkStructuralCompatibility env pc = do
    iohmStructural env $ stepTraced ResponseMediaObjects . fmap _responseContent <$> pc
    iohmStructural env $ stepTraced ResponseHeaders . fmap _responseHeaders <$> pc
    iohmStructural env $ stepTraced ResponseLinks . fmap _responseLinks <$> pc
    pure ()
  checkSemanticCompatibility env beh prodCons = do
    -- Roles are already swapped. Producer is a server and consumer is a client
    checkMediaTypes
    checkHeaders
    pure ()
    where
      checkMediaTypes = do
        -- Media types are sum-like entity
        let check mediaType mtObj =
              let mtEnv = HCons mediaType env
               in checkCompatibility @MediaTypeObject (beh >>> step ResponsePayload) mtEnv mtObj
            elements = getEls <$> prodCons
            getEls resp = M.fromList . IOHM.toList $ tracedContent resp
        checkSums beh ConsumerDoesntHaveMediaType check elements

      checkHeaders = do
        -- Headers are product-like entities
        let check name hdrs = checkCompatibility @Header (beh >>> step (InHeader name)) env hdrs
            elements = getEls <$> headerDefs <*> prodCons
            getEls headerDef resp = M.fromList $ do
              (hname, headerRef) <- IOHM.toList $ tracedHeaders resp
              let header = dereference headerDef headerRef
              pure
                ( hname
                , ProductLike
                    { productValue = header
                    , required = fromMaybe False . _headerRequired . extract $ header
                    }
                )
        checkProducts beh ProducerDoesntHaveResponseHeader check elements
      headerDefs = getH @(ProdCons (Traced (Definitions Header))) env

instance Steppable Responses (Referenced Response) where
  data Step Responses (Referenced Response)
    = ResponseCodeStep HttpStatusCode
    | ResponseDefaultStep
    deriving stock (Eq, Ord, Show)

instance Steppable Response MediaTypeObject where
  data Step Response MediaTypeObject = ResponseMediaObject MediaType
    deriving stock (Eq, Ord, Show)

instance Steppable Response (IOHM.InsOrdHashMap MediaType MediaTypeObject) where
  data Step Response (IOHM.InsOrdHashMap MediaType MediaTypeObject) = ResponseMediaObjects
    deriving stock (Eq, Ord, Show)

instance Steppable Response (Definitions (Referenced Header)) where
  data Step Response (Definitions (Referenced Header)) = ResponseHeaders
    deriving stock (Eq, Ord, Show)

instance Steppable Response (Definitions (Referenced Link)) where
  data Step Response (Definitions (Referenced Link)) = ResponseLinks
    deriving stock (Eq, Ord, Show)

instance Steppable Response (Referenced Header) where
  data Step Response (Referenced Header) = ResponseHeader HeaderName
    deriving stock (Eq, Ord, Show)

instance Steppable Responses (IOHM.InsOrdHashMap HttpStatusCode (Referenced Response)) where
  data Step Responses (IOHM.InsOrdHashMap HttpStatusCode (Referenced Response)) = ResponsesStep
    deriving stock (Eq, Ord, Show)