packages feed

json-autotype-3.1.0: src/Data/Aeson/AutoType/Nested.hs

{-# language DeriveGeneric     #-}
{-# language OverloadedStrings #-}
-- | Simple interface for using AutoType inference
--   in other code generators.
--
--   Simply takes a list of Aeson values,
--   and returns a type description.
--
--   For this type description,
--   we can use function to generate an entire new module.
--
--   Note that while we can put more code in the module,
--   it is recommended to avoid multiple automatically
--   generated types in order to avoid name conflicts.
module Data.Aeson.AutoType.Nested(
    defaultImportedModules
  , generateModuleImports
  , inferType
  , CodeFragment
  , TypeName
  , DeclaredType(..)
  ) where

import Data.Aeson
import Data.Aeson.AutoType.CodeGen.Haskell(generateModuleImports, requiredPackages, importedModules)
import Data.Aeson.AutoType.CodeGen.HaskellFormat(displaySplitTypes)
import Data.Aeson.AutoType.Extract(extractType, unifyTypes)
import Data.Aeson.AutoType.Split(splitTypeByLabel)
import Data.Default
import Data.Typeable
import Data.Text(Text)
import GHC.Generics

-- FIXME: general type to compose generated types
-- move to JSON Autotype as library interface?
-- * API Response Structures
type CodeFragment   = Text
type TypeName       = Text
type ImportedModule = Text
type PackageName    = Text

-- | Type declaration and its requirements
--   Content to embed in an autogenerated module:
--   * name of the type to reference
--   * declarations to describe it
--   * module imports necessary for declarations
--     to work
data DeclaredType = DeclaredType
  {
    -- | Code fragment to be inserted in generated module
    typeCodeFragment    ::  CodeFragment
    -- | Toplevel type name to refer to
  , typeName            ::  TypeName
    -- | List of clauses to add to imports list
  , typeImportedModules :: [ImportedModule]
    -- | List of packages to add to generated package dependencies
  , typePackages        :: [PackageName]
  }
  deriving
    ( Eq
    , Show
    , Generic
    , Typeable
    )


instance Default DeclaredType where
  -- Minimal placeholder to use in case we cannot infer proper type
  def = DeclaredType {
            typeCodeFragment    =  ""
          , typeName            =  "Data.Aeson.Value"
          , typeImportedModules = ["qualified Data.Aeson"]
          , typePackages        = ["aeson"]
          }

-- | List of modules imported for Autotyped declarations
defaultImportedModules = importedModules

-- | Given intended type name, and a list of
--   text fields with JSON, return
--   either an error, or an `EndpointResponse`
--   that allows to declare and use this type
--   in generated module.
inferType :: Text -> [Value] -> DeclaredType
inferType typeName []         = def
inferType typeName jsonValues =
    DeclaredType {
          typeImportedModules = defaultImportedModules
        , typeCodeFragment    = displaySplitTypes splitTypeDescriptors
        , typeName            = typeName
        , typePackages        = requiredPackages
        }
  where
    valueTypes           = map extractType jsonValues
    -- FIXME: should be <> in Typelike?
    unifiedType          = foldr1 unifyTypes valueTypes
    splitTypeDescriptors = splitTypeByLabel typeName unifiedType