packages feed

morley-1.19.1: src/Morley/Michelson/Internal/ViewsSet.hs

-- SPDX-FileCopyrightText: 2022 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

{-# OPTIONS_HADDOCK not-home #-}

-- | Internal helper types and functions for manipulating view sets
module Morley.Michelson.Internal.ViewsSet
  ( module Morley.Michelson.Internal.ViewsSet
  ) where

import Control.Monad.Except (throwError)
import Data.Aeson (FromJSON, ToJSON)
import Data.Coerce (coerce)
import Data.Default (Default(..))
import Data.List.NonEmpty qualified as NE (group)
import Data.Map qualified as Map
import Fmt (Buildable(..), (+|), (|+))

import Morley.Michelson.Internal.ViewName

-- | Type for intermediate coercions between typed and untyped view sets.
-- Intended as an internal helper.
newtype ViewsSetF a = ViewsSetF { unViewsSetF :: Map ViewName a }
  deriving newtype (FromJSON, ToJSON, Default, NFData, Container, ToPairs)
  deriving stock (Eq, Show, Functor)

-- | Errors possible when constructing @ViewsSet@.
data ViewsSetError
  = DuplicatedViewName ViewName
  deriving stock (Show, Eq)

instance Buildable ViewsSetError where
  build = \case
    DuplicatedViewName name -> "Duplicated view name '" +| name |+ "'"

-- | Convenience function to construct 'ViewsSetF'.
mkViewsSet :: (a -> ViewName) -> [a] -> Either ViewsSetError (ViewsSetF a)
mkViewsSet viewName views = do
  ensureNoDuplicates $ viewName <$> views
  pure $ ViewsSetF $ fromList $ views <&> \v -> (viewName v, v)

-- | No views.
emptyViewsSet :: ViewsSetF a
emptyViewsSet = def

-- | Add a view to set.
addViewToSet
  :: (a -> ViewName)
  -> a
  -> ViewsSetF a
  -> Either ViewsSetError (ViewsSetF a)
addViewToSet name v views = do
  let viewName = name v
  when (viewName `Map.member` unViewsSetF views) $
    throwError $ DuplicatedViewName viewName
  pure $ coerce (Map.insert viewName v) views

-- | Find a view in the set.
lookupView :: ViewName -> ViewsSetF a -> Maybe a
lookupView name = Map.lookup name . unViewsSetF

-- | Get all taken names in views set.
viewsSetNames :: ViewsSetF a -> Set ViewName
viewsSetNames = Map.keysSet . unViewsSetF

ensureNoDuplicates :: [ViewName] -> Either ViewsSetError ()
ensureNoDuplicates names =
  forM_ (NE.group $ sort names) \case
    name :| _ : _ -> throwError $ DuplicatedViewName name
    _ :| _ -> pass