[web-devel] Type-safe URL handling

Michael Snoyman michael at snoyman.com
Mon Mar 29 13:16:14 EDT 2010


On Mon, Mar 29, 2010 at 9:18 AM, Jeremy Shaw <jeremy at n-heptane.com> wrote:

> On Mon, Mar 29, 2010 at 8:47 AM, Michael Snoyman <michael at snoyman.com>wrote:
>
>> Jeremy,
>>
>
>
>> We'd been discussing the PathInfo class previously; I understand now what
>> you're trying to achieve with it, but I think for a lot of use cases using a
>> parser like that will be unnecesary. For those cases, I'd hate to introduce
>> a parsec dependency, especially given the 2/3 split we're dealing with right
>> now.
>>
>
> Well, parsec is in the haskell platform, so it's really a question of can
> be it implemented so that it works with both 2 and 3.
>
>
>> I know it's painful to split this up into even more packages, but we do
>> you think of adding web-routes-parser?
>>
>
> I would consider it, but I don't understand how that would work...
>
> So far you have been pretty unexcited about the parsing having a type
> similar to:
>
>  [String] -> (Either String a, [String])
>
> because it is often 'unnecessary'. But the problem with, [String] ->
> (Either String a), is that, as far as I can tell, it won't work in the cases
> where it *is* necessary. And you can't simply switch to, [String] -> (Either
> String a, [String]), when that happens.. But perhaps I am missing something
> here..
>
>
The reason I'm unexcited is that I never would have dreamed of defining my
routes that way. I don't feel like drawing out this point too much, because
you clearly *would* define your routes that way. However, just to draw the
distinction in how I would do things differently, I'll use an example of
mine that you quoted earlier:

instance Yesod PB where
    resources = [$mkResources|
/:
    GET: indexHandler
/entries/$entryId:
    GET: entry
/entries/$entryId/$filename:
    GET: media
/feed:
    GET: feed

If I were to convert this to a datatype, it would be:

data PBRoutes = Home | Entry String | File String String | Feed

I simply wouldn't nest a datatype inside any of the constructors. I
understand that you want to do this in some circumstances, but I would
simply "duplicate" the parsing code for the Entry and File constructors,
since I find that parsing code trivial. In particular:

parsePB ["entries", eid] = Entry eid
parsePB ["entries", eid, filename] = File eid filename

I don't see a need for providing a sophisticated parser.

Also, question about the Site datatype: Did we want the formatLink and
>> parseLink functions to work on [String] instead of String? I think I see
>> advantages either way. Also, I'm not sure if I sent this previously, but I
>> think the defaultPage function is redundant; that should probably be
>> specified by 'parseLink ""'.
>>
>
> I will have to think about [String] vs String more. I don't want using Site
> to mean you have to use PathInfo, but switching to [String] wouldn't have
> that effect, and would mean that people would not have to worry about those
> pesky encoding issues. The only real use case I have for not using PathInfo
> is if you wanted to create a function like: gfromPathSegments :: (Data url)
> => PathInfo -> Either String url. And that can be done most easily if
> PathInfo = [String]. So I think I'll make that change. In the old code base,
> where there was no url encoding done by the library, it did not really make
> a difference.
>
> Regarding defaultPage, not all methods of generating formatLink / parseLink
> lend themselves to handling "" very easily, so it seemed nice to have a
> single place that can add that case in, rather than add similar code to all
> the parsers.. Also, I think that I sometimes changes the landing page of my
> site. But I think I will make 'defaultPage' a Maybe value, and bill it as a
> way to 'override' where "" resolves to.
>
>
That sounds good.


> I'll try to get started on porting the Yesod mkResources code over to
>> web-routes now.
>>
>
> awesome!
>
> - jeremy
>
>

So, I've thought about the syntax for this, and I have this idea in mind.

$(createRoutes MyRoutes [$parseRoutes|
/:
  name: Home
  methods: [GET]
/user/#userid:
  name: User
  methods: [GET, PUT, DELETE]
/static:
  name: Static
  subsite: StaticRoutes
  dispatch: staticRoutes
|])

This would generate a datatype:

data MyRoutes = Home | User Integer | Static StaticRoutes

Handler functions would be getHome, getUser, putUser, deleteUser. Static
would be a pluggable subsite; I'd have to play around with the syntax of
that a bit. Also, this will allow *any* type of application, not just wai (I
want this to be as general as possible).

Michael
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/web-devel/attachments/20100329/78e22f42/attachment-0001.html


More information about the web-devel mailing list