packages feed

agentic-0.2.0.2: src/Agentic/Value.hs

-- | A small JSON-shaped value. The core owns this type so that it depends only on
-- @base@ and @text@; provider packages convert to and from their wire libraries.
module Agentic.Value
  ( Value (..)
  , renderJson
  , lookupField
  ) where

import Data.Text (Text)
import qualified Data.Text as T
import Numeric (showHex)

data Value
  = Null
  | Bool Bool
  | Integer Integer
  | Number Double
  | String Text
  | Array [Value]
  | Object [(Text, Value)]
    -- ^ Fields keep their order, so rendering is stable (useful for caching).
  deriving (Eq, Ord, Show)

-- | Render as compact JSON text.
renderJson :: Value -> Text
renderJson = \case
  Null -> "null"
  Bool True -> "true"
  Bool False -> "false"
  Integer n -> T.pack (show n)
  Number d -> T.pack (show d)
  String s -> quote s
  Array vs -> "[" <> T.intercalate "," (map renderJson vs) <> "]"
  Object kvs -> "{" <> T.intercalate "," [quote k <> ":" <> renderJson v | (k, v) <- kvs] <> "}"

quote :: Text -> Text
quote s = "\"" <> concatMapText escape s <> "\""
  where
    escape = \case
      '"' -> "\\\""
      '\\' -> "\\\\"
      '\n' -> "\\n"
      '\r' -> "\\r"
      '\t' -> "\\t"
      c | c < ' ' -> T.pack ("\\u" <> pad (showHex (fromEnum c) ""))
        | otherwise -> T.singleton c
    pad h = replicate (4 - length h) '0' <> h

lookupField :: Text -> [(Text, Value)] -> Maybe Value
lookupField = lookup

-- | 'T.concatMap', which MicroHs's "Data.Text" doesn't provide.
concatMapText :: (Char -> Text) -> Text -> Text
concatMapText f = T.concat . map f . T.unpack