-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement a default way to handle invalid URLs #5
Comments
I think this can be solved by changing the Snake datatype. I'm going to use haskell for this. Disclaimer: I haven't compiled the code below, so I can't say with 100% certainty that it typechecks, but it should. So right now you essentially have: newtype Snake a b = Snake { runSnake :: a -> b }
chain :: Snake a b -> Snake b c -> Snake a c
chain (Snake snakeAB) (Snake snakeBC) = Snake (snakeBC . snakeAB) This is basically just a wrapper around function composition. I think that what you actually want is Either. Consider: data Either r a = Left r | Right a
-- I'm omitting the requisite Functor and Applicative instances because I
-- don't feel like writing them.
instance Monad (Either l) where
(>>=) :: Either r a -> (a -> Either r b) -> Either r b
Left r >>= f = Left r
Right a >>= f = f a
-- For convenience, here's an equivalent to Snake & Chain
type Snake r a b = a -> Either r b
chain :: Snake r a b -> Snake r b c -> Snake r a c
chain snakeAB snakeBC =
\a -> snakeAB a >>= snakeBC
-- Which, when you expand (>>=) looks like
chain snakeAB snakeBC =
\a -> case snakeAB a of
Left r -> Left r
Right b -> snakeBC b
-- And then our identity function
snake :: Snake r a a
snake = \a -> Right a
-- And a way to lift a normal function into a snake
toSnake :: (a -> b) -> Snake r a b
toSnake f = \a -> Right (f a) Now we can allow a chain to terminate early by returning the left value. We'll just leave it as () for now. type Route = Snake () Context Response And let's make a function that lets us combine two snakes, such that:
-- This is actually mappend from monoid, AKA the <> operator AKA concat
concatRoute :: Route -> Route -> Route
concatRoute leftSnake rightSnake =
\context ->
case leftSnake context of
Left _ -> rightSnake context
Right response -> Right response
concatAllRoutes :: [Route] -> Route
concatAllRoutes (r:routes) = concatRoute r (concatAllRoutes routes) Thus, applySnakes can run the concatenated routes, and return a 404 response if none match. The first matching route stops any other routes from running. -- Let's make a basic 404 response, assuming that
-- withResponseCode :: Int -> Response -> Response
-- since I don't know how the library works
do404 :: Context -> Response
do404 context =
withResponseCode
404
(textResponse
("Lol couldn't find " ++ show (uri context)))
applySnakes :: [Route] -> Context -> Response
applySnakes routes =
\context ->
case (concatAllRoutes routes) context of
Left _ -> do404 context
Right response -> response
app :: [Route]
app = [
snake
`chain` bite "GET" "/"
`chain` textResponse "Hello World!"
]
sendResponse :: Response -> IO ()
sendResponse = ???
server = \context -> sendResponse (applySnakes app) This isn't too terrible to translate to typescript, and allows your routes to remain stateless like they are now. I'm not going to fully implement it, but you could go on from here to add termination reasons for things like access control type Route = Snake TerminationReason Context Response
data TerminationReason = NoMatch | Unauthorized
-- Implement this with special handling on Unauthorized
applySnakes :: [Route] -> Context -> Response |
I really like this solution of using Either to return the possibility of an error. There's one issue though: Snake is not a normal function. It's a transformation on an Because of this, Either has to be the type compose(f1: (Observable<A>): Observable<Either<E, B>>, f2: (Observable<B>): Observable<Either<E, C>>) I thought about redefining Snake to be (a: Observable<A>) => f1(a).pipe(flatMap((v: Either<E, B>) => v.chain(f2))) and Snake can still be lifted to a true operator with flatMap. However, this creates a compatibility issue: RxJS operators are all of the type The only other solution I can think of is to abandon |
Hmm, it appears that RxJS allows deep access into the Observables for operators. I may just be possible if I construct Snake like this: https://github.com/ReactiveX/rxjs/blob/master/doc/operator-creation.md#creating-an-operator-for-inclusion-in-this-library |
This is part of fix to #5. + Added new method to Snake interface: map + Added type alias SnakeFunc + Added new method liftSnake + Updated tfSnake method + Update snake method + Added new errorSnake method - Removed compose method
bite
is currently just a normal operator that operates onObservable<Context>
and filters out all requests that don't match the HTTP method and path pattern. However, no mechanism currently exists to obtain requests that haven't matched any filters.This is an issue because as it stands, Snakey has no way to return 404s on requests which do not match any url patterns. The current behavior hangs the connection indefinitely.
I like
bite
being a simple functional operator, and how it is currently used, however, it may be the case thatbites
api and the implementation ofapplySnakes
might need to be adjusted.Ideally the default behavior of snakey should be to obtain unmatched requests and return 404 responses.
The text was updated successfully, but these errors were encountered: