packages feed

json-spec-2.0.0.0: test/jsonlet3.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

module Main (main) where

import Data.JsonSpec
  ( HasJsonEncodingSpec(EncodingSpec), Module(Module)
  , Specification(JsonArray, JsonInt, JsonLet, JsonModule, JsonObject, JsonRef, JsonString)
  , type (:::), type (:=)
  )
import Data.JsonSpec.Codec.Tuple
  ( Field(Field), Ref(Ref), TupleEncoding(toJsonStructure), encode
  )
import Data.Proxy (Proxy(Proxy))
import Data.Text (Text, empty)
import Prelude ((.), (<$>), IO, Int, print)

-- A container polymorphic in its payload spec. Same shape as the library's
-- own Set instance. Compiles.
data LineItem a = LineItem
  { description :: Text
  , quantity :: Int
  , unitPrice :: a
  , lineTotal :: a
  }
instance HasJsonEncodingSpec (LineItem a) where
  type EncodingSpec (LineItem a) =
    'Module
      (JsonObject
      '[ "description" ::: JsonString
       , "quantity" ::: JsonInt
       , "unitPrice" ::: JsonModule (EncodingSpec a)
       , "lineTotal" ::: JsonModule (EncodingSpec a)
       ])
instance (TupleEncoding a) => TupleEncoding (LineItem a) where
  toJsonStructure li =
    ( Field li.description
    , ( Field li.quantity
      , ( Field (toJsonStructure li.unitPrice)
        , ( Field (toJsonStructure li.lineTotal)
          , ()))))

-- Name the LineItem spec in a JsonLet so we can share it within the invoice
-- spec, exactly as the Triangle/Vertex test names Vertex.
-- This will not compile.
data Invoice a = Invoice
  { invoiceNumber :: Text
  , items :: [LineItem a]
  }
instance HasJsonEncodingSpec (Invoice a) where
  type EncodingSpec (Invoice a) =
    'Module
      (JsonLet
      '[ "LineItem" := JsonModule (EncodingSpec (LineItem a)) ]
      (JsonObject
        '[ "invoiceNumber" ::: JsonString
         , "items" ::: JsonArray (JsonRef "LineItem")
         ]))
instance (TupleEncoding a) => TupleEncoding (Invoice a) where
  toJsonStructure inv =
    ( Field inv.invoiceNumber
    , ( Field (Ref . toJsonStructure <$> inv.items)
      , ()))


newtype Money = Money Int
instance HasJsonEncodingSpec Money where
  type EncodingSpec Money  = 'Module (JsonInt)
instance TupleEncoding Money where
  toJsonStructure (Money i) = i


main :: IO ()
main =
  let
    lineItem :: LineItem Money
    lineItem =
      LineItem
        { description = empty
        , quantity = 0
        , unitPrice = Money 0
        , lineTotal = Money 0
        }

    invoice :: Invoice Money
    invoice =
      Invoice
        { invoiceNumber = empty
        , items = [lineItem]
        }
  in
    print
      (encode
        (Proxy @(EncodingSpec (Invoice Money)))
        (toJsonStructure invoice))