Changed record keys to use lists instead of atoms.

Using atoms will not scale in the large. Also, using atoms as keys forced the
API to convert arbitrary end-user input to atoms, adding another potential
drain of the finite atom-space available.

- ts_api: url paths are now treated and matched as lists, not atoms.
  Usernames and API calls all use lists now.
- ts_json: key items (username, timeline ids) are now expected to be lists,
  not atoms.
This commit is contained in:
Jonathan Bernard 2011-05-03 12:17:00 -05:00
parent 5b267ec67b
commit 9b3f587974
2 changed files with 52 additions and 51 deletions

View File

@ -34,19 +34,18 @@ out(YArg) ->
dispatch_request(YArg, _Session, []) -> make_json_404(YArg, [{see_docs, "/ts_api_doc"}]);
dispatch_request(YArg, Session, [H|T]) ->
Param = path_element_to_atom(H),
case {Session, Param} of
{_, login} -> do_login(YArg);
{_, logout} -> do_logout(YArg);
case {Session, H} of
{_, "login"} -> do_login(YArg);
{_, "logout"} -> do_logout(YArg);
{not_logged_in, _} -> make_json_401(YArg);
{session_expired, _} -> make_json_401(YArg, [{error, "session expired"}]);
{"not_logged_in", _} -> make_json_401(YArg);
{"session_expired", _} -> make_json_401(YArg, [{error, "session expired"}]);
{_S, app} -> dispatch_app(YArg, Session, T);
{_S, users} -> dispatch_user(YArg, Session, T);
{_S, timelines} -> dispatch_timeline(YArg, Session, T);
{_S, entries} -> dispatch_entry(YArg, Session, T);
{_S, "app"} -> dispatch_app(YArg, Session, T);
{_S, "users"} -> dispatch_user(YArg, Session, T);
{_S, "timelines"} -> dispatch_timeline(YArg, Session, T);
{_S, "entries"} -> dispatch_entry(YArg, Session, T);
{_S, _Other} -> make_json_404(YArg, [{see_docs, "/ts_api_doc/"}])
end.
@ -59,7 +58,7 @@ dispatch_app(YArg, Session, Params) ->
{'GET', ["user_summary", UsernameStr]} ->
case {Session#ts_api_session.username,
path_element_to_atom(UsernameStr)} of
UsernameStr} of
{Username, Username} -> get_user_summary(YArg, Username);
_ -> make_json_401(YArg)
@ -75,10 +74,9 @@ dispatch_app(YArg, Session, Params) ->
% -------- Dispatch for /user -------- %
dispatch_user(YArg, Session, []) ->
dispatch_user(YArg, Session, [atom_to_list(Session#ts_api_session.username)]);
dispatch_user(YArg, Session, [Session#ts_api_session.username]);
dispatch_user(YArg, Session, [H]) ->
Username = path_element_to_atom(H),
dispatch_user(YArg, Session, [Username]) ->
HTTPMethod = (YArg#arg.req)#http_request.method,
% compare to the logged-in user
@ -97,8 +95,7 @@ dispatch_user(YArg, Session, [H]) ->
dispatch_timeline(YArg, _Session, []) ->
make_json_404(YArg, [{see_docs, "/ts_api_doc/timelines.html"}]);
dispatch_timeline(YArg, Session, [UrlUsername|_T] = PathElements) ->
Username = path_element_to_atom(UrlUsername),
dispatch_timeline(YArg, Session, [Username|_T] = PathElements) ->
case Session#ts_api_session.username of
Username -> dispatch_timeline(YArg, PathElements);
@ -106,8 +103,7 @@ dispatch_timeline(YArg, Session, [UrlUsername|_T] = PathElements) ->
end.
% just username, list timelines
dispatch_timeline(YArg, [UrlUsername]) ->
Username = path_element_to_atom(UrlUsername),
dispatch_timeline(YArg, [Username]) ->
HTTPMethod = (YArg#arg.req)#http_request.method,
case HTTPMethod of
@ -115,9 +111,7 @@ dispatch_timeline(YArg, [UrlUsername]) ->
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/timelines.html"}])
end;
dispatch_timeline(YArg, [UrlUsername, UrlTimelineId]) ->
Username = path_element_to_atom(UrlUsername),
TimelineId = path_element_to_atom(UrlTimelineId),
dispatch_timeline(YArg, [Username, TimelineId]) ->
HTTPMethod = (YArg#arg.req)#http_request.method,
case HTTPMethod of
@ -136,17 +130,14 @@ dispatch_timeline(YArg, _Other) ->
dispatch_entry(YArg, _Session, []) ->
make_json_404(YArg, [{see_docs, "/ts_aip_doc/entries.html"}]);
dispatch_entry(YArg, Session, [UrlUsername|_T] = PathElements) ->
Username = path_element_to_atom(UrlUsername),
dispatch_entry(YArg, Session, [Username|_T] = PathElements) ->
case Session#ts_api_session.username of
Username -> dispatch_entry(YArg, PathElements);
_Other -> make_json_404(YArg, [{see_docs, "/ts_api_doc/entries.html"}])
end.
dispatch_entry(YArg, [UrlUsername, UrlTimelineId]) ->
Username = path_element_to_atom(UrlUsername),
TimelineId = path_element_to_atom(UrlTimelineId),
dispatch_entry(YArg, [Username, TimelineId]) ->
HTTPMethod = (YArg#arg.req)#http_request.method,
case HTTPMethod of
@ -155,9 +146,7 @@ dispatch_entry(YArg, [UrlUsername, UrlTimelineId]) ->
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/entries.html"}])
end;
dispatch_entry(YArg, [UrlUsername, UrlTimelineId, UrlEntryId]) ->
Username = path_element_to_atom(UrlUsername),
TimelineId = path_element_to_atom(UrlTimelineId),
dispatch_entry(YArg, [Username, TimelineId, UrlEntryId]) ->
EntryId = list_to_integer(UrlEntryId), % TODO: catch non-numbers
HTTPMethod = (YArg#arg.req)#http_request.method,
@ -184,8 +173,7 @@ do_login(YArg) ->
lists:keyfind(password, 1, Fields)} of
% username and password found
{{username, UnameField}, {password, Password}} ->
Username = list_to_atom(UnameField),
{{username, Username}, {password, Password}} ->
% check the uname, password
case ts_user:check_credentials(Username, Password) of
@ -281,7 +269,11 @@ post_timeline(YArg, Username, TimelineId) ->
EJSON = parse_json_body(YArg),
% parse into a Timeline record
TR = ts_json:ejson_to_record(#ts_timeline{}, EJSON),
TR = try ts_json:ejson_to_record(#ts_timeline{}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg))
end,
% set username and timeline id
NewRecord = TR#ts_timeline{ref = {Username, TimelineId}},
@ -309,12 +301,16 @@ put_timeline(YArg, Username, TimelineId) ->
%{struct, Fields} = EJSON,
% parse into a timeline record
TR = ts_json:ejson_to_record(#ts_timeline{}, EJSON),
TR = try ts_json:ejson_to_record(#ts_timeline{}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg))
end,
% not supported right now, would require changing all the entry keys
% check to see if they are changing the timeline id
%NewTimelineId = case lists:keyfind(1, timeline_id, Fields) of
% {timeline_id, Field} -> list_to_atom(Field);
% {timeline_id, Field} -> Field;
% false -> TimelineId end,
% set username and timeline id
@ -428,7 +424,11 @@ post_entry(YArg, Username, TimelineId) ->
EJSON = parse_json_body(YArg),
% parse into ts_entry record
ER = ts_json:ejson_to_record(#ts_entry{}, EJSON),
ER = try ts_json:ejson_to_record(#ts_entry{}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg))
end,
% set username and timeline id
NewRecord = ER#ts_entry{ref = {Username, TimelineId, undef}},
@ -455,7 +455,11 @@ put_entry(YArg, Username, TimelineId, EntryId) ->
EJSON = parse_json_body(YArg),
% parse into ts_entry record
ER = ts_json:ejson_to_record(#ts_entry{}, EJSON),
ER = try ts_json:ejson_to_record(#ts_entry{}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg))
end,
% set uername, timeline id, and entry id
NewRecord = ER#ts_entry{ref = {Username, TimelineId, EntryId}},
@ -492,10 +496,6 @@ delete_entry(YArg, Username, TimelineId, EntryId) ->
% ======== UTIL METHODS ======== %
% ============================== %
%% Convert one path element to an atom.
path_element_to_atom(PE) ->
list_to_atom(re:replace(PE, "\\s", "_", [{return, list}])).
parse_json_body(YArg) ->
case catch json:decode([], binary_to_list(YArg#arg.clidata)) of
{done, {ok, EJSON}, _} -> EJSON;
@ -547,19 +547,19 @@ make_json_405(YArg) -> make_json_405(YArg, []).
make_json_405(YArg, Fields) ->
% add default status if not provided
F1 = case lists:keyfind(status, 1, Fields) of
false -> Fields ++ [{status, method_not_allowed}];
false -> Fields ++ [{status, "method not allowed"}];
_Else -> Fields
end,
% add the path they requested
F2 = F1 ++ [{path, (YArg#arg.req)#http_request.path}],
% F2 = F1 ++ [{path, io_lib:format("~s", [(YArg#arg.req)#http_request.path])}],
[{status, 405}, {content, "application/json", json:encode({struct, F2})}].
[{status, 405}, {content, "application/json", json:encode({struct, F1})}].
make_json_500(_YArg, Error) ->
EJSON = {struct, [
{status, "internal server error"},
{error, io_lib:format("~p", [Error])}]},
{error, io_lib:format("~s", [Error])}]},
[{status, 500}, {content, "application/json", json:encode(EJSON)}].
make_json_500(_YArg) ->

View File

@ -7,10 +7,11 @@ encode_record(Record) -> lists:flatten(json:encode(record_to_ejson(Record))).
record_to_ejson(Record=#ts_user{}) ->
{struct, [
{id, atom_to_list(Record#ts_user.username)},
{id, Record#ts_user.username},
{name, Record#ts_user.name},
{email, Record#ts_user.email},
{join_date, encode_datetime(Record#ts_user.join_date)}]};
{join_date, encode_datetime(Record#ts_user.join_date)},
{ext_data, Record#ts_user.ext_data}]};
record_to_ejson(Record=#ts_timeline{}) ->
% pull out the username and timeline id
@ -18,8 +19,8 @@ record_to_ejson(Record=#ts_timeline{}) ->
% create the EJSON struct
{struct, [
{user_id, atom_to_list(Username)},
{id, atom_to_list(TimelineId)},
{user_id, Username},
{id, TimelineId},
{created, encode_datetime(Record#ts_timeline.created)},
{description, Record#ts_timeline.desc}]};
@ -32,15 +33,15 @@ record_to_ejson(Record=#ts_entry{}) ->
% create the EJSON struct
{struct, [
{user_id, atom_to_list(Username)},
{timeline_id, atom_to_list(TimelineId)},
{user_id, Username},
{timeline_id, TimelineId},
{id, EntryId},
{timestamp, encode_datetime(DateTime)},
{mark, Record#ts_entry.mark},
{notes, Record#ts_entry.notes}]}.
encode_datetime({{Year, Month, Day}, {Hour, Minute, Second}}) ->
lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B~3.10.0BZ",
lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B.~3.10.0BZ",
[Year, Month, Day, Hour, Minute, Second, 000])).
ejson_to_record(_Empty=#ts_timeline{}, EJSON) ->