diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2024-04-17 10:47:33 +0200 |
---|---|---|
committer | Francois Kritzinger <francois@codesynthesis.com> | 2024-06-05 09:12:46 +0200 |
commit | c92142ce729c5ef38b25465dd2e7c2d67df05172 (patch) | |
tree | a0e5849276604d1e8bf08ba168e41650344b9228 /mod/mod-ci-github-gq.hxx | |
parent | 4364c39d0708d2c353cb06d8fb5e5e2a017cf000 (diff) |
Review (restructure)
Diffstat (limited to 'mod/mod-ci-github-gq.hxx')
-rw-r--r-- | mod/mod-ci-github-gq.hxx | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/mod/mod-ci-github-gq.hxx b/mod/mod-ci-github-gq.hxx new file mode 100644 index 0000000..de7021a --- /dev/null +++ b/mod/mod-ci-github-gq.hxx @@ -0,0 +1,216 @@ +// file : mod/mod-ci-github-gq.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_MOD_CI_GITHUB_GQ_HXX +#define MOD_MOD_CI_GITHUB_GQ_HXX + +#include <libbrep/types.hxx> +#include <libbrep/utility.hxx> + +#include <mod/mod-ci-github-gh.hxx> +#include <mod/mod-ci-github-service-data.hxx> + + +namespace brep +{ + // GraphQL functions (all start with gq_). + // + + // @@ TODO: + + gq_create_check_run (); + gq_update_check_run (); + + // @@ TODO Pass error, trace in same order everywhere. + + // Fetch from GitHub the check run with the specified name (hints-shortened + // build ID). + // + // Return the check run or nullopt if no such check run exists. + // + // In case of error diagnostics will be issued and false returned in second. + // + // Note that the existence of more than one check run with the same name is + // considered an error and reported as such. The API docs imply that there + // can be more than one check run with the same name in a check suite, but + // the observed behavior is that creating a check run destroys the extant + // one, leaving only the new one with different node ID. + // + pair<optional<gh::check_run>, bool> + gq_fetch_check_run (const string& iat, + const string& check_suite_id, + const string& cr_name, + const basic_mark& error) noexcept + { + try + { + // Example request: + // + // query { + // node(id: "CS_kwDOLc8CoM8AAAAFQPQYEw") { + // ... on CheckSuite { + // checkRuns(last: 100, filterBy: {checkName: "linux_debian_..."}) { + // totalCount, + // edges { + // node { + // id, name, status + // } + // } + // } + // } + // } + // } + // + // This request does the following: + // + // - Look up the check suite by node ID ("direct node lookup"). This + // returns a Node (GraphQL interface). + // + // - Get to the concrete CheckSuite type by using a GraphQL "inline + // fragment" (`... on CheckSuite`). + // + // - Get the check suite's check runs + // - Filter by the sought name + // - Return only two check runs, just enough to be able to tell + // whether there are more than one check runs with this name (which + // is an error). + // + // - Return the id, name, and status fields from the matching check run + // objects. + // + string rq; + { + ostringstream os; + + os << "query {" << '\n'; + + os << "node(id: " << gq_str (check_suite_id) << ") {" << '\n' + << " ... on CheckSuite {" << '\n' + << " checkRuns(last: 2," << '\n' + << " filterBy: {" << '\n' + << "checkName: " << gq_str (cr_name) << '\n' + << " })" << '\n' + // Specify the selection set (fields to be returned). Note that + // edges and node are mandatory. + // + << " {" << '\n' + << " totalCount," << '\n' + << " edges {" << '\n' + << " node {" << '\n' + << " id, name, status" << '\n' + << " }" << '\n' + << " }" << '\n' + << " }" << '\n' + << " }" << '\n' + << "}" << '\n'; + + os << "}" << '\n'; + + rq = os.str (); + } + + // Example response (the part we need to parse here, at least): + // + // { + // "node": { + // "checkRuns": { + // "totalCount": 1, + // "edges": [ + // { + // "node": { + // "id": "CR_kwDOLc8CoM8AAAAFgeoweg", + // "name": "linux_debian_...", + // "status": "IN_PROGRESS" + // } + // } + // ] + // } + // } + // } + // + struct resp + { + optional<check_run> cr; + size_t cr_count = 0; + + resp (json::parser& p) + { + using event = json::event; + + parse_graphql_response (p, [this] (json::parser& p) + { + p.next_expect (event::begin_object); + p.next_expect_member_object ("node"); + p.next_expect_member_object ("checkRuns"); + + cr_count = p.next_expect_member_number<size_t> ("totalCount"); + + p.next_expect_member_array ("edges"); + + for (size_t i (0); i != cr_count; ++i) + { + p.next_expect (event::begin_object); + p.next_expect_name ("node"); + check_run cr (p); + p.next_expect (event::end_object); + + if (i == 0) + this->cr = move (cr); + } + + p.next_expect (event::end_array); // edges + p.next_expect (event::end_object); // checkRuns + p.next_expect (event::end_object); // node + p.next_expect (event::end_object); + }); + } + + resp () = default; + } rs; + + uint16_t sc (github_post (rs, + "graphql", + strings {"Authorization: Bearer " + iat}, + graphql_request (rq))); + + if (sc == 200) + { + if (rs.cr_count <= 1) + return {rs.cr, true}; + else + { + error << "unexpected number of check runs (" << rs.cr_count + << ") in response"; + } + } + else + error << "failed to get check run by name: error HTTP " + << "response status " << sc; + } + catch (const json::invalid_json_input& e) + { + // Note: e.name is the GitHub API endpoint. + // + error << "malformed JSON in response from " << e.name + << ", line: " << e.line << ", column: " << e.column + << ", byte offset: " << e.position << ", error: " << e; + } + catch (const invalid_argument& e) + { + error << "malformed header(s) in response: " << e; + } + catch (const system_error& e) + { + error << "unable to get check run by name (errno=" << e.code () + << "): " << e.what (); + } + catch (const std::exception& e) + { + error << "unable to get check run by name: " << e.what (); + } + + return {nullopt, false}; + } +} + +#endif // MOD_MOD_CI_GITHUB_GQ_HXX |