timestamper/src/ts_json.erl
Jonathan Bernard 1e05258381 Bugfixes.
* ts_api:list_timelines/2 was looking for keys as atoms instead of lists (strings).
* Exported the ts_json:decode_datetime/1 function (needed by ts_api module).
* Fixed a crash case when trying to check the credentials of a non-existent user.
2013-09-15 00:33:02 +00:00

214 lines
7.7 KiB
Erlang

-module(ts_json).
-export([encode_record/2, record_to_ejson/2,
ejson_to_record/2, ejson_to_record/3,
ejson_to_record_strict/2, ejson_to_record_strict/3,
decode_datetime/1]).
-include("ts_db_records.hrl").
encode_record(Record, ExtData) -> lists:flatten(json:encode(record_to_ejson(Record, ExtData))).
% User JSON record required structure:
% {"id": "john_doe",
% "name": "John Doe",
% "email": "john.doe@example.com",
% "join_date": "2011-01-01T12:00.000Z"}
record_to_ejson(Record=#ts_user{}, ExtData) ->
{struct, [
{id, Record#ts_user.username},
{name, Record#ts_user.name},
{email, Record#ts_user.email},
{join_date, encode_datetime(Record#ts_user.join_date)}] ++
lists:map(fun ext_data_to_ejson/1, ExtData)};
% Timeline JSON record stucture:
% {"user_id": "john_doe",
% "id": "personal",
% "created": "2011-01-01T14:00.000Z",
% "description:"Personal time-tracking."}
record_to_ejson(Record=#ts_timeline{}, ExtData) ->
% pull out the username and timeline id
{Username, TimelineId} = Record#ts_timeline.ref,
% create the EJSON struct
{struct, [
{user_id, Username},
{id, TimelineId},
{created, encode_datetime(Record#ts_timeline.created)},
{description, Record#ts_timeline.desc}] ++
lists:map(fun ext_data_to_ejson/1, ExtData)};
% Entry JSON record structure:
% {"user_id": "john_doe",
% "timeline_id": "personal",
% "id": "1",
% "timestamp": "2011-01-01T14:01.000Z",
% "mark": "Workout.",
% "notes": "First workout after a long break."}
record_to_ejson(Record=#ts_entry{}, ExtData) ->
% pull out the username, timeline id, and entry id
{Username, TimelineId, EntryId} = Record#ts_entry.ref,
% convert the timestamp to a date-time
DateTime = calendar:gregorian_seconds_to_datetime(Record#ts_entry.timestamp),
% create the EJSON struct
{struct, [
{user_id, Username},
{timeline_id, TimelineId},
{id, EntryId},
{timestamp, encode_datetime(DateTime)},
{mark, Record#ts_entry.mark},
{notes, Record#ts_entry.notes}] ++
lists:map(fun ext_data_to_ejson/1, ExtData)}.
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",
[Year, Month, Day, Hour, Minute, Second, 000])).
ext_data_to_ejson({Key, Value}) ->
case Key of
entry_exclusions -> {Key, {array, Value}};
_Other -> {Key, Value}
end.
ejson_to_record(Empty, {struct, EJSONFields}) ->
construct_record(Empty, EJSONFields, []).
ejson_to_record(Empty, Ref, EJSON) ->
Constructed = ejson_to_record(Empty, EJSON),
setelement(2, Constructed, Ref).
ejson_to_record_strict(Empty=#ts_user{}, EJSON) ->
Constructed = ejson_to_record(Empty, EJSON),
case Constructed of
#ts_user{name = undefined} -> throw("Missing user 'name' field.");
#ts_user{email = undefined} -> throw("Missing user 'email' field.");
#ts_user{join_date = undefined} ->
throw("Missing user 'join_date' field.");
_Other -> Constructed
end;
ejson_to_record_strict(Empty=#ts_timeline{}, EJSON) ->
Constructed = ejson_to_record(Empty, EJSON),
case Constructed of
#ts_timeline{created = undefined} ->
throw("Missing timeline 'created' field.");
#ts_timeline{desc = undefined} ->
throw("Missing timeline 'description' field.");
_Other -> Constructed
end;
ejson_to_record_strict(Empty=#ts_entry{}, EJSON) ->
Constructed = ejson_to_record(Empty, EJSON),
case Constructed of
#ts_entry{timestamp = undefined} ->
throw("Missing timelne 'timestamp' field.");
#ts_entry{mark = undefined} ->
throw("Missing timeline 'mark' field.");
#ts_entry{notes = undefined} ->
throw("Missing timeline 'notes' field/");
_Other -> Constructed
end.
ejson_to_record_strict(Empty, Ref, EJSON) ->
Constructed = ejson_to_record_strict(Empty, EJSON),
setelement(2, Constructed, Ref).
construct_record(User=#ts_user{}, [{Key, Value}|Fields], ExtData) ->
case Key of
id -> construct_record(User#ts_user{username = Value},
Fields, ExtData);
name -> construct_record(User#ts_user{name = Value}, Fields, ExtData);
email -> construct_record(User#ts_user{email = Value}, Fields, ExtData);
join_date -> construct_record(
User#ts_user{join_date = decode_datetime(Value)},
Fields, ExtData);
_Other ->
ExtDataProp = ejson_to_ext_data({Key, Value}),
construct_record(User, Fields, [ExtDataProp|ExtData])
end;
construct_record(Timeline=#ts_timeline{}, [{Key, Value}|Fields], ExtData) ->
case Key of
user_id ->
{_, TimelineId} = Timeline#ts_timeline.ref,
construct_record(Timeline#ts_timeline{ref = {Value, TimelineId}},
Fields, ExtData);
id ->
{Username, _} = Timeline#ts_timeline.ref,
construct_record(Timeline#ts_timeline{ref = {Username, Value}},
Fields, ExtData);
created -> construct_record(
Timeline#ts_timeline{created = decode_datetime(Value)},
Fields, ExtData);
description -> construct_record(Timeline#ts_timeline{desc = Value},
Fields, ExtData);
_Other ->
ExtDataProp = ejson_to_ext_data({Key, Value}),
construct_record(Timeline, Fields, [ExtDataProp|ExtData])
end;
construct_record(Entry=#ts_entry{}, [{Key, Value}|Fields], ExtData) ->
case Key of
user_id ->
{_, TimelineId, EntryId} = Entry#ts_entry.ref,
construct_record(Entry#ts_entry{ref = {Value, TimelineId, EntryId}},
Fields, ExtData);
timeline_id ->
{Username, _, EntryId} = Entry#ts_entry.ref,
construct_record(Entry#ts_entry{ref = {Username, Value, EntryId}},
Fields, ExtData);
id ->
{Username, TimelineId, _} = Entry#ts_entry.ref,
construct_record(Entry#ts_entry{ref = {Username, TimelineId, Value}},
Fields, ExtData);
timestamp -> construct_record(
Entry#ts_entry{timestamp = calendar:datetime_to_gregorian_seconds(
decode_datetime(Value))},
Fields, ExtData);
mark -> construct_record(Entry#ts_entry{mark=Value}, Fields, ExtData);
notes -> construct_record(Entry#ts_entry{notes=Value}, Fields, ExtData);
_Other ->
ExtDataProp = ejson_to_ext_data({Key, Value}),
construct_record(Entry, Fields, [ExtDataProp|ExtData])
end;
construct_record(Record, [], ExtData) -> {Record, ExtData}.
decode_datetime(DateTimeString) ->
% TODO: catch badmatch and badarg on whole function
[DateString, TimeString] = re:split(DateTimeString, "[TZ]",
[{return, list}, trim]),
[YearString, MonthString, DayString] =
re:split(DateString, "-", [{return, list}]),
[HourString, MinuteString, SecondString] =
case re:split(TimeString, "[:\\.]", [{return, list}]) of
[HS, MS, SS, _MSS] -> [HS, MS, SS];
[HS, MS, SS] -> [HS, MS, SS]
end,
Date = {list_to_integer(YearString), list_to_integer(MonthString),
list_to_integer(DayString)},
Time = {list_to_integer(HourString), list_to_integer(MinuteString),
list_to_integer(SecondString)},
{Date, Time}.
ejson_to_ext_data({Key, Value}) ->
case Key of
entry_exclusions ->
{array, ExclList} = Value,
{Key, ExclList};
_Other -> {Key, Value}
end.