Skip to content

Add end' combinator to support trailing slashes #3

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

paulyoung
Copy link
Contributor

As discussed on Slack. I figured it was a pretty common use case.

| Foo
| Bar String Int String { a :: String, b :: Boolean }
| Baz { id :: String, search :: String }
| Qux String (Array String)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally put made Qux nullary and put it at the bottom but quickcheck was sometimes generating "qux" which would cause the tests to fail.

I know the order doesn't matter here but it does when using sum, and I thought it was best to keep them the same.

@@ -81,6 +82,10 @@ root = path ""
end :: forall a b. RouteDuplex a b -> RouteDuplex a b
end (RouteDuplex enc dec) = RouteDuplex enc (dec <* Parser.end)

-- Like `end`, but matches an optional trailing slash when parsing. The slash is omitted when printing.
end' :: forall a b. RouteDuplex a b -> RouteDuplex a b
end' (RouteDuplex enc dec) = RouteDuplex enc (dec <* (Parser.end <|> (Parser.prefix "" Parser.end)))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm proposing end' instead of index since it's kind of like end/

}
where
fooRoute =
segment / int segment / segment ? { a: string, b: flag }
path "qux" noArgs # end'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be “foo”

@paulyoung
Copy link
Contributor Author

paulyoung commented Oct 26, 2018

I don’t think this actually tests that end’ behaves as expected with trailing slashes but I’m not sure how to test that either current setup.

I could write some manual test cases (e.g. //, ///, /foo, /foo/, /foo//, /foo///) but quickcheck might do a better job.

@natefaubion
Copy link
Owner

Something to also consider is if you want to canonicalize to no trailing slash, you could use existing combinators with a pre-processing step which trims empty segments (via parsePath and runRouteParser).

runRouteParser :: forall a. RouteState -> RouteParser a -> RouteResult a
runRouteParser = go
where
go state = case _ of
Alt xs -> foldl (goAlt state) (Fail EndOfPath) xs
Chomp f -> f state
Prefix pre p ->
case chompPrefix pre state of
Fail err -> Fail err
Success state' _ -> go state' p
goAlt state (Fail _) p = runRouteParser state p
goAlt _ res _ = res
parsePath :: String -> RouteState
parsePath =
splitAt (flip Tuple "") "#"
>>> lmap splitPath
>>> toRouteState
where
splitPath =
splitAt (flip Tuple "") "?"
>>> bimap splitSegments splitParams
splitSegments = splitNonEmpty (Pattern "/") >>> case _ of
["", ""] -> [""]
xs -> map unsafeDecodeURIComponent xs
splitParams =
splitNonEmpty (Pattern "&") >>> map splitKeyValue
splitKeyValue =
splitAt (flip Tuple "") "=" >>> bimap unsafeDecodeURIComponent unsafeDecodeURIComponent
splitNonEmpty _ "" = []
splitNonEmpty p s = split p s
toRouteState (Tuple (Tuple segments params) h) =
{ segments, params, hash: h }
splitAt k p str =
case String.indexOf (Pattern p) str of
Just ix -> Tuple (String.take ix str) (String.drop (ix + String.length p) str)
Nothing -> k str

@paulyoung
Copy link
Contributor Author

It’s not clear to me how to do that, unless you’re suggesting changing the implementation of those functions.

@natefaubion
Copy link
Owner

Parsing is just the composition of these two functions

run :: forall a. RouteParser a -> String -> Either RouteError a
run p = parsePath >>> flip runRouteParser p >>> case _ of
Fail err -> Left err
Success _ res -> Right res

I'm saying that it's possible to define your own version of this that just inserts a normalization step between the two, removing trailing ""s from the RouteState.

@natefaubion
Copy link
Owner

natefaubion commented Nov 27, 2018

After thinking about this some more, I think that a preprocessing/normalization stage is the better approach. That is, I think that the combinators provided by the library should be bidirectional, and I don't really like the idea of exposing a combinator with a clear bias. Enough guts are exposed that it's very easy to implement your own bias if that's what you want. I would think that whether or not to accept a trailing slash is something you'd want to do across the board though, which benefits from a preprocessing step, and is something we could make easily configurable. Some options could be whether to remove duplicate slashes, or whether to add/remove trailing/leading slashes. Is this something you'd be interested in doing? Do you have a strong use case for ad-hoc slash handling?

@paulyoung
Copy link
Contributor Author

I don't have a use case for ad-hoc slash handling, and would be interested in working on preprocessing/normalization although I'm not sure when I'd get around to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants