packages feed

ron-rdt-0.11: lib/RON/Experimental/Data.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

module RON.Experimental.Data (
    AsAtom (..),
    AsAtoms (..),
    BaseType (..),
    ReplicatedObject (..),
    baseTypeId,
) where

import RON.Prelude

import RON.Error (MonadE, throwErrorText)
import RON.Event (ReplicaClock)
import RON.Store.Class (MonadStore)
import RON.Types (
    Atom (AString, AUuid),
    ObjectRef (..),
    OpenFrame,
    UUID,
 )
import RON.Types.Experimental (Ref (..))
import RON.UUID qualified as UUID

-- | Basic RON type.
data BaseType = LWW | Set

baseTypeId :: BaseType -> UUID
baseTypeId LWW = $(UUID.liftName "lww")
baseTypeId Set = $(UUID.liftName "set")

-- | Any type that may be encoded as a RON object in whole.
class ReplicatedObject a where
    -- | RON representation type
    baseType :: BaseType
    baseType = Set

    encodeObject ::
        (MonadStore m, ReplicaClock m) =>
        -- | Object id
        UUID ->
        a ->
        m ()

    decodeObject ::
        (MonadE m) =>
        -- | Object id
        UUID ->
        -- | Object ops
        OpenFrame ->
        m a

class AsAtom a where
    toAtom :: a -> Atom
    fromAtom :: (MonadE m) => Atom -> m a

instance AsAtom Atom where
    toAtom = id
    fromAtom = pure

instance AsAtom UUID where
    toAtom = AUuid

    fromAtom = \case
        AUuid u -> pure u
        a -> throwErrorText $ "Expected UUID atom, got " <> show a

instance AsAtom Text where
    toAtom = AString

    fromAtom = \case
        AString t -> pure t
        a -> throwErrorText $ "Expected string atom, got " <> show a

class AsAtoms a where
    toAtoms :: a -> [Atom]
    fromAtoms :: (MonadE m) => [Atom] -> m a

instance AsAtoms [Atom] where
    toAtoms = id
    fromAtoms = pure

instance AsAtoms Atom where
    toAtoms a = [a]

    fromAtoms = \case
        [a] -> pure a
        as -> throwErrorText $ "Expected 1 atom, got " <> show (length as)

instance AsAtoms UUID where
    toAtoms u = [AUuid u]

    fromAtoms as = do
        a <- fromAtoms as
        case a of
            AUuid u -> pure u
            _ -> throwErrorText $ "Expected UUID, got " <> show a

instance AsAtoms Text where
    toAtoms a = [toAtom a]
    fromAtoms = fromAtoms @Atom >=> fromAtom @Text

instance AsAtoms (ObjectRef a) where
    toAtoms (ObjectRef uuid) = toAtoms uuid
    fromAtoms = fmap ObjectRef . fromAtoms

instance (AsAtom head, AsAtoms tail) => AsAtoms (head, tail) where
    toAtoms (head, tail) = toAtom head : toAtoms tail

    fromAtoms = \case
        [] -> throwErrorText "Expected some atoms, got none"
        head : tail -> (,) <$> fromAtom head <*> fromAtoms tail

instance AsAtoms (Ref a) where
    toAtoms Ref{object, path} = AUuid object : path

    fromAtoms = \case
        [] -> throwErrorText "Expected some atoms, got none"
        AUuid object : path -> pure Ref{object, path}
        a : _ -> throwErrorText $ "Expected UUID, got " <> show a