[web-devel] Type-safe URL handling

Michael Snoyman michael at snoyman.com
Mon Mar 29 19:17:43 EDT 2010


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

> On Mon, Mar 29, 2010 at 12:16 PM, Michael Snoyman <michael at snoyman.com>wrote:
>
>
>> 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
>>
>
> You do still have nested data-types here. Namely the String. In this case
> it is trivial to handle by hand, but it does pose a problem for things like
> TH and Regular. That is why I had PathInfo in the original code in the first
> place.. I couldn't figure out how to write the TH code with out it.
>
> 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.
>>
>
> If you are going to duplicate the code instead of calling fromPathSegments,
> then you don't really need PathInfo at all, right? The current code is
> designed so that you are not forced to use PathInfo.
>
> Right, I just didn't understand the purpose of PathInfo; you've explained
it very clearly now, thank you.


> We have:
>
> data Site = Site { ...
>                            , parsePathSegments  :: [String] -> Either
> String url
>                            }
>
> And you can do:
>
> Site { parsePathSegments = parsePB }
>
> The only real reason to have PathInfo is to build composable parsers as far
> as I can tell. So, I guess maybe you are suggesting that PathInfo should be
> a separate package? I don't see a big win here since we will depend on
> parsec 2 anyway, and since web-routes-wai would need to depend on it anyway
> to provide the wai related functions that do use PathInfo..
>
> I did add a new parser combinator though:
>
> patternParse :: ([String] -> Either String a) -> URLParser a
>
> so you can do:
>
> fromPathSegments = patternParse parsePB
>
> patternParse consumes all the remaining segments and passes them to
> parsePB.
>
>
>> 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
>>
>
>
> So your idea is to generate the data-type from the routes, rather than try
> to map the routes onto an existing datatype?
>
> Your approach sounds easier. The advantage of the latter is that you could
> change the look of the url with out having to go change all your code that
> uses the URL type.. Not sure how doable the latter is though.
>
> Well, I've started implementing it: it can now generate the data types, but
doesn't do the parsing, building and dispatching functions. Those should be
fairly simple, but I'm just running out of time (Passover seder in a few
hours...). Thought I'd let you have a sneak preview:

http://github.com/snoyberg/web-routes-quasi


> 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).
>>
>
> right. I see no reason for it to be wai specific.
>
> Speaking of wai, there is a bug in wai-extra in SimpleServer. It does not
> put a space between the status code and the status message
>
> ~/n-heptane/projects/haskell/web-routes $ curl -v
> http://localhost:3000/MyHomeoeu
> * About to connect() to localhost port 3000 (#0)
> *   Trying ::1... Connection refused
> *   Trying 127.0.0.1... connected
> * Connected to localhost (127.0.0.1) port 3000 (#0)
> > GET /MyHomeoeu HTTP/1.1
> > User-Agent: curl/7.19.5 (i486-pc-linux-gnu) libcurl/7.19.5 OpenSSL/0.9.8g
> zlib/1.2.3.3 libidn/1.10 libssh2/0.18
> > Host: localhost:3000
> > Accept: */*
> >
> < HTTP/1.1 404Not Found
> * no chunk, no close, no size. Assume close to signal end
>
> Note the second to last line.
>
> - jeremy
>

Thanks for catching that, bug fix is (as you can imagine) just one line;
I'll upload when I have a free moment.

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


More information about the web-devel mailing list