packages feed

bond-0.11.0.0: src/Language/Bond/Syntax/JSON.hs

-- Copyright (c) Microsoft. All rights reserved.
-- Licensed under the MIT license. See LICENSE file in the project root for full license information.

{-# LANGUAGE OverloadedStrings, QuasiQuotes, RecordWildCards, ScopedTypeVariables, TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

{-|
Copyright   : (c) Microsoft
License     : MIT
Maintainer  : adamsap@microsoft.com
Stability   : provisional
Portability : portable
-}

module Language.Bond.Syntax.JSON
    ( -- * FromJSON and ToJSON instances
      -- $aeson
    )
    where

import Control.Applicative
import Data.Aeson
import Data.Aeson.TH
import Data.Aeson.Types
import Data.HashMap.Strict (member)
import Data.Text.Lazy (unpack)
import Language.Bond.Syntax.Types hiding (MethodType(..))
import qualified Language.Bond.Syntax.Types as BST (MethodType(..))
import Prelude
import Text.Shakespeare.Text (lt)

-- $aeson
--
-- This module defines 'FromJSON' and 'ToJSON' instances for Bond abstract
-- syntax tree.  They allow using the <http://hackage.haskell.org/package/aeson aeson>
-- library to encode Bond AST types to <https://microsoft.github.io/bond/manual/compiler.html#schema-ast JSON format>:
--
-- > > encode (Bond [] [Namespace Nothing ["example"]] [])
-- > "{\"namespaces\":[{\"name\":[\"example\"]}],\"imports\":[],\"declarations\":[]}"
--
-- and decode Bond data types from JSON:
--
-- > > decode "{\"namespaces\":[{\"name\":[\"example\"]}],\"imports\":[],\"declarations\":[]}" :: Maybe Bond
-- > Just (Bond {bondImports = [], bondNamespaces = [Namespace {nsLanguage = Nothing, nsName = ["example"]}], bondDeclarations = []})

instance FromJSON Type where
    parseJSON (String "int8") = pure BT_Int8
    parseJSON (String "int16") = pure BT_Int16
    parseJSON (String "int32") = pure BT_Int32
    parseJSON (String "int64") = pure BT_Int64
    parseJSON (String "uint8") = pure BT_UInt8
    parseJSON (String "uint16") = pure BT_UInt16
    parseJSON (String "uint32") = pure BT_UInt32
    parseJSON (String "uint64") = pure BT_UInt64
    parseJSON (String "float") = pure BT_Float
    parseJSON (String "double") = pure BT_Double
    parseJSON (String "bool") = pure BT_Bool
    parseJSON (String "string") = pure BT_String
    parseJSON (String "wstring") = pure BT_WString
    parseJSON (String "bond_meta::name") = pure BT_MetaName
    parseJSON (String "bond_meta::full_name") = pure BT_MetaFullName
    parseJSON (String "blob") = pure BT_Blob
    parseJSON (Object o) = do
        type_ <- o .: "type"
        case type_ of
            String "maybe" -> BT_Maybe <$>
                o .: "element"
            String "list" -> BT_List <$>
                o .: "element"
            String "vector" -> BT_Vector <$>
                o .: "element"
            String "nullable" -> BT_Nullable <$>
                o .: "element"
            String "set" -> BT_Set <$>
                o .: "element"
            String "map" -> BT_Map <$>
                o .: "key" <*>
                o .: "element"
            String "bonded" -> BT_Bonded <$>
                o .: "element"
            String "constant" -> BT_IntTypeArg <$>
                o .: "value"
            String "parameter" -> BT_TypeParam <$>
                o .: "value"
            String "user" -> BT_UserDefined <$>
                o .: "declaration" <*>
                o .:? "arguments" .!= []
            _ -> modifyFailure
                    (const $ "Invalid value `" ++ show type_ ++ "` for the `type` key.")
                    empty
    parseJSON x = modifyFailure
                    (const $ "Expected a representation of Type but found: " ++ show x)
                    empty

instance ToJSON Type where
    toJSON BT_Int8 = "int8"
    toJSON BT_Int16 = "int16"
    toJSON BT_Int32 = "int32"
    toJSON BT_Int64 = "int64"
    toJSON BT_UInt8 = "uint8"
    toJSON BT_UInt16 = "uint16"
    toJSON BT_UInt32 = "uint32"
    toJSON BT_UInt64 = "uint64"
    toJSON BT_Float = "float"
    toJSON BT_Double = "double"
    toJSON BT_Bool = "bool"
    toJSON BT_String = "string"
    toJSON BT_WString = "wstring"
    toJSON BT_MetaName = "bond_meta::name"
    toJSON BT_MetaFullName = "bond_meta::full_name"
    toJSON BT_Blob = "blob"
    toJSON (BT_Maybe t) = object
        [ "type" .= String "maybe"
        , "element" .= t
        ]
    toJSON (BT_List t) = object
        [ "type" .= String "list"
        , "element" .= t
        ]
    toJSON (BT_Vector t) = object
        [ "type" .= String "vector"
        , "element" .= t
        ]
    toJSON (BT_Nullable t) = object
        [ "type" .= String "nullable"
        , "element" .= t
        ]
    toJSON (BT_Set t) = object
        [ "type" .= String "set"
        , "element" .= t
        ]
    toJSON (BT_Map k t) = object
        [ "type" .= String "map"
        , "key" .= k
        , "element" .= t
        ]
    toJSON (BT_Bonded t) = object
        [ "type" .= String "bonded"
        , "element" .= t
        ]
    toJSON (BT_IntTypeArg n) = object
        [ "type" .= String "constant"
        , "value" .= n
       ]
    toJSON (BT_TypeParam p) = object
        [ "type" .= String "parameter"
        , "value" .= p
        ]
    toJSON (BT_UserDefined decl []) = object
        [ "type" .= String "user"
        , "declaration" .= decl
        ]
    toJSON (BT_UserDefined decl args) = object
        [ "type" .= String "user"
        , "declaration" .= decl
        , "arguments" .= args
        ]

instance FromJSON Default where
    parseJSON (Object o) = do
        type_ <- o .: "type"
        case type_ of
            String "bool" -> DefaultBool <$> o .: "value"
            String "integer" -> DefaultInteger <$> o .: "value"
            String "float" -> DefaultFloat <$> o .: "value"
            String "string" -> DefaultString <$> o .: "value"
            String "enum" -> DefaultEnum <$> o .: "value"
            String "nothing" -> pure DefaultNothing
            _ -> modifyFailure
                    (const $ "Invalid value `" ++ show type_ ++ "` for the `type` key.")
                    empty
    parseJSON x = modifyFailure
                    (const $ "Expected a representation of Default but found: " ++ show x)
                    empty

instance ToJSON Default where
    toJSON (DefaultBool x) = object
        [ "type" .= String "bool"
        , "value" .= x
        ]
    toJSON (DefaultInteger x) = object
        [ "type" .= String "integer"
        , "value" .= x
        ]
    toJSON (DefaultFloat x) = object
        [ "type" .= String "float"
        , "value" .= x
        ]
    toJSON (DefaultString x) = object
        [ "type" .= String "string"
        , "value" .= x
        ]
    toJSON (DefaultEnum x) = object
        [ "type" .= String "enum"
        , "value" .= x
        ]
    toJSON DefaultNothing = object
        [ "type" .= String "nothing"
        ]

instance FromJSON Field where
    parseJSON (Object o) = Field <$>
        o .:? "fieldAttributes" .!= [] <*>
        o .:  "fieldOrdinal" <*>
        o .:? "fieldModifier" .!= Optional <*>
        o .:  "fieldType" <*>
        o .:  "fieldName" <*>
        o .:? "fieldDefault" .!= Nothing
    parseJSON x = modifyFailure
                    (const $ "Expected a representation of Field but found: " ++ show x)
                    empty

instance ToJSON Field where
    toJSON f = object
        [ "fieldAttributes" .= fieldAttributes f
        , "fieldOrdinal" .= fieldOrdinal f
        , "fieldModifier" .= fieldModifier f
        , "fieldType" .= fieldType f
        , "fieldName" .= fieldName f
        , "fieldDefault" .= fieldDefault f
        ]

instance FromJSON Constraint where
    parseJSON (String "value") = pure Value
    parseJSON x = modifyFailure
                    (const $ "Expected a representation of Constraint but found: " ++ show x)
                    empty

instance ToJSON Constraint where
    toJSON Value = "value"

instance FromJSON Namespace where
    parseJSON (Object v) =
        Namespace <$>
            v .:? "language" <*>
            v .: "name"
    parseJSON x = modifyFailure
                    (const $ "Expected an object but found: " ++ show x)
                    empty

instance ToJSON Namespace where
    toJSON (Namespace Nothing name) = object
        [ "name" .= name
        ]
    toJSON Namespace {..} = object
        [ "language" .= nsLanguage
        , "name" .= nsName
        ]

instance FromJSON Bond where
    parseJSON (Object v) =
        Bond <$>
            v .: "imports" <*>
            v .: "namespaces" <*>
            v .: "declarations"
    parseJSON x = modifyFailure
                    (const $ "Expected an object but found: " ++ show x)
                    empty

instance ToJSON Bond where
    toJSON Bond {..} = object
        [ "imports" .= bondImports
        , "namespaces" .= bondNamespaces
        , "declarations" .= bondDeclarations
        ]

instance ToJSON BST.MethodType where
    toJSON BST.Void = Null
    toJSON (BST.Unary t) = toJSON t
    toJSON (BST.Streaming t) = toJSON t

data MethodStreamingTag = Unary | Client | Server | Duplex deriving Show
$(deriveJSON defaultOptions ''MethodStreamingTag)

methodStreamingTag :: BST.MethodType -> BST.MethodType -> MethodStreamingTag
methodStreamingTag input result = case (input, result) of
  (BST.Streaming _, BST.Streaming _) -> Duplex
  (BST.Streaming _, _) -> Client
  (_, BST.Streaming _) -> Server
  _ -> Unary

instance ToJSON Method where
    toJSON Event {..} = object
        [ "tag" .= String "Event"
        , "methodName" .= methodName
        , "methodAttributes" .= methodAttributes
        , "methodInput" .= methodInput
        ]
    toJSON Function {..} = object
        [ "tag" .= String "Function"
        , "methodName" .= methodName
        , "methodAttributes" .= methodAttributes
        , "methodResult" .= methodResult
        , "methodInput" .= methodInput
        , "methodStreaming" .= (methodStreamingTag methodInput methodResult)
        ]

instance FromJSON Method where
  parseJSON = withObject "Method" (\o -> do
    tag <- o .: "tag"
    methodName :: String <- o .:? "methodName" .!= "<unknown>"
    case tag of
      (String "Event") -> modifyFailure ((unpack [lt|Parsing event '#{show methodName}' failed: |]) ++) (parseEvent o)
      (String "Function") -> modifyFailure ((unpack [lt|Parsing function '#{show methodName}' failed: |]) ++) (parseFunction o)
      _ -> modifyFailure (const $ unpack [lt|Unexpected tag '#{show tag}' when parsing method '#{show methodName}'. Expecting "Event" or "Function".|]) empty)
    where
      parseEvent :: Object -> Parser Method
      parseEvent o =
        Event <$>
          o .:? "methodAttributes" .!= [] <*>
          o .: "methodName" <*>
          methodInput
        <* ensureNoMethodStreaming
        where ensureNoMethodStreaming = if member "methodStreaming" o
                                          then fail "Encountered Event with \"methodStreaming\" member. Events cannot have this member."
                                          else pure ()
              methodInput = maybe BST.Void BST.Unary <$> (o .:? "methodInput")

      parseFunction :: Object -> Parser Method
      parseFunction o =
          Function <$>
            o .:? "methodAttributes" .!= [] <*>
            methodResult <*>
            o .: "methodName" <*>
            methodInput
        where
          streamingTag :: Parser MethodStreamingTag
          streamingTag = o .:? "methodStreaming"  .!= Unary

          methodInput :: Parser BST.MethodType
          methodInput = do
            i <- o .:? "methodInput"
            st <- streamingTag
            case (i, st) of
              (Nothing, Unary) -> pure $ BST.Void
              (Nothing, Client) -> fail $ invalidNothingComboMsg "input" Client
              (Nothing, Server) -> pure $ BST.Void
              (Nothing, Duplex) -> fail $ invalidNothingComboMsg "input" Duplex
              (Just t, Unary) -> pure $ BST.Unary t
              (Just t, Client) -> pure $ BST.Streaming t
              (Just t, Server) -> pure $ BST.Unary t
              (Just t, Duplex) -> pure $ BST.Streaming t

          methodResult :: Parser BST.MethodType
          methodResult = do
            r <- o .:? "methodResult"
            st <- streamingTag
            case (r, st) of
              (Nothing, Unary) -> pure $ BST.Void
              (Nothing, Client) -> pure $ BST.Void
              (Nothing, Server) -> fail $ invalidNothingComboMsg "result" Server
              (Nothing, Duplex) -> fail $ invalidNothingComboMsg "result" Duplex
              (Just t, Unary) -> pure $ BST.Unary t
              (Just t, Client) -> pure $ BST.Unary t
              (Just t, Server) -> pure $ BST.Streaming t
              (Just t, Duplex) -> pure $ BST.Streaming t

          invalidNothingComboMsg :: String -> MethodStreamingTag -> String
          invalidNothingComboMsg dir streaming = unpack [lt|Method marked as #{show streaming}, but has void #{dir}|]

$(deriveJSON defaultOptions ''Modifier)
$(deriveJSON defaultOptions ''Attribute)
$(deriveJSON defaultOptions ''Constant)
$(deriveJSON defaultOptions ''TypeParam)
$(deriveJSON defaultOptions ''Declaration)
$(deriveJSON defaultOptions ''Import)
$(deriveJSON defaultOptions ''Language)