summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lehnardt <jan@apache.org>2009-03-04 00:15:07 +0000
committerJan Lehnardt <jan@apache.org>2009-03-04 00:15:07 +0000
commit446735bf7d60831b800275e3bb85251e4d6f8e38 (patch)
treefd4a11092366fec7b2bc1cbf2659645e1d1b4997
parent5ce28dab2a2f803efdc6ae4d1a39e82d758cec57 (diff)
allow for handling 404s in document show functions
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@749852 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--share/www/script/test/show_documents.js306
-rw-r--r--src/couchdb/couch_httpd_db.erl17
-rw-r--r--src/couchdb/couch_httpd_show.erl16
-rw-r--r--src/couchdb/couch_query_servers.erl18
4 files changed, 190 insertions, 167 deletions
diff --git a/share/www/script/test/show_documents.js b/share/www/script/test/show_documents.js
index 4837c18d..24b72739 100644
--- a/share/www/script/test/show_documents.js
+++ b/share/www/script/test/show_documents.js
@@ -21,11 +21,15 @@ couchTests.show_documents = function(debug) {
_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 @@ couchTests.show_documents = function(debug) {
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\".");
};
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 524b52b4..1bd853f3 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -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 @@ db_doc_req(Req, _Db, _DocId) ->
% 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.
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index 09484a3c..475f9ae2 100644
--- a/src/couchdb/couch_httpd_show.erl
+++ b/src/couchdb/couch_httpd_show.erl
@@ -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 @@ handle_doc_show_req(#httpd{
#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 @@ finish_list(Req, Db, QueryServer, Etag, FoldResult, StartListRespFun, TotalRows)
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 @@ send_doc_show_response(Lang, ShowSrc, nil, #httpd{mochi_req=MReq}=Req, Db) ->
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 @@ send_doc_show_response(Lang, ShowSrc, #doc{revs=[DocRev|_]}=Doc,
% 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).
diff --git a/src/couchdb/couch_query_servers.erl b/src/couchdb/couch_query_servers.erl
index a33ffada..4ff69a2d 100644
--- a/src/couchdb/couch_query_servers.erl
+++ b/src/couchdb/couch_query_servers.erl
@@ -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 @@ validate_doc_update(Lang, FunSrc, EditDoc, DiskDoc, Ctx) ->
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 ->