I’m aware of this StackOverflow question but my question goes one step beyond what’s asked there.
I’m trying to model authentication in a web app in a way that I can have functions that require a user to be an admin
scaryStuff :: ??? -> IO ()
scaryStuff (Admin UserSession {..}) = undefined
but at the same time I need functions that can return a type that’s either an admin, a normal user, or not logged in at all.
login :: IO ???
login = do
userid <- login
userSession <- getUserSession userid
userRoles <- getUserRoles userid
if hasAdmin userRoles then Admin userSession
else User userSession
The way I’m handling this right now is by stacking lots and lots of types inside one another.
data Auth = None | Authenticated
data Authenticated = User UserSession | Admin
newtype Admin = Admin UserSession
which let’s me write functions that can work with any kind of auth foo :: Auth -> IO ()
or restricted to specific types of auth bar :: Admin -> IO ()
So what have I tried so far? It’s easy enough to do something with DataKinds
:
data AuthStatus
= None
| AuthUser
| AuthAdmin
deriving (Show, Eq)
data Auth s a where
NotAuthenticated :: Auth None a
IsUser :: UserSession -> Auth AuthUser UserSession
IsAdmin :: UserSession -> Auth AuthAdmin UserSession
But this only achieves the first part, namely restricting functions to certain inputs. This doesn’t let me write a function that either returns or accepts just a general Auth
thing, without immediately restricting it to just one variation.
Summary
I need a single type that can act both as a normal ADT but where I can also restrict functions to just a single constructor. I can achieve this by wrapping types inside another but that’s ugly. I’m wondering if there’s something else out there that I’m just not aware of.