{- | Streams are the fundamental unit of organization of evented, service-oriented systems.
 They are both the storage and the transport of messages in message-based systems.
 And they are the principle storage medium of applicative entity data.

 Streams are created by writing a message to the stream. Messages are appended to the end of streams.
 If the stream doesn't exist when an event is appended to it, the event will be appended at position 0.
 If the stream already exists, the event will be appended at the next position number.

 Read more at: http://docs.eventide-project.org/core-concepts/streams
-}
module MessageDb.StreamName
  ( StreamName (..),
    CategoryName,
    categoryOfStream,
    categoryNameToText,
    categoryName,
    IdentityName (..),
    identityOfStream,
    addIdentityToCategory,
    addMaybeIdentityToCategory,
  )
where

import qualified Data.Aeson as Aeson
import Data.Coerce (coerce)
import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Text as Text
import Prelude hiding (all)


-- | Name of a stream.
newtype StreamName = StreamName
  { StreamName -> Text
streamNameToText :: Text
  }
  deriving (StreamName -> StreamName -> Bool
(StreamName -> StreamName -> Bool)
-> (StreamName -> StreamName -> Bool) -> Eq StreamName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: StreamName -> StreamName -> Bool
$c/= :: StreamName -> StreamName -> Bool
== :: StreamName -> StreamName -> Bool
$c== :: StreamName -> StreamName -> Bool
Eq, Eq StreamName
Eq StreamName
-> (StreamName -> StreamName -> Ordering)
-> (StreamName -> StreamName -> Bool)
-> (StreamName -> StreamName -> Bool)
-> (StreamName -> StreamName -> Bool)
-> (StreamName -> StreamName -> Bool)
-> (StreamName -> StreamName -> StreamName)
-> (StreamName -> StreamName -> StreamName)
-> Ord StreamName
StreamName -> StreamName -> Bool
StreamName -> StreamName -> Ordering
StreamName -> StreamName -> StreamName
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: StreamName -> StreamName -> StreamName
$cmin :: StreamName -> StreamName -> StreamName
max :: StreamName -> StreamName -> StreamName
$cmax :: StreamName -> StreamName -> StreamName
>= :: StreamName -> StreamName -> Bool
$c>= :: StreamName -> StreamName -> Bool
> :: StreamName -> StreamName -> Bool
$c> :: StreamName -> StreamName -> Bool
<= :: StreamName -> StreamName -> Bool
$c<= :: StreamName -> StreamName -> Bool
< :: StreamName -> StreamName -> Bool
$c< :: StreamName -> StreamName -> Bool
compare :: StreamName -> StreamName -> Ordering
$ccompare :: StreamName -> StreamName -> Ordering
$cp1Ord :: Eq StreamName
Ord, String -> StreamName
(String -> StreamName) -> IsString StreamName
forall a. (String -> a) -> IsString a
fromString :: String -> StreamName
$cfromString :: String -> StreamName
IsString, b -> StreamName -> StreamName
NonEmpty StreamName -> StreamName
StreamName -> StreamName -> StreamName
(StreamName -> StreamName -> StreamName)
-> (NonEmpty StreamName -> StreamName)
-> (forall b. Integral b => b -> StreamName -> StreamName)
-> Semigroup StreamName
forall b. Integral b => b -> StreamName -> StreamName
forall a.
(a -> a -> a)
-> (NonEmpty a -> a)
-> (forall b. Integral b => b -> a -> a)
-> Semigroup a
stimes :: b -> StreamName -> StreamName
$cstimes :: forall b. Integral b => b -> StreamName -> StreamName
sconcat :: NonEmpty StreamName -> StreamName
$csconcat :: NonEmpty StreamName -> StreamName
<> :: StreamName -> StreamName -> StreamName
$c<> :: StreamName -> StreamName -> StreamName
Semigroup)
  deriving (Int -> StreamName -> ShowS
[StreamName] -> ShowS
StreamName -> String
(Int -> StreamName -> ShowS)
-> (StreamName -> String)
-> ([StreamName] -> ShowS)
-> Show StreamName
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [StreamName] -> ShowS
$cshowList :: [StreamName] -> ShowS
show :: StreamName -> String
$cshow :: StreamName -> String
showsPrec :: Int -> StreamName -> ShowS
$cshowsPrec :: Int -> StreamName -> ShowS
Show) via Text


instance Aeson.ToJSON StreamName where
  toJSON :: StreamName -> Value
toJSON = Text -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON (Text -> Value) -> (StreamName -> Text) -> StreamName -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StreamName -> Text
streamNameToText
  toEncoding :: StreamName -> Encoding
toEncoding = Text -> Encoding
forall a. ToJSON a => a -> Encoding
Aeson.toEncoding (Text -> Encoding)
-> (StreamName -> Text) -> StreamName -> Encoding
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StreamName -> Text
streamNameToText


instance Aeson.FromJSON StreamName where
  parseJSON :: Value -> Parser StreamName
parseJSON = (Text -> StreamName) -> Parser Text -> Parser StreamName
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> StreamName
StreamName (Parser Text -> Parser StreamName)
-> (Value -> Parser Text) -> Value -> Parser StreamName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Text
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON


separator :: Char
separator :: Char
separator = Char
'-'


{- | A category stream name does not have an ID.
 For example, the stream name for the category of all accounts is "account".
-}
newtype CategoryName = CategoryName Text
  deriving (CategoryName -> CategoryName -> Bool
(CategoryName -> CategoryName -> Bool)
-> (CategoryName -> CategoryName -> Bool) -> Eq CategoryName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: CategoryName -> CategoryName -> Bool
$c/= :: CategoryName -> CategoryName -> Bool
== :: CategoryName -> CategoryName -> Bool
$c== :: CategoryName -> CategoryName -> Bool
Eq, Eq CategoryName
Eq CategoryName
-> (CategoryName -> CategoryName -> Ordering)
-> (CategoryName -> CategoryName -> Bool)
-> (CategoryName -> CategoryName -> Bool)
-> (CategoryName -> CategoryName -> Bool)
-> (CategoryName -> CategoryName -> Bool)
-> (CategoryName -> CategoryName -> CategoryName)
-> (CategoryName -> CategoryName -> CategoryName)
-> Ord CategoryName
CategoryName -> CategoryName -> Bool
CategoryName -> CategoryName -> Ordering
CategoryName -> CategoryName -> CategoryName
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: CategoryName -> CategoryName -> CategoryName
$cmin :: CategoryName -> CategoryName -> CategoryName
max :: CategoryName -> CategoryName -> CategoryName
$cmax :: CategoryName -> CategoryName -> CategoryName
>= :: CategoryName -> CategoryName -> Bool
$c>= :: CategoryName -> CategoryName -> Bool
> :: CategoryName -> CategoryName -> Bool
$c> :: CategoryName -> CategoryName -> Bool
<= :: CategoryName -> CategoryName -> Bool
$c<= :: CategoryName -> CategoryName -> Bool
< :: CategoryName -> CategoryName -> Bool
$c< :: CategoryName -> CategoryName -> Bool
compare :: CategoryName -> CategoryName -> Ordering
$ccompare :: CategoryName -> CategoryName -> Ordering
$cp1Ord :: Eq CategoryName
Ord)
  deriving (Int -> CategoryName -> ShowS
[CategoryName] -> ShowS
CategoryName -> String
(Int -> CategoryName -> ShowS)
-> (CategoryName -> String)
-> ([CategoryName] -> ShowS)
-> Show CategoryName
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [CategoryName] -> ShowS
$cshowList :: [CategoryName] -> ShowS
show :: CategoryName -> String
$cshow :: CategoryName -> String
showsPrec :: Int -> CategoryName -> ShowS
$cshowsPrec :: Int -> CategoryName -> ShowS
Show) via Text


-- | Converts from a 'CategoryName' to a nromal 'Text'.
categoryNameToText :: CategoryName -> Text
categoryNameToText :: CategoryName -> Text
categoryNameToText (CategoryName Text
text) =
  Text
text


{- | Gets the category of a stream.
 For example for "account-123" it would return "account".
-}
categoryOfStream :: StreamName -> CategoryName
categoryOfStream :: StreamName -> CategoryName
categoryOfStream (StreamName Text
text) =
  case (Char -> Bool) -> Text -> [Text]
Text.split (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
separator) Text
text of
    (Text
name : [Text]
_) -> Text -> CategoryName
CategoryName Text
name
    [Text]
_ -> Text -> CategoryName
CategoryName Text
"" -- 'Text.split' never returns an empty list


categoryName :: Text -> CategoryName
categoryName :: Text -> CategoryName
categoryName =
  StreamName -> CategoryName
categoryOfStream (StreamName -> CategoryName)
-> (Text -> StreamName) -> Text -> CategoryName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> StreamName
StreamName


instance Aeson.ToJSON CategoryName where
  toJSON :: CategoryName -> Value
toJSON = Text -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON (Text -> Value) -> (CategoryName -> Text) -> CategoryName -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CategoryName -> Text
categoryNameToText
  toEncoding :: CategoryName -> Encoding
toEncoding = Text -> Encoding
forall a. ToJSON a => a -> Encoding
Aeson.toEncoding (Text -> Encoding)
-> (CategoryName -> Text) -> CategoryName -> Encoding
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CategoryName -> Text
categoryNameToText


instance Aeson.FromJSON CategoryName where
  parseJSON :: Value -> Parser CategoryName
parseJSON = (Text -> CategoryName) -> Parser Text -> Parser CategoryName
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> CategoryName
CategoryName (Parser Text -> Parser CategoryName)
-> (Value -> Parser Text) -> Value -> Parser CategoryName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Text
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON


-- | The identifier part of a stream name. Anything after the first hypen (-).
newtype IdentityName = IdentityName
  { IdentityName -> Text
identityNameToText :: Text
  }
  deriving (IdentityName -> IdentityName -> Bool
(IdentityName -> IdentityName -> Bool)
-> (IdentityName -> IdentityName -> Bool) -> Eq IdentityName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: IdentityName -> IdentityName -> Bool
$c/= :: IdentityName -> IdentityName -> Bool
== :: IdentityName -> IdentityName -> Bool
$c== :: IdentityName -> IdentityName -> Bool
Eq, Eq IdentityName
Eq IdentityName
-> (IdentityName -> IdentityName -> Ordering)
-> (IdentityName -> IdentityName -> Bool)
-> (IdentityName -> IdentityName -> Bool)
-> (IdentityName -> IdentityName -> Bool)
-> (IdentityName -> IdentityName -> Bool)
-> (IdentityName -> IdentityName -> IdentityName)
-> (IdentityName -> IdentityName -> IdentityName)
-> Ord IdentityName
IdentityName -> IdentityName -> Bool
IdentityName -> IdentityName -> Ordering
IdentityName -> IdentityName -> IdentityName
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: IdentityName -> IdentityName -> IdentityName
$cmin :: IdentityName -> IdentityName -> IdentityName
max :: IdentityName -> IdentityName -> IdentityName
$cmax :: IdentityName -> IdentityName -> IdentityName
>= :: IdentityName -> IdentityName -> Bool
$c>= :: IdentityName -> IdentityName -> Bool
> :: IdentityName -> IdentityName -> Bool
$c> :: IdentityName -> IdentityName -> Bool
<= :: IdentityName -> IdentityName -> Bool
$c<= :: IdentityName -> IdentityName -> Bool
< :: IdentityName -> IdentityName -> Bool
$c< :: IdentityName -> IdentityName -> Bool
compare :: IdentityName -> IdentityName -> Ordering
$ccompare :: IdentityName -> IdentityName -> Ordering
$cp1Ord :: Eq IdentityName
Ord)
  deriving (Int -> IdentityName -> ShowS
[IdentityName] -> ShowS
IdentityName -> String
(Int -> IdentityName -> ShowS)
-> (IdentityName -> String)
-> ([IdentityName] -> ShowS)
-> Show IdentityName
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [IdentityName] -> ShowS
$cshowList :: [IdentityName] -> ShowS
show :: IdentityName -> String
$cshow :: IdentityName -> String
showsPrec :: Int -> IdentityName -> ShowS
$cshowsPrec :: Int -> IdentityName -> ShowS
Show) via Text


{- | Gets the identifier of a stream from a 'StreamName'.
 For example "account-ed3b4af7-b4a0-499e-8a16-a09763811274" would return Just "ed3b4af7-b4a0-499e-8a16-a09763811274",
 and "account" would return Nothing.
-}
identityOfStream :: StreamName -> Maybe IdentityName
identityOfStream :: StreamName -> Maybe IdentityName
identityOfStream (StreamName Text
text) =
  let separatorText :: Text
separatorText = String -> Text
Text.pack [Char
separator]
      value :: Text
value = Text -> [Text] -> Text
Text.intercalate Text
separatorText ([Text] -> Text) -> ([Text] -> [Text]) -> [Text] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [Text] -> [Text]
forall a. Int -> [a] -> [a]
drop Int
1 ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> Text -> [Text]
Text.split (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
separator) Text
text
   in if Text -> Bool
Text.null Text
value
        then Maybe IdentityName
forall a. Maybe a
Nothing
        else IdentityName -> Maybe IdentityName
forall a. a -> Maybe a
Just (IdentityName -> Maybe IdentityName)
-> IdentityName -> Maybe IdentityName
forall a b. (a -> b) -> a -> b
$ Text -> IdentityName
IdentityName Text
value


{- | Add an identifier to a 'CategoryName'.
 For example category "account" and identity "123" would return "account-123".
-}
addIdentityToCategory :: CategoryName -> IdentityName -> StreamName
addIdentityToCategory :: CategoryName -> IdentityName -> StreamName
addIdentityToCategory (CategoryName Text
categoryText) IdentityName
identityName =
  Text -> StreamName
StreamName (Text -> StreamName) -> Text -> StreamName
forall a b. (a -> b) -> a -> b
$ Text
categoryText Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Char -> Text
Text.singleton Char
separator Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> IdentityName -> Text
identityNameToText IdentityName
identityName


-- | Add a maybe identifier, allowing you to add an identifier to the stream name if it is Just.
addMaybeIdentityToCategory :: CategoryName -> Maybe IdentityName -> StreamName
addMaybeIdentityToCategory :: CategoryName -> Maybe IdentityName -> StreamName
addMaybeIdentityToCategory CategoryName
categoryText Maybe IdentityName
maybeIdentityName =
  case Maybe IdentityName
maybeIdentityName of
    Maybe IdentityName
Nothing ->
      CategoryName -> StreamName
coerce CategoryName
categoryText
    Just IdentityName
identityName ->
      CategoryName -> IdentityName -> StreamName
addIdentityToCategory CategoryName
categoryText IdentityName
identityName


instance Aeson.ToJSON IdentityName where
  toJSON :: IdentityName -> Value
toJSON = Text -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON (Text -> Value) -> (IdentityName -> Text) -> IdentityName -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IdentityName -> Text
identityNameToText
  toEncoding :: IdentityName -> Encoding
toEncoding = Text -> Encoding
forall a. ToJSON a => a -> Encoding
Aeson.toEncoding (Text -> Encoding)
-> (IdentityName -> Text) -> IdentityName -> Encoding
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IdentityName -> Text
identityNameToText


instance Aeson.FromJSON IdentityName where
  parseJSON :: Value -> Parser IdentityName
parseJSON = (Text -> IdentityName) -> Parser Text -> Parser IdentityName
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> IdentityName
IdentityName (Parser Text -> Parser IdentityName)
-> (Value -> Parser Text) -> Value -> Parser IdentityName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Text
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON