Author: jan Date: Wed Mar 4 00:15:07 2009 New Revision: 749852 URL: http://svn.apache.org/viewvc?rev=749852&view=rev Log: allow for handling 404s in document show functions
Modified: couchdb/trunk/share/www/script/test/show_documents.js couchdb/trunk/src/couchdb/couch_httpd_db.erl couchdb/trunk/src/couchdb/couch_httpd_show.erl couchdb/trunk/src/couchdb/couch_query_servers.erl Modified: couchdb/trunk/share/www/script/test/show_documents.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/show_documents.js?rev=749852&r1=749851&r2=749852&view=diff ============================================================================== --- couchdb/trunk/share/www/script/test/show_documents.js (original) +++ couchdb/trunk/share/www/script/test/show_documents.js Wed Mar 4 00:15:07 2009 @@ -21,11 +21,15 @@ _id:"_design/template", language: "javascript", shows: { - "hello" : stringFun(function(doc) { + "hello" : stringFun(function(doc, req) { if (doc) { - return "Hello World"; + return "Hello World"; } else { - return "Empty World"; + if(req.docId) { + return "New World"; + } else { + return "Empty World"; + } } }), "just-name" : stringFun(function(doc, req) { @@ -122,161 +126,167 @@ var xhr = CouchDB.request("GET", "/test_suite_db/_show/"); T(xhr.status == 404); T(JSON.parse(xhr.responseText).reason == "Invalid path."); - + // hello template world xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/"+docid); T(xhr.responseText == "Hello World"); - + // hello template world (no docid) - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello"); + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/"); T(xhr.responseText == "Empty World"); + // // hello template world (non-existing docid) + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/nonExistingDoc"); + T(xhr.responseText == "New World"); // show with doc xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid); T(xhr.responseText == "Just Rusty"); - // show with missing doc - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/missingdoc"); - T(xhr.status == 404); - var resp = JSON.parse(xhr.responseText); - T(resp.error == "not_found"); - T(resp.reason == "missing"); - // show with missing func - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/missing/"+docid); - T(xhr.status == 404); - - // missing design doc - xhr = CouchDB.request("GET", "/test_suite_db/_show/missingdoc/just-name/"+docid); - T(xhr.status == 404); - var resp = JSON.parse(xhr.responseText); - T(resp.error == "not_found"); - - // query parameters - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/req-info/"+docid+"?foo=bar", { - headers: { - "Accept": "text/html;text/plain;*/*", - "X-Foo" : "bar" - } - }); - var resp = JSON.parse(xhr.responseText); - T(equals(resp.headers["X-Foo"], "bar")); - T(equals(resp.query, {foo:"bar"})); - T(equals(resp.verb, "GET")); - T(equals(resp.path[4], docid)); - T(equals(resp.info.db_name, "test_suite_db")); - - // returning a content-type - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/xml-type/"+docid); - T("application/xml" == xhr.getResponseHeader("Content-Type")); - T("Accept" == xhr.getResponseHeader("Vary")); - - // accept header switching - // different mime has different etag - - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, { - headers: {"Accept": "text/html;text/plain;*/*"} - }); - T("text/html" == xhr.getResponseHeader("Content-Type")); - T("Accept" == xhr.getResponseHeader("Vary")); - var etag = xhr.getResponseHeader("etag"); - - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, { - headers: {"Accept": "image/png;*/*"} - }); - T(xhr.responseText.match(/PNG/)) - T("image/png" == xhr.getResponseHeader("Content-Type")); - var etag2 = xhr.getResponseHeader("etag"); - T(etag2 != etag); - - // proper etags - // show with doc - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid); - // extract the ETag header values - etag = xhr.getResponseHeader("etag"); - // get again with etag in request - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { - headers: {"if-none-match": etag} - }); - // should be 304 - T(xhr.status == 304); - - // update the doc - doc.name = "Crusty"; - resp = db.save(doc); - T(resp.ok); - // req with same etag - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { - headers: {"if-none-match": etag} - }); - // status is 200 - T(xhr.status == 200); - - // get new etag and request again - etag = xhr.getResponseHeader("etag"); - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { - headers: {"if-none-match": etag} - }); - // should be 304 - T(xhr.status == 304); - - // update design doc (but not function) - designDoc.isChanged = true; - T(db.save(designDoc).ok); - - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { - headers: {"if-none-match": etag} - }); - // should be 304 - T(xhr.status == 304); - - // update design doc function - designDoc.shows["just-name"] = (function(doc, req) { - return { - body : "Just old " + doc.name - }; - }).toString(); - T(db.save(designDoc).ok); - - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { - headers: {"if-none-match": etag} - }); - // status is 200 - T(xhr.status == 200); - - - // JS can't set etag - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/no-set-etag/"+docid); - // extract the ETag header values - etag = xhr.getResponseHeader("etag"); - T(etag != "skipped") - - // test the respondWith mime matcher - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { - headers: { - "Accept": 'text/html,application/atom+xml; q=0.9' - } - }); - T(xhr.getResponseHeader("Content-Type") == "text/html"); - T(xhr.responseText == "Ha ha, you said \"plankton\"."); - - // now with xml - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { - headers: { - "Accept": 'application/xml' - } - }); - T(xhr.getResponseHeader("Content-Type") == "application/xml"); - T(xhr.responseText.match(/node/)); - T(xhr.responseText.match(/plankton/)); - - // registering types works - xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { - headers: { - "Accept": "application/x-foo" - } - }); - T(xhr.getResponseHeader("Content-Type") == "application/x-foo"); - T(xhr.responseText.match(/foofoo/)); + // show with missing func + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/missing/"+docid); + T(xhr.status == 404); + + // missing design doc + xhr = CouchDB.request("GET", "/test_suite_db/_show/missingdoc/just-name/"+docid); + T(xhr.status == 404); + var resp = JSON.parse(xhr.responseText); + T(resp.error == "not_found"); + + // query parameters + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/req-info/"+docid+"?foo=bar", { + headers: { + "Accept": "text/html;text/plain;*/*", + "X-Foo" : "bar" + } + }); + var resp = JSON.parse(xhr.responseText); + T(equals(resp.headers["X-Foo"], "bar")); + T(equals(resp.query, {foo:"bar"})); + T(equals(resp.verb, "GET")); + T(equals(resp.path[4], docid)); + T(equals(resp.info.db_name, "test_suite_db")); + + // returning a content-type + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/xml-type/"+docid); + T("application/xml" == xhr.getResponseHeader("Content-Type")); + T("Accept" == xhr.getResponseHeader("Vary")); + + // accept header switching + // different mime has different etag + + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, { + headers: {"Accept": "text/html;text/plain;*/*"} + }); + T("text/html" == xhr.getResponseHeader("Content-Type")); + T("Accept" == xhr.getResponseHeader("Vary")); + var etag = xhr.getResponseHeader("etag"); + + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, { + headers: {"Accept": "image/png;*/*"} + }); + T(xhr.responseText.match(/PNG/)) + T("image/png" == xhr.getResponseHeader("Content-Type")); + var etag2 = xhr.getResponseHeader("etag"); + T(etag2 != etag); + + // proper etags + // show with doc + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid); + // extract the ETag header values + etag = xhr.getResponseHeader("etag"); + // get again with etag in request + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // should be 304 + T(xhr.status == 304); + + // update the doc + doc.name = "Crusty"; + resp = db.save(doc); + T(resp.ok); + // req with same etag + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // status is 200 + T(xhr.status == 200); + + // get new etag and request again + etag = xhr.getResponseHeader("etag"); + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // should be 304 + T(xhr.status == 304); + + // update design doc (but not function) + designDoc.isChanged = true; + T(db.save(designDoc).ok); + + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // should be 304 + T(xhr.status == 304); + + // update design doc function + designDoc.shows["just-name"] = (function(doc, req) { + return { + body : "Just old " + doc.name + }; + }).toString(); + T(db.save(designDoc).ok); + + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // status is 200 + T(xhr.status == 200); + + + // JS can't set etag + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/no-set-etag/"+docid); + // extract the ETag header values + etag = xhr.getResponseHeader("etag"); + T(etag != "skipped") + + // test the respondWith mime matcher + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { + headers: { + "Accept": 'text/html,application/atom+xml; q=0.9' + } + }); + T(xhr.getResponseHeader("Content-Type") == "text/html"); + T(xhr.responseText == "Ha ha, you said \"plankton\"."); + + // now with xml + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { + headers: { + "Accept": 'application/xml' + } + }); + T(xhr.getResponseHeader("Content-Type") == "application/xml"); + T(xhr.responseText.match(/node/)); + T(xhr.responseText.match(/plankton/)); + + // registering types works + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { + headers: { + "Accept": "application/x-foo" + } + }); + T(xhr.getResponseHeader("Content-Type") == "application/x-foo"); + T(xhr.responseText.match(/foofoo/)); + + // test the respondWith mime matcher without + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { + headers: { + "Accept": 'text/html,application/atom+xml; q=0.9' + } + }); + T(xhr.getResponseHeader("Content-Type") == "text/html"); + T(xhr.responseText == "Ha ha, you said \"plankton\"."); }; Modified: couchdb/trunk/src/couchdb/couch_httpd_db.erl URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=749852&r1=749851&r2=749852&view=diff ============================================================================== --- couchdb/trunk/src/couchdb/couch_httpd_db.erl (original) +++ couchdb/trunk/src/couchdb/couch_httpd_db.erl Wed Mar 4 00:15:07 2009 @@ -13,7 +13,7 @@ -module(couch_httpd_db). -include("couch_db.hrl"). --export([handle_request/1, db_req/2, couch_doc_open/4]). +-export([handle_request/1, db_req/2, couch_doc_open/4, couch_doc_open/5]). -import(couch_httpd, [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2, @@ -538,20 +538,27 @@ % couch_doc_open(Db, DocId, [], []). couch_doc_open(Db, DocId, Rev, Options) -> + couch_doc_open(Db, DocId, Rev, Options, true). + +couch_doc_open(Db, DocId, Rev, Options, Throw) -> case Rev of "" -> % open most recent rev case couch_db:open_doc(Db, DocId, Options) of {ok, Doc} -> Doc; - Error -> - throw(Error) + Error when Throw -> + throw(Error); + _ -> + nil end; _ -> % open a specific rev (deletions come back as stubs) case couch_db:open_doc_revs(Db, DocId, [Rev], Options) of {ok, [{ok, Doc}]} -> Doc; - {ok, [Else]} -> - throw(Else) + {ok, [Else]} when Throw -> + throw(Else); + {ok, _} -> + nil end end. Modified: couchdb/trunk/src/couchdb/couch_httpd_show.erl URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_show.erl?rev=749852&r1=749851&r2=749852&view=diff ============================================================================== --- couchdb/trunk/src/couchdb/couch_httpd_show.erl (original) +++ couchdb/trunk/src/couchdb/couch_httpd_show.erl Wed Mar 4 00:15:07 2009 @@ -24,14 +24,14 @@ handle_doc_show_req(#httpd{ method='GET', - path_parts=[_, _, DesignName, ShowName, Docid] + path_parts=[_, _, DesignName, ShowName, DocId] }=Req, Db) -> DesignId = <<"_design/", DesignName/binary>>, #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []), Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]), - Doc = couch_httpd_db:couch_doc_open(Db, Docid, [], []), - send_doc_show_response(Lang, ShowSrc, Doc, Req, Db); + Doc = couch_httpd_db:couch_doc_open(Db, DocId, [], [], false), + send_doc_show_response(Lang, ShowSrc, DocId, Doc, Req, Db); handle_doc_show_req(#httpd{ method='GET', @@ -41,7 +41,7 @@ #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []), Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]), - send_doc_show_response(Lang, ShowSrc, nil, Req, Db); + send_doc_show_response(Lang, ShowSrc, nil, nil, Req, Db); handle_doc_show_req(#httpd{method='GET'}=Req, _Db) -> send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>); @@ -257,7 +257,7 @@ throw(Error) end. -send_doc_show_response(Lang, ShowSrc, nil, #httpd{mochi_req=MReq}=Req, Db) -> +send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq}=Req, Db) -> % compute etag with no doc Headers = MReq:get(headers), Hlist = mochiweb_headers:to_list(Headers), @@ -265,12 +265,12 @@ CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, nil, Accept}), couch_httpd:etag_respond(Req, CurrentEtag, fun() -> ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc, - nil, Req, Db), + DocId, nil, Req, Db), JsonResp = apply_etag(ExternalResp, CurrentEtag), couch_httpd_external:send_external_response(Req, JsonResp) end); -send_doc_show_response(Lang, ShowSrc, #doc{revs=[DocRev|_]}=Doc, +send_doc_show_response(Lang, ShowSrc, DocId, #doc{revs=[DocRev|_]}=Doc, #httpd{mochi_req=MReq}=Req, Db) -> % calculate the etag Headers = MReq:get(headers), @@ -280,7 +280,7 @@ % We know our etag now couch_httpd:etag_respond(Req, CurrentEtag, fun() -> ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc, - Doc, Req, Db), + DocId, Doc, Req, Db), JsonResp = apply_etag(ExternalResp, CurrentEtag), couch_httpd_external:send_external_response(Req, JsonResp) end). Modified: couchdb/trunk/src/couchdb/couch_query_servers.erl URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_query_servers.erl?rev=749852&r1=749851&r2=749852&view=diff ============================================================================== --- couchdb/trunk/src/couchdb/couch_query_servers.erl (original) +++ couchdb/trunk/src/couchdb/couch_query_servers.erl Wed Mar 4 00:15:07 2009 @@ -18,7 +18,9 @@ -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]). -export([start_doc_map/2, map_docs/2, stop_doc_map/1]). -export([reduce/3, rereduce/3,validate_doc_update/5]). --export([render_doc_show/5,start_view_list/2,render_list_head/5, render_list_row/4, render_list_tail/3, render_reduce_head/3, render_reduce_row/4]). +-export([render_doc_show/6,start_view_list/2,render_list_head/5, + render_list_row/4, render_list_tail/3, render_reduce_head/3, + render_reduce_row/4]). % -export([test/0]). -include("couch_db.hrl"). @@ -122,14 +124,18 @@ after ok = ret_os_process(Lang, Pid) end. +append_docid(DocId, JsonReqIn) -> + [{<<"docId">>, DocId} | JsonReqIn]. -render_doc_show(Lang, ShowSrc, Doc, Req, Db) -> +render_doc_show(Lang, ShowSrc, DocId, Doc, Req, Db) -> Pid = get_os_process(Lang), - JsonDoc = case Doc of - nil -> null; - _ -> couch_doc:to_json_obj(Doc, [revs]) + {JsonReqIn} = couch_httpd_external:json_req_obj(Req, Db), + + {JsonReq, JsonDoc} = case {DocId, Doc} of + {nil, nil} -> {{JsonReqIn}, null}; + {DocId, nil} -> {{append_docid(DocId, JsonReqIn)}, null}; + _ -> {{append_docid(DocId, JsonReqIn)}, couch_doc:to_json_obj(Doc, [revs])} end, - JsonReq = couch_httpd_external:json_req_obj(Req, Db), try couch_os_process:prompt(Pid, [<<"show_doc">>, ShowSrc, JsonDoc, JsonReq]) of FormResp ->