-- | This module exports an OpenAPI specification for the GraphQL Engine
-- metadata API.
--
-- The OpenAPI specification for metadata is experimental and incomplete. Please
-- do not incorporate it into essential workflows at this time.
module Hasura.Server.MetadataOpenAPI (metadataOpenAPI) where

import Autodocodec (HasCodec, JSONCodec)
import Autodocodec.OpenAPI (declareNamedSchemaVia, declareNamedSchemaViaCodec)
import Control.Lens ((.~), (^.))
import Data.Data (Proxy)
import Data.HashMap.Strict.InsOrd qualified as HM
import Data.OpenApi
  ( HasComponents (components),
    HasName (name),
    HasSchema (schema),
    HasSchemas (schemas),
    OpenApi,
  )
import Data.OpenApi qualified as OpenApi
import Data.OpenApi.Declare (undeclare)
import Data.Proxy (Proxy (..))
import Hasura.Backends.BigQuery.Source (BigQueryConnSourceConfig)
import Hasura.Backends.DataConnector.Adapter.Types qualified as DataConnector
import Hasura.Backends.MSSQL.Connection (MSSQLConnConfiguration)
import Hasura.Backends.MySQL.Types qualified as MySQL
import Hasura.Backends.Postgres.Connection.Settings (PostgresConnConfiguration)
import Hasura.Metadata.DTO.Metadata (MetadataDTO)
import Hasura.Metadata.DTO.MetadataV1 (MetadataV1)
import Hasura.Metadata.DTO.MetadataV2 (MetadataV2)
import Hasura.Metadata.DTO.MetadataV3 (MetadataV3)
import Hasura.Prelude
import Hasura.RQL.Types.Metadata.Common
  ( BackendSourceMetadata,
    FunctionMetadata,
    SourceMetadata,
    TableMetadata,
    backendSourceMetadataCodec,
  )
import Hasura.RQL.Types.SourceCustomization (SourceCustomization)
import Hasura.SQL.Backend (BackendType (..), PostgresKind (..))

-- | An OpenApi document includes \"schemas\" that describe the data that may be
-- produced or consumed by an API. It can also include \"paths\" which describe
-- REST endpoints, and the document can include other API metadata. This example
-- only includes schemas.
--
-- Throws an error if any schema listed in 'openApiSchemas' does not have
-- a name.
--
-- The OpenAPI specification for metadata is experimental and incomplete. Please
-- do not incorporate it into essential workflows at this time.
metadataOpenAPI :: OpenApi
metadataOpenAPI :: OpenApi
metadataOpenAPI =
  (OpenApi
forall a. Monoid a => a
mempty :: OpenApi)
    OpenApi -> (OpenApi -> OpenApi) -> OpenApi
forall a b. a -> (a -> b) -> b
& (Components -> Identity Components) -> OpenApi -> Identity OpenApi
forall s a. HasComponents s a => Lens' s a
components ((Components -> Identity Components)
 -> OpenApi -> Identity OpenApi)
-> ((InsOrdHashMap Text Schema
     -> Identity (InsOrdHashMap Text Schema))
    -> Components -> Identity Components)
-> (InsOrdHashMap Text Schema
    -> Identity (InsOrdHashMap Text Schema))
-> OpenApi
-> Identity OpenApi
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (InsOrdHashMap Text Schema -> Identity (InsOrdHashMap Text Schema))
-> Components -> Identity Components
forall s a. HasSchemas s a => Lens' s a
schemas ((InsOrdHashMap Text Schema
  -> Identity (InsOrdHashMap Text Schema))
 -> OpenApi -> Identity OpenApi)
-> InsOrdHashMap Text Schema -> OpenApi -> OpenApi
forall s t a b. ASetter s t a b -> b -> s -> t
.~ [(Text, Schema)] -> InsOrdHashMap Text Schema
forall k v. (Eq k, Hashable k) => [(k, v)] -> InsOrdHashMap k v
HM.fromList (NamedSchema -> (Text, Schema)
applySchemaName (NamedSchema -> (Text, Schema))
-> [NamedSchema] -> [(Text, Schema)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [NamedSchema]
openApiSchemas)

-- | All metadata DTOs should be listed here. Schemas in this list must be
-- named! Some autodocodec combinators apply names for you, like 'object'.
-- Otherwise you can use the 'named' combinator to apply a name.
--
-- As far as I can tell it is necessary to explicitly list all of the data
-- types that should be included in the OpenApi document with their names. It
-- would be nice to provide only a top-level type ('Metadata' in this case), and
-- have all of the types referenced by that type included automatically; but
-- I haven't seen a way to do that.
openApiSchemas :: [OpenApi.NamedSchema]
openApiSchemas :: [NamedSchema]
openApiSchemas =
  [ Proxy MetadataDTO -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy MetadataDTO
forall k (t :: k). Proxy t
Proxy @MetadataDTO),
    Proxy MetadataV1 -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy MetadataV1
forall k (t :: k). Proxy t
Proxy @MetadataV1),
    Proxy MetadataV2 -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy MetadataV2
forall k (t :: k). Proxy t
Proxy @MetadataV2),
    Proxy MetadataV3 -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy MetadataV3
forall k (t :: k). Proxy t
Proxy @MetadataV3),
    JSONCodec BackendSourceMetadata
-> Proxy BackendSourceMetadata -> NamedSchema
forall a. JSONCodec a -> Proxy a -> NamedSchema
toNamedSchemaVia JSONCodec BackendSourceMetadata
backendSourceMetadataCodec (Proxy BackendSourceMetadata
forall k (t :: k). Proxy t
Proxy @BackendSourceMetadata),
    Proxy SourceCustomization -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy SourceCustomization
forall k (t :: k). Proxy t
Proxy @SourceCustomization),
    -- SourceMetadata
    Proxy (SourceMetadata ('Postgres 'Vanilla)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (SourceMetadata ('Postgres 'Vanilla))
forall k (t :: k). Proxy t
Proxy @(SourceMetadata ('Postgres 'Vanilla))),
    Proxy (SourceMetadata ('Postgres 'Citus)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (SourceMetadata ('Postgres 'Citus))
forall k (t :: k). Proxy t
Proxy @(SourceMetadata ('Postgres 'Citus))),
    Proxy (SourceMetadata ('Postgres 'Cockroach)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (SourceMetadata ('Postgres 'Cockroach))
forall k (t :: k). Proxy t
Proxy @(SourceMetadata ('Postgres 'Cockroach))),
    Proxy (SourceMetadata 'MSSQL) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (SourceMetadata 'MSSQL)
forall k (t :: k). Proxy t
Proxy @(SourceMetadata ('MSSQL))),
    Proxy (SourceMetadata 'BigQuery) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (SourceMetadata 'BigQuery)
forall k (t :: k). Proxy t
Proxy @(SourceMetadata ('BigQuery))),
    Proxy (SourceMetadata 'MySQL) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (SourceMetadata 'MySQL)
forall k (t :: k). Proxy t
Proxy @(SourceMetadata ('MySQL))),
    Proxy (SourceMetadata 'DataConnector) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (SourceMetadata 'DataConnector)
forall k (t :: k). Proxy t
Proxy @(SourceMetadata ('DataConnector))),
    -- FunctionMetadata
    Proxy (FunctionMetadata ('Postgres 'Vanilla)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (FunctionMetadata ('Postgres 'Vanilla))
forall k (t :: k). Proxy t
Proxy @(FunctionMetadata ('Postgres 'Vanilla))),
    Proxy (FunctionMetadata ('Postgres 'Citus)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (FunctionMetadata ('Postgres 'Citus))
forall k (t :: k). Proxy t
Proxy @(FunctionMetadata ('Postgres 'Citus))),
    Proxy (FunctionMetadata ('Postgres 'Cockroach)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (FunctionMetadata ('Postgres 'Cockroach))
forall k (t :: k). Proxy t
Proxy @(FunctionMetadata ('Postgres 'Cockroach))),
    Proxy (FunctionMetadata 'MSSQL) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (FunctionMetadata 'MSSQL)
forall k (t :: k). Proxy t
Proxy @(FunctionMetadata ('MSSQL))),
    Proxy (FunctionMetadata 'BigQuery) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (FunctionMetadata 'BigQuery)
forall k (t :: k). Proxy t
Proxy @(FunctionMetadata ('BigQuery))),
    Proxy (FunctionMetadata 'MySQL) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (FunctionMetadata 'MySQL)
forall k (t :: k). Proxy t
Proxy @(FunctionMetadata ('MySQL))),
    Proxy (FunctionMetadata 'DataConnector) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (FunctionMetadata 'DataConnector)
forall k (t :: k). Proxy t
Proxy @(FunctionMetadata ('DataConnector))),
    -- TableMetadata
    Proxy (TableMetadata ('Postgres 'Vanilla)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (TableMetadata ('Postgres 'Vanilla))
forall k (t :: k). Proxy t
Proxy @(TableMetadata ('Postgres 'Vanilla))),
    Proxy (TableMetadata ('Postgres 'Citus)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (TableMetadata ('Postgres 'Citus))
forall k (t :: k). Proxy t
Proxy @(TableMetadata ('Postgres 'Citus))),
    Proxy (TableMetadata ('Postgres 'Cockroach)) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (TableMetadata ('Postgres 'Cockroach))
forall k (t :: k). Proxy t
Proxy @(TableMetadata ('Postgres 'Cockroach))),
    Proxy (TableMetadata 'MSSQL) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (TableMetadata 'MSSQL)
forall k (t :: k). Proxy t
Proxy @(TableMetadata ('MSSQL))),
    Proxy (TableMetadata 'BigQuery) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (TableMetadata 'BigQuery)
forall k (t :: k). Proxy t
Proxy @(TableMetadata ('BigQuery))),
    Proxy (TableMetadata 'MySQL) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (TableMetadata 'MySQL)
forall k (t :: k). Proxy t
Proxy @(TableMetadata ('MySQL))),
    Proxy (TableMetadata 'DataConnector) -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy (TableMetadata 'DataConnector)
forall k (t :: k). Proxy t
Proxy @(TableMetadata ('DataConnector))),
    -- Postgres-specific types
    Proxy PostgresConnConfiguration -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy PostgresConnConfiguration
forall k (t :: k). Proxy t
Proxy @PostgresConnConfiguration),
    -- MSSQL-specific types
    Proxy MSSQLConnConfiguration -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy MSSQLConnConfiguration
forall k (t :: k). Proxy t
Proxy @MSSQLConnConfiguration),
    -- BigQuery-specific types
    Proxy BigQueryConnSourceConfig -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy BigQueryConnSourceConfig
forall k (t :: k). Proxy t
Proxy @BigQueryConnSourceConfig),
    -- MySQL-specific types
    Proxy ConnSourceConfig -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy ConnSourceConfig
forall k (t :: k). Proxy t
Proxy @MySQL.ConnSourceConfig),
    -- DataConnector-specific types
    Proxy ConnSourceConfig -> NamedSchema
forall a. HasCodec a => Proxy a -> NamedSchema
toNamedSchema (Proxy ConnSourceConfig
forall k (t :: k). Proxy t
Proxy @DataConnector.ConnSourceConfig)
  ]

-- | Introspect a given 'OpenApi.NamedSchema' to get its name, and return the
-- name with the unwrapped schema. (NamedSchema wraps a pair of an
-- 'OpenApi.Schema' and an optional name.)
--
-- Throws an exception if the named schema has no name. If this happens to you
-- then use autodocodec's 'named' combinator to apply a name to your codec.
applySchemaName :: OpenApi.NamedSchema -> (Text, OpenApi.Schema)
applySchemaName :: NamedSchema -> (Text, Schema)
applySchemaName NamedSchema
givenSchema = (Text
schemaName, NamedSchema
givenSchema NamedSchema -> Getting Schema NamedSchema Schema -> Schema
forall s a. s -> Getting a s a -> a
^. Getting Schema NamedSchema Schema
forall s a. HasSchema s a => Lens' s a
schema)
  where
    schemaName :: Text
schemaName = case NamedSchema
givenSchema NamedSchema
-> Getting (Maybe Text) NamedSchema (Maybe Text) -> Maybe Text
forall s a. s -> Getting a s a -> a
^. Getting (Maybe Text) NamedSchema (Maybe Text)
forall s a. HasName s a => Lens' s a
name of
      Just Text
n -> Text
n
      Maybe Text
Nothing ->
        [Char] -> Text
forall a. HasCallStack => [Char] -> a
error ([Char] -> Text) -> [Char] -> Text
forall a b. (a -> b) -> a -> b
$
          [Char]
"a codec listed in 'openApiSchemas' does not have a name; "
            [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
"use the 'named' combinator from autodocodec to apply a name "
            [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
"to any codec in that list that does not already have one"

toNamedSchema :: HasCodec a => Proxy a -> OpenApi.NamedSchema
toNamedSchema :: Proxy a -> NamedSchema
toNamedSchema Proxy a
proxy = Declare (InsOrdHashMap Text Schema) NamedSchema -> NamedSchema
forall d a. Monoid d => Declare d a -> a
undeclare (Declare (InsOrdHashMap Text Schema) NamedSchema -> NamedSchema)
-> Declare (InsOrdHashMap Text Schema) NamedSchema -> NamedSchema
forall a b. (a -> b) -> a -> b
$ Proxy a -> Declare (InsOrdHashMap Text Schema) NamedSchema
forall value.
HasCodec value =>
Proxy value -> Declare (InsOrdHashMap Text Schema) NamedSchema
declareNamedSchemaViaCodec Proxy a
proxy

toNamedSchemaVia :: JSONCodec a -> Proxy a -> OpenApi.NamedSchema
toNamedSchemaVia :: JSONCodec a -> Proxy a -> NamedSchema
toNamedSchemaVia JSONCodec a
codec Proxy a
proxy = Declare (InsOrdHashMap Text Schema) NamedSchema -> NamedSchema
forall d a. Monoid d => Declare d a -> a
undeclare (Declare (InsOrdHashMap Text Schema) NamedSchema -> NamedSchema)
-> Declare (InsOrdHashMap Text Schema) NamedSchema -> NamedSchema
forall a b. (a -> b) -> a -> b
$ JSONCodec a
-> Proxy a -> Declare (InsOrdHashMap Text Schema) NamedSchema
forall value.
JSONCodec value
-> Proxy value -> Declare (InsOrdHashMap Text Schema) NamedSchema
declareNamedSchemaVia JSONCodec a
codec Proxy a
proxy