From: Omar Polo Subject: improve the json structure To: gameoftrees@openbsd.org Date: Thu, 28 Mar 2024 10:28:27 +0100 This changes the produced json so that the "author" field becomes a object itself with the following fields "author": { "full": "Omar Polo ", "name": "Omar Polo", "mail": "op@openbsd.org", "user": "op" } and similarly for the committer. The only field that is guaranteed to be always present is author.user since the short formats shows only the (abbreviated) username. In the short format we should report "committer" instead of "author" since it's actually the committer. To make things consistent, in the "long" format remember the author and use that for the committer too if not provided. This way, we can guaranteed that committer.user is always present in all notifications. Then, similarly, instead of a single field "message" that is the full commit message in some cases and the first line in some others, add an extra field "short_message" that is just the first line. Again, this is to guarantee that "short_message" is always present. There is some redundancy in the JSON now, but I think it's better to make the consuming easier. For example, knowing this, to produce notifications in the IRC channel one only has to extract the id, committer.user and short_message field (that are now guaranteed to always exist). ok? diff /home/op/w/got commit - 87890bc26c1c6958bd64bb9d46fbc29ba6a92d95 path + /home/op/w/got blob - 80cfc6f803bbf046beb7285ddb64fd7ef3169391 file + gotd/libexec/got-notify-http/got-notify-http.c --- gotd/libexec/got-notify-http/got-notify-http.c +++ gotd/libexec/got-notify-http/got-notify-http.c @@ -137,6 +137,46 @@ json_field(FILE *fp, const char *key, const char *val, fprintf(fp, "\"%s", comma ? "," : ""); } +static void +json_author(FILE *fp, const char *type, char *address, int comma) +{ + char *gt, *lt, *at, *email, *endname; + + fprintf(fp, "\"%s\":{", type); + + gt = strchr(address, '<'); + if (gt != NULL) { + /* long format, e.g. "Omar Polo " */ + + json_field(fp, "full", address, 1); + + endname = gt; + while (endname > address && endname[-1] == ' ') + endname--; + + *endname = '\0'; + json_field(fp, "name", address, 1); + + email = gt + 1; + lt = strchr(email, '>'); + if (lt) + *lt = '\0'; + + json_field(fp, "mail", email, 1); + + at = strchr(email, '@'); + if (at) + *at = '\0'; + + json_field(fp, "user", email, 0); + } else { + /* short format only shows the username */ + json_field(fp, "user", address, 0); + } + + fprintf(fp, "}%s", comma ? "," : ""); +} + static int jsonify_short(FILE *fp) { @@ -175,9 +215,9 @@ jsonify_short(FILE *fp) fprintf(fp, "{\"short\":true,"); json_field(fp, "id", id, 1); - json_field(fp, "author", author, 1); + json_author(fp, "committer", author, 1); json_field(fp, "date", date, 1); - json_field(fp, "message", message, 0); + json_field(fp, "short_message", message, 0); fprintf(fp, "}"); } @@ -194,6 +234,7 @@ static int jsonify(FILE *fp) { const char *errstr; + char *author = NULL; char *l; char *line = NULL; size_t linesize = 0; @@ -242,7 +283,13 @@ jsonify(FILE *fp) if (strncmp(l, "from: ", 6) != 0) errx(1, "unexpected from line"); l += 6; - json_field(fp, "author", l, 1); + + author = strdup(l); + if (author == NULL) + err(1, "strdup"); + + json_author(fp, "author", l, 1); + phase = P_VIA; break; @@ -250,10 +297,17 @@ jsonify(FILE *fp) /* optional */ if (!strncmp(l, "via: ", 5)) { l += 5; - json_field(fp, "via", l, 1); + json_author(fp, "committer", l, 1); phase = P_DATE; break; } + + if (author == NULL) /* impossible */ + err(1, "from not specified"); + json_author(fp, "committer", author, 1); + free(author); + author = NULL; + phase = P_DATE; /* fallthrough */ @@ -301,7 +355,6 @@ jsonify(FILE *fp) if (errstr) errx(1, "message len is %s: %s", errstr, l); - fprintf(fp, "\"message\":\""); phase = P_MSG; break; @@ -317,14 +370,21 @@ jsonify(FILE *fp) * tolerate one byte less than advertised. */ if (*l == ' ') { - escape(fp, l + 1); /* skip leading space */ + l++; /* skip leading space */ + linelen--; - /* avoid pre-pending \n to the commit msg */ - msgwrote += linelen - 1; - if (msgwrote != 0) + if (msgwrote == 0 && linelen != 0) { + json_field(fp, "short_message", l, 1); + fprintf(fp, "\"message\":\""); + escape(fp, l); escape(fp, "\n"); + msgwrote += linelen; + } else if (msgwrote != 0) { + escape(fp, l); + escape(fp, "\n"); + } } - msglen -= linelen; + msglen -= linelen + 1; if (msglen <= 1) { fprintf(fp, "\","); msgwrote = 0; blob - 6892df7536c1a19134db4827c42f870d903c65a7 file + regress/gotd/http_notification.sh --- regress/gotd/http_notification.sh +++ regress/gotd/http_notification.sh @@ -62,8 +62,20 @@ test_file_changed() { {"notifications":[{ "short":false, "id":"$commit_id", - "author":"$GOT_AUTHOR", + "author":{ + "full":"$GOT_AUTHOR", + "name":"$GIT_AUTHOR_NAME", + "mail":"$GIT_AUTHOR_EMAIL", + "user":"$GOT_AUTHOR_11" + }, + "committer":{ + "full":"$GOT_AUTHOR", + "name":"$GIT_AUTHOR_NAME", + "mail":"$GIT_AUTHOR_EMAIL", + "user":"$GOT_AUTHOR_11" + }, "date":"$d", + "short_message":"make changes", "message":"make changes\n", "diffstat":{}, "changes":{} @@ -139,8 +151,20 @@ test_many_commits_not_summarized() { { "short":false, "id":"$commit_id", - "author":"$GOT_AUTHOR", + "author":{ + "full":"$GOT_AUTHOR", + "name":"$GIT_AUTHOR_NAME", + "mail":"$GIT_AUTHOR_EMAIL", + "user":"$GOT_AUTHOR_11" + }, + "committer":{ + "full":"$GOT_AUTHOR", + "name":"$GIT_AUTHOR_NAME", + "mail":"$GIT_AUTHOR_EMAIL", + "user":"$GOT_AUTHOR_11" + }, "date":"$commit_time", + "short_message":"make changes", "message":"make changes\n", "diffstat":{}, "changes":{} @@ -220,9 +244,11 @@ test_many_commits_summarized() { { "short":true, "id":"$commit_id", - "author":"$GOT_AUTHOR_8", + "committer":{ + "user":"$GOT_AUTHOR_8" + }, "date":"$commit_time", - "message":"make changes" + "short_message":"make changes" } EOF done >> $testroot/stdout.expected