{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Contains function to resolve references within the OpenAPI specification
module OpenAPI.Generate.Reference
  ( ReferenceMap,
    ComponentReference (..),
    buildReferenceMap,
    getSchemaReference,
    getResponseReference,
    getParameterReference,
    getExampleReference,
    getRequestBodyReference,
    getHeaderReference,
    getSecuritySchemeReference,
  )
where

import Control.Monad
import qualified Data.Bifunctor as BF
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import Data.Text (Text)
import GHC.Generics
import qualified OpenAPI.Generate.Types as OAT
import qualified OpenAPI.Generate.Types.Schema as OAS

-- | Represents all types the 'ReferenceMap' can hold
data ComponentReference
  = SchemaReference OAS.SchemaObject
  | ResponseReference OAT.ResponseObject
  | ParameterReference OAT.ParameterObject
  | ExampleReference OAT.ExampleObject
  | RequestBodyReference OAT.RequestBodyObject
  | HeaderReference OAT.HeaderObject
  | SecuritySchemeReference OAT.SecuritySchemeObject
  deriving (Int -> ComponentReference -> ShowS
[ComponentReference] -> ShowS
ComponentReference -> String
(Int -> ComponentReference -> ShowS)
-> (ComponentReference -> String)
-> ([ComponentReference] -> ShowS)
-> Show ComponentReference
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ComponentReference -> ShowS
showsPrec :: Int -> ComponentReference -> ShowS
$cshow :: ComponentReference -> String
show :: ComponentReference -> String
$cshowList :: [ComponentReference] -> ShowS
showList :: [ComponentReference] -> ShowS
Show, ComponentReference -> ComponentReference -> Bool
(ComponentReference -> ComponentReference -> Bool)
-> (ComponentReference -> ComponentReference -> Bool)
-> Eq ComponentReference
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ComponentReference -> ComponentReference -> Bool
== :: ComponentReference -> ComponentReference -> Bool
$c/= :: ComponentReference -> ComponentReference -> Bool
/= :: ComponentReference -> ComponentReference -> Bool
Eq, (forall x. ComponentReference -> Rep ComponentReference x)
-> (forall x. Rep ComponentReference x -> ComponentReference)
-> Generic ComponentReference
forall x. Rep ComponentReference x -> ComponentReference
forall x. ComponentReference -> Rep ComponentReference x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. ComponentReference -> Rep ComponentReference x
from :: forall x. ComponentReference -> Rep ComponentReference x
$cto :: forall x. Rep ComponentReference x -> ComponentReference
to :: forall x. Rep ComponentReference x -> ComponentReference
Generic)

-- | A lookup table for references within the OpenAPI specification
type ReferenceMap = Map.Map Text ComponentReference

-- | Creates a 'ReferenceMap' from an 'OAT.OpenApiSpecification' containing all elements within components.
-- It does not capture possibly referenced locations anywhere else in the specification.
buildReferenceMap :: OAT.OpenApiSpecification -> ReferenceMap
buildReferenceMap :: OpenApiSpecification -> ReferenceMap
buildReferenceMap =
  [(Text, ComponentReference)] -> ReferenceMap
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList
    ([(Text, ComponentReference)] -> ReferenceMap)
-> (OpenApiSpecification -> [(Text, ComponentReference)])
-> OpenApiSpecification
-> ReferenceMap
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ( \ComponentsObject
o ->
          Text
-> (SchemaObject -> ComponentReference)
-> Map Text (Referencable SchemaObject)
-> [(Text, ComponentReference)]
forall a.
Text
-> (a -> ComponentReference)
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
buildReferencesForComponentType Text
"schemas" SchemaObject -> ComponentReference
SchemaReference (ComponentsObject -> Map Text (Referencable SchemaObject)
OAT.componentsObjectSchemas ComponentsObject
o)
            [(Text, ComponentReference)]
-> [(Text, ComponentReference)] -> [(Text, ComponentReference)]
forall a. Semigroup a => a -> a -> a
<> Text
-> (ResponseObject -> ComponentReference)
-> Map Text (Referencable ResponseObject)
-> [(Text, ComponentReference)]
forall a.
Text
-> (a -> ComponentReference)
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
buildReferencesForComponentType Text
"responses" ResponseObject -> ComponentReference
ResponseReference (ComponentsObject -> Map Text (Referencable ResponseObject)
OAT.componentsObjectResponses ComponentsObject
o)
            [(Text, ComponentReference)]
-> [(Text, ComponentReference)] -> [(Text, ComponentReference)]
forall a. Semigroup a => a -> a -> a
<> Text
-> (ParameterObject -> ComponentReference)
-> Map Text (Referencable ParameterObject)
-> [(Text, ComponentReference)]
forall a.
Text
-> (a -> ComponentReference)
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
buildReferencesForComponentType Text
"parameters" ParameterObject -> ComponentReference
ParameterReference (ComponentsObject -> Map Text (Referencable ParameterObject)
OAT.componentsObjectParameters ComponentsObject
o)
            [(Text, ComponentReference)]
-> [(Text, ComponentReference)] -> [(Text, ComponentReference)]
forall a. Semigroup a => a -> a -> a
<> Text
-> (ExampleObject -> ComponentReference)
-> Map Text (Referencable ExampleObject)
-> [(Text, ComponentReference)]
forall a.
Text
-> (a -> ComponentReference)
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
buildReferencesForComponentType Text
"examples" ExampleObject -> ComponentReference
ExampleReference (ComponentsObject -> Map Text (Referencable ExampleObject)
OAT.componentsObjectExamples ComponentsObject
o)
            [(Text, ComponentReference)]
-> [(Text, ComponentReference)] -> [(Text, ComponentReference)]
forall a. Semigroup a => a -> a -> a
<> Text
-> (RequestBodyObject -> ComponentReference)
-> Map Text (Referencable RequestBodyObject)
-> [(Text, ComponentReference)]
forall a.
Text
-> (a -> ComponentReference)
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
buildReferencesForComponentType Text
"requestBodies" RequestBodyObject -> ComponentReference
RequestBodyReference (ComponentsObject -> Map Text (Referencable RequestBodyObject)
OAT.componentsObjectRequestBodies ComponentsObject
o)
            [(Text, ComponentReference)]
-> [(Text, ComponentReference)] -> [(Text, ComponentReference)]
forall a. Semigroup a => a -> a -> a
<> Text
-> (HeaderObject -> ComponentReference)
-> Map Text (Referencable HeaderObject)
-> [(Text, ComponentReference)]
forall a.
Text
-> (a -> ComponentReference)
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
buildReferencesForComponentType Text
"headers" HeaderObject -> ComponentReference
HeaderReference (ComponentsObject -> Map Text (Referencable HeaderObject)
OAT.componentsObjectHeaders ComponentsObject
o)
            [(Text, ComponentReference)]
-> [(Text, ComponentReference)] -> [(Text, ComponentReference)]
forall a. Semigroup a => a -> a -> a
<> Text
-> (SecuritySchemeObject -> ComponentReference)
-> Map Text (Referencable SecuritySchemeObject)
-> [(Text, ComponentReference)]
forall a.
Text
-> (a -> ComponentReference)
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
buildReferencesForComponentType Text
"securitySchemes" SecuritySchemeObject -> ComponentReference
SecuritySchemeReference (ComponentsObject -> Map Text (Referencable SecuritySchemeObject)
OAT.componentsObjectSecuritySchemes ComponentsObject
o)
      )
    (ComponentsObject -> [(Text, ComponentReference)])
-> (OpenApiSpecification -> ComponentsObject)
-> OpenApiSpecification
-> [(Text, ComponentReference)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OpenApiSpecification -> ComponentsObject
OAT.openApiSpecificationComponents

-- | Maps the subtypes of components to the entries of the 'ReferenceMap' and filters references (the lookup table should only contain concrete values).
buildReferencesForComponentType ::
  Text ->
  (a -> ComponentReference) ->
  Map.Map Text (OAT.Referencable a) ->
  [(Text, ComponentReference)]
buildReferencesForComponentType :: forall a.
Text
-> (a -> ComponentReference)
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
buildReferencesForComponentType Text
componentName a -> ComponentReference
constructor =
  ((Text, ComponentReference) -> (Text, ComponentReference))
-> [(Text, ComponentReference)] -> [(Text, ComponentReference)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((Text -> Text)
-> (Text, ComponentReference) -> (Text, ComponentReference)
forall a b c. (a -> b) -> (a, c) -> (b, c)
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
BF.first ((Text
"#/components/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
componentName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/") Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>))
    ([(Text, ComponentReference)] -> [(Text, ComponentReference)])
-> (Map Text (Referencable a) -> [(Text, ComponentReference)])
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Text, Referencable a) -> Maybe (Text, ComponentReference))
-> [(Text, Referencable a)] -> [(Text, ComponentReference)]
forall a b. (a -> Maybe b) -> [a] -> [b]
Maybe.mapMaybe ((a -> ComponentReference)
-> (Text, Referencable a) -> Maybe (Text, ComponentReference)
forall a.
(a -> ComponentReference)
-> (Text, Referencable a) -> Maybe (Text, ComponentReference)
convertReferencableToReference a -> ComponentReference
constructor)
    ([(Text, Referencable a)] -> [(Text, ComponentReference)])
-> (Map Text (Referencable a) -> [(Text, Referencable a)])
-> Map Text (Referencable a)
-> [(Text, ComponentReference)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map Text (Referencable a) -> [(Text, Referencable a)]
forall k a. Map k a -> [(k, a)]
Map.toList

convertReferencableToReference ::
  (a -> ComponentReference) ->
  (Text, OAT.Referencable a) ->
  Maybe (Text, ComponentReference)
convertReferencableToReference :: forall a.
(a -> ComponentReference)
-> (Text, Referencable a) -> Maybe (Text, ComponentReference)
convertReferencableToReference a -> ComponentReference
constructor (Text
name', OAT.Concrete a
object) = (Text, ComponentReference) -> Maybe (Text, ComponentReference)
forall a. a -> Maybe a
Just (Text
name', a -> ComponentReference
constructor a
object)
convertReferencableToReference a -> ComponentReference
_ (Text
_, OAT.Reference Text
_) = Maybe (Text, ComponentReference)
forall a. Maybe a
Nothing

getReference :: Text -> ReferenceMap -> Maybe ComponentReference
getReference :: Text -> ReferenceMap -> Maybe ComponentReference
getReference = Text -> ReferenceMap -> Maybe ComponentReference
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup

createReferenceLookup :: (ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup :: forall a.
(ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup ComponentReference -> Maybe a
conversionFn Text
key = Text -> ReferenceMap -> Maybe ComponentReference
getReference Text
key (ReferenceMap -> Maybe ComponentReference)
-> (ComponentReference -> Maybe a) -> ReferenceMap -> Maybe a
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> ComponentReference -> Maybe a
conversionFn

-- | Resolve a 'OAS.SchemaObject' reference from a 'ReferenceMap'
getSchemaReference :: Text -> ReferenceMap -> Maybe OAS.SchemaObject
getSchemaReference :: Text -> ReferenceMap -> Maybe SchemaObject
getSchemaReference = (ComponentReference -> Maybe SchemaObject)
-> Text -> ReferenceMap -> Maybe SchemaObject
forall a.
(ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup ((ComponentReference -> Maybe SchemaObject)
 -> Text -> ReferenceMap -> Maybe SchemaObject)
-> (ComponentReference -> Maybe SchemaObject)
-> Text
-> ReferenceMap
-> Maybe SchemaObject
forall a b. (a -> b) -> a -> b
$ \case
  SchemaReference SchemaObject
r -> SchemaObject -> Maybe SchemaObject
forall a. a -> Maybe a
Just SchemaObject
r
  ComponentReference
_ -> Maybe SchemaObject
forall a. Maybe a
Nothing

-- | Resolve a 'OAT.ResponseObject' reference from a 'ReferenceMap'
getResponseReference :: Text -> ReferenceMap -> Maybe OAT.ResponseObject
getResponseReference :: Text -> ReferenceMap -> Maybe ResponseObject
getResponseReference = (ComponentReference -> Maybe ResponseObject)
-> Text -> ReferenceMap -> Maybe ResponseObject
forall a.
(ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup ((ComponentReference -> Maybe ResponseObject)
 -> Text -> ReferenceMap -> Maybe ResponseObject)
-> (ComponentReference -> Maybe ResponseObject)
-> Text
-> ReferenceMap
-> Maybe ResponseObject
forall a b. (a -> b) -> a -> b
$ \case
  ResponseReference ResponseObject
r -> ResponseObject -> Maybe ResponseObject
forall a. a -> Maybe a
Just ResponseObject
r
  ComponentReference
_ -> Maybe ResponseObject
forall a. Maybe a
Nothing

-- | Resolve a 'OAT.ParameterObject' reference from a 'ReferenceMap'
getParameterReference :: Text -> ReferenceMap -> Maybe OAT.ParameterObject
getParameterReference :: Text -> ReferenceMap -> Maybe ParameterObject
getParameterReference = (ComponentReference -> Maybe ParameterObject)
-> Text -> ReferenceMap -> Maybe ParameterObject
forall a.
(ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup ((ComponentReference -> Maybe ParameterObject)
 -> Text -> ReferenceMap -> Maybe ParameterObject)
-> (ComponentReference -> Maybe ParameterObject)
-> Text
-> ReferenceMap
-> Maybe ParameterObject
forall a b. (a -> b) -> a -> b
$ \case
  ParameterReference ParameterObject
r -> ParameterObject -> Maybe ParameterObject
forall a. a -> Maybe a
Just ParameterObject
r
  ComponentReference
_ -> Maybe ParameterObject
forall a. Maybe a
Nothing

-- | Resolve a 'OAT.ExampleObject' reference from a 'ReferenceMap'
getExampleReference :: Text -> ReferenceMap -> Maybe OAT.ExampleObject
getExampleReference :: Text -> ReferenceMap -> Maybe ExampleObject
getExampleReference = (ComponentReference -> Maybe ExampleObject)
-> Text -> ReferenceMap -> Maybe ExampleObject
forall a.
(ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup ((ComponentReference -> Maybe ExampleObject)
 -> Text -> ReferenceMap -> Maybe ExampleObject)
-> (ComponentReference -> Maybe ExampleObject)
-> Text
-> ReferenceMap
-> Maybe ExampleObject
forall a b. (a -> b) -> a -> b
$ \case
  ExampleReference ExampleObject
r -> ExampleObject -> Maybe ExampleObject
forall a. a -> Maybe a
Just ExampleObject
r
  ComponentReference
_ -> Maybe ExampleObject
forall a. Maybe a
Nothing

-- | Resolve a 'OAT.RequestBodyObject' reference from a 'ReferenceMap'
getRequestBodyReference :: Text -> ReferenceMap -> Maybe OAT.RequestBodyObject
getRequestBodyReference :: Text -> ReferenceMap -> Maybe RequestBodyObject
getRequestBodyReference = (ComponentReference -> Maybe RequestBodyObject)
-> Text -> ReferenceMap -> Maybe RequestBodyObject
forall a.
(ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup ((ComponentReference -> Maybe RequestBodyObject)
 -> Text -> ReferenceMap -> Maybe RequestBodyObject)
-> (ComponentReference -> Maybe RequestBodyObject)
-> Text
-> ReferenceMap
-> Maybe RequestBodyObject
forall a b. (a -> b) -> a -> b
$ \case
  RequestBodyReference RequestBodyObject
r -> RequestBodyObject -> Maybe RequestBodyObject
forall a. a -> Maybe a
Just RequestBodyObject
r
  ComponentReference
_ -> Maybe RequestBodyObject
forall a. Maybe a
Nothing

-- | Resolve a 'OAT.HeaderObject' reference from a 'ReferenceMap'
getHeaderReference :: Text -> ReferenceMap -> Maybe OAT.HeaderObject
getHeaderReference :: Text -> ReferenceMap -> Maybe HeaderObject
getHeaderReference = (ComponentReference -> Maybe HeaderObject)
-> Text -> ReferenceMap -> Maybe HeaderObject
forall a.
(ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup ((ComponentReference -> Maybe HeaderObject)
 -> Text -> ReferenceMap -> Maybe HeaderObject)
-> (ComponentReference -> Maybe HeaderObject)
-> Text
-> ReferenceMap
-> Maybe HeaderObject
forall a b. (a -> b) -> a -> b
$ \case
  HeaderReference HeaderObject
r -> HeaderObject -> Maybe HeaderObject
forall a. a -> Maybe a
Just HeaderObject
r
  ComponentReference
_ -> Maybe HeaderObject
forall a. Maybe a
Nothing

-- | Resolve a 'OAT.SecuritySchemeObject' reference from a 'ReferenceMap'
getSecuritySchemeReference :: Text -> ReferenceMap -> Maybe OAT.SecuritySchemeObject
getSecuritySchemeReference :: Text -> ReferenceMap -> Maybe SecuritySchemeObject
getSecuritySchemeReference = (ComponentReference -> Maybe SecuritySchemeObject)
-> Text -> ReferenceMap -> Maybe SecuritySchemeObject
forall a.
(ComponentReference -> Maybe a) -> Text -> ReferenceMap -> Maybe a
createReferenceLookup ((ComponentReference -> Maybe SecuritySchemeObject)
 -> Text -> ReferenceMap -> Maybe SecuritySchemeObject)
-> (ComponentReference -> Maybe SecuritySchemeObject)
-> Text
-> ReferenceMap
-> Maybe SecuritySchemeObject
forall a b. (a -> b) -> a -> b
$ \case
  SecuritySchemeReference SecuritySchemeObject
r -> SecuritySchemeObject -> Maybe SecuritySchemeObject
forall a. a -> Maybe a
Just SecuritySchemeObject
r
  ComponentReference
_ -> Maybe SecuritySchemeObject
forall a. Maybe a
Nothing