haskellconduitservant

Is it possible to dynamically reconfigure a ConduitT step?


I have a servant server implementing a web socket ticketing system for authorization. I'm using servant-websockets with a Conduit endpoint. I've defined an input message:

data WSInput
  = Auth {token :: String}
  | InMessage {inValue :: Value}
  deriving (Show, Generic)

and since the ticket will just last a couple of seconds, my conduit pipe will just check the Auth message for the first message in. The out message is like:

data WSOutput
  = PoisonPill
  | AuthOK
  | OutMessage {outValue :: Value}
  deriving (Show, Generic)

The pipe could be something like:

    wsConduit =
      mapMC checkTokenExists
        .| takeWhileC
          ( \case
              PoisonPill -> False
              otherwise -> True
          )

The pipe is just an echo after authentication. The checkTokenExists is just needed for the first message and in fact is blocking all InMessage so I cannot leave it there.

I could have some state in the checkTokenExists to let pass InMessages after authorization but to me seems it feels like the best way would be to replace completely the mapMC checkTokenExists by a filterC just letting app messages in.

How could I dynamically change the shape of the conduit flow based on a single element of the inflow?


Solution

  • "Change shape of program based on input" = Monad. And for this reason, ConduitT is a Monad, with operations await and yield. Just say what you mean:

    checkTokenExists :: MonadIO m => WSInput -> m Bool
    
    -- should be along the lines of
    wsConduit = do
      auth <- checkTokenExists <$> await
      if auth then forever $ await >>= yield
              else yield PoisonPill