shrub/pkg/hs-urbit/lib/Arvo/Common.hs

189 lines
4.7 KiB
Haskell

module Arvo.Common
( NounTree(..), NounMap, NounSet
, Json, JsonNode(..)
, Desk(..), Mime(..)
, AtomIf, AtomIs, Lane(..), Port(..), Turf(..)
, HttpServerConf(..), HttpEvent(..), PEM, Method, Header
, Routed(..), Reorgd(..), organized, reorg, disorg
) where
import Urbit.Time
import UrbitPrelude hiding (Term)
import qualified Network.HTTP.Types.Method as H
-- Misc Types ------------------------------------------------------------------
type AtomIf = Atom -- @if (TODO: What does this mean?)
type AtomIs = Atom -- @is (TODO: What does this mean?)
-- Domain Name
newtype Turf = Turf { unTurf :: [Cord] }
deriving newtype (Eq, Ord, Show, ToNoun, FromNoun)
-- Http Common -----------------------------------------------------------------
data Header = Header Cord Bytes
deriving (Eq, Ord, Show)
data ResponseHeader = ResponseHeader
{ statusCode :: Word
, headers :: [Header]
}
deriving (Eq, Ord, Show)
data HttpEvent
= Start ResponseHeader (Maybe File) Bool
| Continue (Maybe File) Bool
| Cancel ()
deriving (Eq, Ord, Show)
deriveNoun ''ResponseHeader
deriveNoun ''Header
deriveNoun ''HttpEvent
-- Http Requests ---------------------------------------------------------------
type Method = H.StdMethod
-- TODO Hack! Don't write instances for library types. Write them for
-- our types instead.
instance ToNoun H.StdMethod where
toNoun = toNoun . Cord . decodeUtf8 . H.renderStdMethod
instance FromNoun H.StdMethod where
parseNoun n = named "StdMethod" $ do
Cord m <- parseNoun n
case H.parseMethod (encodeUtf8 m) of
Left bs -> fail ("Unexpected method: " <> unpack (decodeUtf8 bs))
Right m -> pure m
-- Http Server Configuration ---------------------------------------------------
newtype PEM = PEM Cord
deriving newtype (Eq, Ord, Show, ToNoun, FromNoun)
type Key = PEM
type Cert = PEM
data HttpServerConf = HttpServerConf
{ secure :: Maybe (Key, Cert)
, proxy :: Bool
, log :: Bool
, redirect :: Bool
}
deriving (Eq, Ord, Show)
deriveNoun ''HttpServerConf
-- Desk and Mime ---------------------------------------------------------------
newtype Desk = Desk { unDesk :: Cord }
deriving newtype (Eq, Ord, Show, ToNoun, FromNoun)
data Mime = Mime Path File
deriving (Eq, Ord, Show)
deriveNoun ''Mime
-- Trees, Maps, and Sets -------------------------------------------------------
data NounTreeNode a = HTN
{ ntnNode :: a
, ntnLeft :: NounTree a
, ntnRite :: NounTree a
}
deriving (Eq, Ord, Show)
type NounTree a = Nullable (NounTreeNode a)
type NounMap k v = NounTree (k, v)
type NounSet a = NounTree a
deriveNoun ''NounTreeNode
-- Json ------------------------------------------------------------------------
type Json = Nullable JsonNode
data JsonNode
= JNA [Json]
| JNB Bool
| JNO (NounMap Cord Json)
| JNN Knot
| JNS Cord
deriving (Eq, Ord, Show)
deriveNoun ''JsonNode
-- Lanes -----------------------------------------------------------------------
-- Network Port
newtype Port = Port { unPort :: Word }
deriving newtype (Eq, Ord, Show, ToNoun, FromNoun)
data Lane
= If Wen Port AtomIf
| Is Atom (Maybe Lane) AtomIs
| Ix Wen Port AtomIf
deriving (Eq, Ord, Show)
deriveNoun ''Lane
-- Path+Tagged Restructuring ---------------------------------------------------
data Routed = Routed (Cord, Cord, EvilPath) (Cord, Noun)
data Reorgd = Reorgd Cord Cord Cord EvilPath Noun
deriveNoun ''Reorgd
deriveToNoun ''Routed
instance FromNoun Routed where
parseNoun = named "Routed" . \case
A _ -> expected "got atom"
C (A _) _ -> expected "empty route"
C _ (A _) -> expected "value not tagged"
C (C _ (A _)) (C _ _) -> expected "route is too short"
C (C f (C s p)) (C t v) -> do
f <- named "first-route" $ parseNoun f
s <- named "second-route" $ parseNoun s
p <- named "rest-of-route" $ parseNoun p
t <- named "tag" $ parseNoun t
pure (Routed (f, s, p) (t, v))
where
expected got = fail ("expected route+tagged; " <> got)
organized :: Iso' Routed Reorgd
organized = iso to from
where
to = \case Routed (b, r, p) (t, v) -> Reorgd b r t p v
from = \case Reorgd b r t p v -> Routed (b, r, p) (t, v)
reorg :: Routed -> Noun
reorg = toNoun . view organized
{-
This code may crash, but only if the FromNoun/ToNoun instances for
the effects are incorrect.
-}
disorg :: Noun -> Noun
disorg = toNoun . view (from organized) . fromNounCrash
where
fromNounCrash :: FromNoun a => Noun -> a
fromNounCrash =
fromNounErr >>> \case
Left err -> error (show err)
Right vl -> vl