[web-devel] Type-safe URL handling

Michael Snoyman michael at snoyman.com
Thu Apr 1 14:19:54 EDT 2010


Minor update: I think that YAML syntax for these kinds of routes is a little
bit verbose; any thoughts on this syntax:

/                    Home       GET
/user/#userid        User       GET PUT DELETE
/static              Static     StaticRoutes staticRoutes
/foo/*slurp          Foo
/bar/$barparam       Bar

First column is the pattern, second is the constructor name, and after that
you have three possibilities:

Nothing is a handler function for any request method. Above, the fourth and
fifth entries.
A list of request methods will allow a handler function for each request
method. Above, the first and second entries.
A datatype and function name, allowing a subsite datatype and subsite
function. Above,the third entry. I'll need to develop this one a bit more.

Michael

On Mon, Mar 29, 2010 at 4:17 PM, Michael Snoyman <michael at snoyman.com>wrote:

>
>
> 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/20100401/f97244f3/attachment.html


More information about the web-devel mailing list