aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancois Kritzinger <francois@codesynthesis.com>2024-11-04 17:18:32 +0200
committerFrancois Kritzinger <francois@codesynthesis.com>2024-11-05 11:23:24 +0200
commitfe0957fdd6563e5be02d723b6cb35f70e7ec4b3f (patch)
treebb286be51040706f695fbd4425a2e9cf9776917b
parent3eb521db2d615c8513caad9e29cfa1197ece7b7a (diff)
Parse check_run webhook events
-rw-r--r--mod/mod-ci-github-gh.cxx90
-rw-r--r--mod/mod-ci-github-gh.hxx42
2 files changed, 117 insertions, 15 deletions
diff --git a/mod/mod-ci-github-gh.cxx b/mod/mod-ci-github-gh.cxx
index 208adbd..7f9aa51 100644
--- a/mod/mod-ci-github-gh.cxx
+++ b/mod/mod-ci-github-gh.cxx
@@ -156,19 +156,46 @@ namespace brep
// gh_check_run
//
+ // Note that this constructor parses both 1) webhook events/REST responses
+ // and 2) GraphQL responses. In the former case (in addition to expecting a
+ // few more fields to be present) the node id is in the `node_id` field
+ // whereas in the latter it is in the `id` field. However we use the GraphQL
+ // field alias feature to rename the `id` field to `node_id` so we only ever
+ // have to look for `node_id`.
+ //
gh_check_run::
- gh_check_run (json::parser& p)
+ gh_check_run (json::parser& p, bool wor)
{
p.next_expect (event::begin_object);
- // We always ask for this exact set of fields to be returned in GraphQL
- // requests.
+ bool ni (false), nm (false), st (false), du (false), cs (false);
+
+ // Skip unknown/uninteresting members.
//
- node_id = p.next_expect_member_string ("id");
- name = p.next_expect_member_string ("name");
- status = p.next_expect_member_string ("status");
+ while (p.next_expect (event::name, event::end_object))
+ {
+ auto c = [&p] (bool& v, const char* s)
+ {
+ return p.name () == s ? (v = true) : false;
+ };
+
+ if (c (ni, "node_id")) node_id = p.next_expect_string ();
+ else if (c (nm, "name")) name = p.next_expect_string ();
+ else if (c (st, "status")) status = p.next_expect_string ();
+ else if (c (du, "details_url")) details_url = p.next_expect_string ();
+ else if (c (cs, "check_suite")) check_suite = gh_check_suite (p);
+ else p.next_expect_value_skip ();
+ }
- p.next_expect (event::end_object);
+ if (!ni) missing_member (p, "gh_check_run", "node_id");
+ if (!nm) missing_member (p, "gh_check_run", "name");
+ if (!st) missing_member (p, "gh_check_run", "status");
+
+ if (wor) // Parsing a webhook event or REST API response.
+ {
+ if (!du) missing_member (p, "gh_check_run", "details_url");
+ if (!cs) missing_member (p, "gh_check_run", "check_suite");
+ }
}
ostream&
@@ -178,6 +205,9 @@ namespace brep
<< ", name: " << cr.name
<< ", status: " << cr.status;
+ if (cr.check_suite)
+ os << ", check_suite: { " << *cr.check_suite << " }";
+
return os;
}
@@ -404,6 +434,52 @@ namespace brep
return os;
}
+ // gh_check_run_event
+ //
+ gh_check_run_event::
+ gh_check_run_event (json::parser& p)
+ {
+ p.next_expect (event::begin_object);
+
+ bool ac (false), cs (false), rp (false), in (false);
+
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
+ {
+ auto c = [&p] (bool& v, const char* s)
+ {
+ return p.name () == s ? (v = true) : false;
+ };
+
+ // Pass true to gh_check_run() to indicate that the we're parsing a
+ // webhook event or REST API response (in which case more fields are
+ // expected to be present than in a GraphQL response).
+ //
+ if (c (ac, "action")) action = p.next_expect_string ();
+ else if (c (cs, "check_run")) check_run = gh_check_run (p, true);
+ else if (c (rp, "repository")) repository = gh_repository (p);
+ else if (c (in, "installation")) installation = gh_installation (p);
+ else p.next_expect_value_skip ();
+ }
+
+ if (!ac) missing_member (p, "gh_check_run_event", "action");
+ if (!cs) missing_member (p, "gh_check_run_event", "check_run");
+ if (!rp) missing_member (p, "gh_check_run_event", "repository");
+ if (!in) missing_member (p, "gh_check_run_event", "installation");
+ }
+
+ ostream&
+ operator<< (ostream& os, const gh_check_run_event& cr)
+ {
+ os << "action: " << cr.action;
+ os << ", check_run { " << cr.check_run << " }";
+ os << ", repository { " << cr.repository << " }";
+ os << ", installation { " << cr.installation << " }";
+
+ return os;
+ }
+
// gh_pull_request_event
//
gh_pull_request_event::
diff --git a/mod/mod-ci-github-gh.hxx b/mod/mod-ci-github-gh.hxx
index f3bcfeb..e6e9ba6 100644
--- a/mod/mod-ci-github-gh.hxx
+++ b/mod/mod-ci-github-gh.hxx
@@ -27,18 +27,21 @@ namespace brep
// GitHub request/response types (all start with gh_).
//
- // Note that the GitHub REST and GraphQL APIs use different ID types and
+ // Note that the GitHub REST and GraphQL APIs use different id types and
// values. In the REST API they are usually integers (but sometimes
// strings!) whereas in GraphQL they are always strings (note:
- // base64-encoded and opaque, not just the REST ID value as a string).
+ // base64-encoded and opaque, not just the REST id value as a string).
//
- // In both APIs the ID field is called `id`, but REST responses and webhook
- // events also contain the corresponding GraphQL object's ID in the
+ // In both APIs the id field is called `id`, but REST responses and webhook
+ // events also contain the corresponding GraphQL object's id in the
// `node_id` field.
//
- // In the structures below we always use the RESP API/webhook names for ID
- // fields. I.e., `id` always refers to the REST/webhook ID, and `node_id`
- // always refers to the GraphQL ID.
+ // The GraphQL API's ids are called "global node ids" by GitHub. We refer to
+ // them simply as node ids and we use them almost exclusively (over the
+ // REST/webhook ids).
+ //
+ // In the structures below, `id` always refers to the REST/webhook id and
+ // `node_id` always refers to the node id.
//
namespace json = butl::json;
@@ -61,9 +64,16 @@ namespace brep
string node_id;
string name;
string status;
+ optional<string> details_url; // Webhooks/REST only.
+
+ optional<gh_check_suite> check_suite; // Webhooks/REST only.
+ // If the second argument is true then we're parsing a webhook event or
+ // REST API response in which case we expect a few more fields to be
+ // present than in a GraphQL response.
+ //
explicit
- gh_check_run (json::parser&);
+ gh_check_run (json::parser&, bool webhook_or_rest = false);
gh_check_run () = default;
};
@@ -151,6 +161,19 @@ namespace brep
gh_check_suite_event () = default;
};
+ struct gh_check_run_event
+ {
+ string action;
+ gh_check_run check_run;
+ gh_repository repository;
+ gh_installation installation;
+
+ explicit
+ gh_check_run_event (json::parser&);
+
+ gh_check_run_event () = default;
+ };
+
struct gh_pull_request_event
{
string action;
@@ -203,6 +226,9 @@ namespace brep
operator<< (ostream&, const gh_check_suite_event&);
ostream&
+ operator<< (ostream&, const gh_check_run_event&);
+
+ ostream&
operator<< (ostream&, const gh_pull_request_event&);
ostream&