{-# language DataKinds #-} {-# language PolyKinds #-} {-# language TypeFamilies #-} {-# language TypeOperators #-} {-# language UndecidableInstances #-} {-| Description: From 'Schema' to Haskell types. Obtains a 'Schema' from a set of Haskell types. Unfortunately, GHC does not allow type families to appear in instances, so you cannot use the resulting type directly. Instead, evaluate it in an interpreter session using @:kind!@ and copy the result to the file. -} module Mu.Schema.Conversion.TypesToSchema ( SchemaFromTypes , FromType(..) , AsRecord, AsEnum ) where import Data.Kind import Data.Map as M import Data.SOP import GHC.Generics import GHC.TypeLits import Mu.Schema.Definition -- | Defines whether to turn each Haskell type -- into a record or an enumeration. -- Any type not declared in the given list -- of 'FromType's is considered primitive. data FromType tn fn = -- | Declares that the type should become a record. AsRecord' Type tn (Mappings Symbol fn) -- | Declares that the type should become an enumeration. | AsEnum' Type tn (Mappings Symbol fn) -- | Declares that the type should become a record. type AsRecord t tn = 'AsRecord' t tn '[] -- | Declares that the type should become an enumeration. type AsEnum t tn = 'AsEnum' t tn '[] -- | Convert a set of types into a 'Schema'. type family SchemaFromTypes (f :: [FromType tn fn]) :: Schema tn fn where SchemaFromTypes f = SchemaFromTypes' f f type family SchemaFromTypes' (all :: [FromType tn fn]) (f :: [FromType tn fn]) :: Schema tn fn where SchemaFromTypes' all '[] = '[] SchemaFromTypes' all (t ': ts) = TypeDefFromType all t ': SchemaFromTypes' all ts type family TypeDefFromType (all :: [FromType tn fn]) (info :: FromType tn fn) :: TypeDef tn fn where TypeDefFromType all ('AsRecord' t name mp) = 'DRecord name (FieldsFromType all mp (Rep t)) TypeDefFromType all ('AsEnum' t name mp) = 'DEnum name (ChoicesFromType all mp (Rep t)) type family FieldsFromType (all :: [FromType tn fn]) (mp :: Mappings Symbol fn) (f :: * -> *) :: [FieldDef tn fn] where FieldsFromType all mp (x :+: y) = TypeError ('Text "sum types cannot be converted to record schemas") FieldsFromType all mp (D1 meta f) = FieldsFromType all mp f -- go through data info FieldsFromType all mp (C1 meta f) = FieldsFromType all mp f -- go through constructor info FieldsFromType all mp (x :*: y) = ConcatList (FieldsFromType all mp x) (FieldsFromType all mp y) FieldsFromType all mp (S1 ('MetaSel ('Just x) u ss ds) (K1 i t)) = '[ 'FieldDef (MappingRight mp x) (ChooseFieldType all t) ] FieldsFromType all mp v = TypeError ('Text "unsupported conversion from " ':<>: 'ShowType v ':<>: 'Text " to record schema") type family ConcatList (xs :: [k]) (ys :: [k]) :: [k] where ConcatList '[] ys = ys ConcatList (x ': xs) ys = x ': ConcatList xs ys type family ChooseFieldType (all :: [FromType tn fn]) (t :: Type) :: FieldType tn where ChooseFieldType all () = 'TNull ChooseFieldType all (Maybe t) = 'TOption (ChooseFieldType all t) ChooseFieldType all [t] = 'TList (ChooseFieldType all t) ChooseFieldType all (M.Map k v) = 'TMap (ChooseFieldType all k) (ChooseFieldType all v) ChooseFieldType all (NS I choices) = 'TUnion (ChooseFieldUnion all choices) ChooseFieldType all t = ChooseFieldPrimitiveOrSchematic t (FindTypeName all t) type family ChooseFieldUnion (all :: [FromType tn fn]) (t :: [Type]) :: [FieldType tn] where ChooseFieldUnion all '[] = '[] ChooseFieldUnion all (t ': ts) = ChooseFieldType all t ': ChooseFieldUnion all ts type family FindTypeName (all :: [FromType tn fn]) (t :: Type) :: Maybe tn where FindTypeName '[] t = 'Nothing FindTypeName ('AsRecord' t tn mp ': rest) t = 'Just tn FindTypeName ('AsEnum' t tn mp ': rest) t = 'Just tn FindTypeName (other ': rest) t = FindTypeName rest t type family ChooseFieldPrimitiveOrSchematic (t :: Type) (ref :: Maybe tn) :: FieldType tn where ChooseFieldPrimitiveOrSchematic t ('Just name) = 'TSchematic name ChooseFieldPrimitiveOrSchematic t 'Nothing = 'TPrimitive t type family ChoicesFromType (all :: [FromType tn fn]) (mp :: Mappings Symbol fn) (f :: * -> *) :: [ChoiceDef fn] where ChoicesFromType all mp (D1 meta f) = ChoicesFromType all mp f -- go through data info ChoicesFromType all mp (x :+: y) = ConcatList (ChoicesFromType all mp x) (ChoicesFromType all mp y) ChoicesFromType all mp (C1 ('MetaCons cname p s) U1) = '[ 'ChoiceDef (MappingRight mp cname) ] -- go through constructor info ChoicesFromType all mp (C1 ('MetaCons cname p s) f) = TypeError ('Text "constructor " ':<>: 'ShowType cname ':<>: 'Text "has fields and cannot be turned into an enumeration schema") ChoicesFromType all mp v = TypeError ('Text "unsupported conversion from " ':<>: 'ShowType v ':<>: 'Text " to enumeration schema")