packages feed

scrod-0.2026.2.21: source/library/Scrod/Json/Pair.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskellQuotes #-}

module Scrod.Json.Pair where

import qualified Data.ByteString.Builder as Builder
import qualified Data.Text as Text
import qualified Scrod.Extra.Builder as Builder
import qualified Scrod.Extra.Parsec as Parsec
import qualified Scrod.Json.String as String
import qualified Scrod.Spec as Spec
import qualified Text.Parsec as Parsec

data Pair a = MkPair
  { name :: String.String,
    value :: a
  }
  deriving (Eq, Ord, Show)

decode :: (Parsec.Stream s m Char) => Parsec.ParsecT s u m a -> Parsec.ParsecT s u m (Pair a)
decode p =
  MkPair
    <$> (String.decode <* Parsec.many Parsec.blank)
    <*> (Parsec.char ':' *> Parsec.many Parsec.blank *> p)

encode :: (a -> Builder.Builder) -> Pair a -> Builder.Builder
encode b p = String.encode (name p) <> Builder.charUtf8 ':' <> b (value p)

spec :: (Applicative m, Monad n) => Spec.Spec m n -> n ()
spec s = do
  let pair :: String -> a -> Pair a
      pair = MkPair . String.MkString . Text.pack

  Spec.named s 'decode $ do
    let p :: (Parsec.Stream t m Char) => Parsec.ParsecT t u m String
        p = Parsec.many1 Parsec.digit

    Spec.it s "succeeds with simple pair" $ do
      Spec.assertEq s (Parsec.parseString (decode p) "\"a\":1") . Just $ pair "a" "1"

    Spec.it s "succeeds with blank space after name" $ do
      Spec.assertEq s (Parsec.parseString (decode p) "\"a\" :1") . Just $ pair "a" "1"

    Spec.it s "succeeds with blank space after separator" $ do
      Spec.assertEq s (Parsec.parseString (decode p) "\"a\": 1") . Just $ pair "a" "1"

    Spec.it s "fails with missing name" $ do
      Spec.assertEq s (Parsec.parseString (decode p) ":1") Nothing

    Spec.it s "fails with missing separator" $ do
      Spec.assertEq s (Parsec.parseString (decode p) "\"a\" 1") Nothing

    Spec.it s "fails with extra separator" $ do
      Spec.assertEq s (Parsec.parseString (decode p) "\"a\"::1") Nothing

    Spec.it s "fails with missing value" $ do
      Spec.assertEq s (Parsec.parseString (decode p) ":1") Nothing

  Spec.named s 'encode $ do
    let b = Builder.integerDec

    Spec.it s "encodes simple pair" $ do
      Spec.assertEq s (Builder.toString . encode b $ pair "a" 1) "\"a\":1"