From fe0957fdd6563e5be02d723b6cb35f70e7ec4b3f Mon Sep 17 00:00:00 2001 From: Francois Kritzinger Date: Mon, 4 Nov 2024 17:18:32 +0200 Subject: Parse check_run webhook events --- mod/mod-ci-github-gh.cxx | 90 ++++++++++++++++++++++++++++++++++++++++++++---- mod/mod-ci-github-gh.hxx | 42 +++++++++++++++++----- 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 details_url; // Webhooks/REST only. + + optional 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& -- cgit v1.1