{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# OPTIONS_GHC -XNoCPP                 #-}

module Data.API.API.DSL
    ( apiAPI
    ) where

import           Data.API.Parse
import           Data.API.Types


-- | API description of the api-tools API itself
apiAPI :: API
apiAPI = [api|

//
// #The api-tools API
//
// Here we make the DSP specs available in JSON so that clients
// working in non-Haskell frameworks can integrate API
// specs into their own environments.

ans :: APISpec
    // an API specification consists of a list of nodes,
    // each describing a named JSON thing which can be
    // referenced in other (object) nodes.
    = [APINode]

an :: APINode
    // Each node is named (with a name that conforms to Haskell's
    // Type identifier rules, starting with a capital letter) and some
    // comment text (like this comment). The JSON spec is all contained
    // in the {Spec}, the other fields being used behind the scenes
    // to manage the Haskell name space and migrations.
    = record
        name      :: string         // the name must start with a big letter
                                    // and conform to Haskell rules for type
                                    // identifiers
        comment   :: string         // natural language description of the node
        prefix    :: string         // (Haskell side only) this prefix used to
                                    // structure the Haskell name space
                                    // (which is uncomfortably flat where
                                    // record field names are concerned);
                                    // it has no effect on the JSON representation
        spec      :: Spec           // here we specify the JSON representation
        convert   :: ? Conversion   // (Haskell side only) sometimes we may
                                    // choose to not use the default internal
                                    // representation for the JSON
                                    // but instead supply a couple of functions
                                    // for injecting the default representation
                                    // into the actual type we will use and the
                                    // and project it back again; here we can check
                                    // some properties (which must be explained
                                    // in the comments) and reject a request
                                    // with a 400 error; has no effect on the
                                    // JSON representation

sp :: Spec
    // the JSON representation is specified as a simple 'newtype', a record
    // type, a union type, an enumerated type or a (mere) type synonym.
    = union
      | newtype   :: SpecNewtype    // here we have a basic string or number type
      | 'record'  :: [Field]        // an object representing a record
      | 'union'   :: [Field]        // an object representing a number of alternatives
      | 'enum'    :: [string]       // a string type which must contain a number of
                                    // discrete values
      | synonym   :: APIType        // is just an alias for another type

sn :: SpecNewtype
    // 'newtype' specs are a basic type and an optional filter specification
    = record
        type      :: BasicType
        filter    :: ? Filter

ft :: Filter
    = union
      | 'string'  :: RegularExpression
      | 'integer' :: IntRange
      | 'utc'     :: UTCRange

re :: RegularExpression
    // regular expressions are represented by simple strings
    = basic string

ir :: IntRange
    // integer ranges are specified with their bounds and may open at
    // the bottom and top
    = record
        lo :: ? integer
        hi :: ? integer

ur :: UTCRange
    // UTC ranges are specified with their bounds and may open at
    // the bottom and top
    = record
        lo :: ? utc
        hi :: ? utc

cv :: Conversion
    // Conversions are just used on the Haskell side to map the concrete JSON
    // representation into an internal type and back again
    = record
        injection :: string         // the injection function for injecting
                                    // representations into the internal
                                    // representation; may return a failure
                                    // indicating that some API precondition
                                    // was not met
        projection:: string         // the projection function converts the
                                    // internal type back into the JSON
                                    // representation for communication
                                    // back to the client

fd :: Field
    // a field represent both a field in a record object and an alternative in
    // a union object (in which exactly one of the 'fields' is ever present in
    // the object)
    = record
        name     :: string          // the name of the method
        type     :: APIType         // the JSON type of the field
        readonly :: boolean         // read-only status
        default  :: ? DefaultValue  // the default value of the field
        comment  :: string          // a comment describing the field (like
                                    // this one)

ty :: APIType
    // this is used to represent JSON types in fields (or synonyms) and is one
    // one of the following:
    = union
      | list   :: APIType           // a JSON list of the given type
      | maybe  :: APIType           // either the given type or the null value
      | ref    :: TypeRef           // a named type (node) with possible example
      | 'basic':: BasicType         // a basic JSON type
      | 'json' :: integer           // a generic JSON value

tr :: TypeRef
    // reference to a type name
    = basic string

bt :: BasicType
    // finally we get down to the basic JSON types ('binary' is a string
    // in which the byte string has been encoded with base-64, safe for
    // embedding in a UTF-8-encoded JSON string
    = enum
        | 'string'
        | 'binary'
        | 'boolean'
        | 'integer'
        | 'utc'

dv :: DefaultValue
    // a default value
    = union
        | 'list'    :: integer
        | 'maybe'   :: integer
        | 'string'  :: string
        | 'boolean' :: boolean
        | 'integer' :: integer
        | 'utc'     :: utc
|]

{-
-}