{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_HADDOCK not-home #-}
module GraphQL.Internal.Name
( Name(unName, Name)
, NameError(..)
, makeName
, nameFromSymbol
, nameParser
, HasName(..)
, unsafeMakeName
) where
import Protolude
import qualified Data.Aeson as Aeson
import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)
import Data.Char (isDigit)
import Data.Text as T (Text)
import qualified Data.Attoparsec.Text as A
import Test.QuickCheck (Arbitrary(..), elements, listOf)
import Data.String (IsString(..))
import GraphQL.Internal.Syntax.Tokens (tok)
newtype Name = Name { unName :: T.Text } deriving (Eq, Ord, Show)
unsafeMakeName :: HasCallStack => Text -> Name
unsafeMakeName name =
case makeName name of
Left e -> panic (show e)
Right n -> n
makeName :: Text -> Either NameError Name
makeName name = first (const (NameError name)) (A.parseOnly nameParser name)
nameParser :: A.Parser Name
nameParser = Name <$> tok ((<>) <$> A.takeWhile1 isA_z
<*> A.takeWhile ((||) <$> isDigit <*> isA_z))
where
isA_z = A.inClass $ '_' : ['A'..'Z'] <> ['a'..'z']
newtype NameError = NameError Text deriving (Eq, Show)
nameFromSymbol :: forall (n :: Symbol). KnownSymbol n => Either NameError Name
nameFromSymbol = makeName (toS (symbolVal @n Proxy))
class HasName a where
getName :: a -> Name
instance IsString Name where
fromString = unsafeMakeName . toS
instance Aeson.ToJSON Name where
toJSON = Aeson.toJSON . unName
instance Arbitrary Name where
arbitrary = do
initial <- elements alpha
rest <- listOf (elements (alpha <> numeric))
pure (Name (toS (initial:rest)))
where
alpha = ['A'..'Z'] <> ['a'..'z'] <> ['_']
numeric = ['0'..'9']