diff options
author | Francois Kritzinger <francois@codesynthesis.com> | 2024-04-17 11:09:46 +0200 |
---|---|---|
committer | Francois Kritzinger <francois@codesynthesis.com> | 2024-06-05 09:12:46 +0200 |
commit | 3f2a23ab62b5cbb940db7f925b3fff84d688285f (patch) | |
tree | f9340d9efac04cd4b884427a0b19c63b3e938c89 /mod/mod-ci-github-qg.cxx | |
parent | c92142ce729c5ef38b25465dd2e7c2d67df05172 (diff) |
Get restructure to compile
Diffstat (limited to 'mod/mod-ci-github-qg.cxx')
-rw-r--r-- | mod/mod-ci-github-qg.cxx | 509 |
1 files changed, 0 insertions, 509 deletions
diff --git a/mod/mod-ci-github-qg.cxx b/mod/mod-ci-github-qg.cxx deleted file mode 100644 index b0e40a6..0000000 --- a/mod/mod-ci-github-qg.cxx +++ /dev/null @@ -1,509 +0,0 @@ -// file : mod/mod-ci-github-gq.cxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#include <mod/mod-ci-github-gq.hxx> - -namespace brep -{ - // GraphQL serialization functions (see definitions and documentation at the - // bottom). - // - static const string& gq_name (const string&); - static string gq_str (const string&); - static string gq_bool (bool); - static const string& gq_enum (const string&); - - // Send a GraphQL mutation request `rq` that operates on one or more check - // runs. Update the check runs in `crs` with the new state and the node ID - // if unset (note: both fields are optionals). Return false and issue - // diagnostics if the request failed. - // - static bool - gq_mutate_check_runs (vector<service_data::check_run>& crs, - const vector<reference_wrapper<const build>>& bs, - const string& iat, - string rq, - build_state st, - const basic_mark& error) noexcept - { - vector<check_run> rcrs; - - try - { - // Response type which parses a GraphQL response containing multiple - // check_run objects. - // - struct resp - { - vector<check_run> check_runs; // Received check runs. - - resp (json::parser& p) : check_runs (parse_check_runs_response (p)) {} - - resp () = default; - } rs; - - uint16_t sc (github_post (rs, - "graphql", // API Endpoint. - strings {"Authorization: Bearer " + iat}, - move (rq))); - - if (sc == 200) - { - rcrs = move (rs.check_runs); - - if (rcrs.size () == bs.size ()) - { - for (size_t i (0); i != rcrs.size (); ++i) - { - // Validate the check run in the response against the build. - // - const check_run& rcr (rcrs[i]); // Received check run. - const build& b (bs[i]); - - build_state rst (from_string_gh (rcr.status)); // Received state. - - if (rst != build_state::built && rst != st) - { - error << "unexpected check_run status: received '" << rcr.status - << "' but expected '" << to_string_gh (st) << '\''; - - return false; // Fail because something is clearly very wrong. - } - else - { - service_data::check_run& cr (crs[i]); - - if (!cr.node_id) - cr.node_id = move (rcr.node_id); - - cr.state = from_string_gh (rcr.status); - } - } - - return true; - } - else - error << "unexpected number of check_run objects in response"; - } - else - error << "failed to update check run: 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 mutate check runs (errno=" << e.code () << "): " - << e.what (); - } - catch (const runtime_error& e) // From parse_check_runs_response(). - { - // GitHub response contained error(s) (could be ours or theirs at this - // point). - // - error << "unable to mutate check runs: " << e; - } - - return false; - } - - static bool - gq_mutate_check_run (service_data::check_run& cr, - const vector<reference_wrapper<const build>>& bs, - const string& iat, - string rq, - build_state st, - const basic_mark& error) noexcept - { - vector<service_data::check_run> crs {move (cr)}; - - bool r (mutate_check_runs (crs, bs, iat, move (rq), st, error)); - - cr = move (crs[0]); - - return r; - } - - // Serialize `createCheckRun` mutations for one or more builds to GraphQL. - // - static string - gq_create_check_runs (const string& ri, // Repository ID - const string& hs, // Head SHA - const vector<reference_wrapper<const build>>& bs, - build_state st, - const tenant_service_base::build_hints* bh = nullptr) - { - ostringstream os; - - os << "mutation {" << '\n'; - - // Serialize a `createCheckRun` for each build. - // - for (size_t i (0); i != bs.size (); ++i) - { - const build& b (bs[i]); - - string al ("cr" + to_string (i)); // Field alias. - - // Check run name. - // - string nm (check_run_name (b, bh)); - - os << gq_name (al) << ":createCheckRun(input: {" << '\n' - << " name: " << gq_str (nm) << ',' << '\n' - << " repositoryId: " << gq_str (ri) << ',' << '\n' - << " headSha: " << gq_str (hs) << ',' << '\n' - << " status: " << gq_enum (to_string_gh (st)) << '\n' - << "})" << '\n' - // Specify the selection set (fields to be returned). - // - << "{" << '\n' - << " checkRun {" << '\n' - << " id," << '\n' - << " name," << '\n' - << " status" << '\n' - << " }" << '\n' - << "}" << '\n'; - } - - os << "}" << '\n'; - - return os.str (); - } - - // Serialize an `updateCheckRun` mutation for one build to GraphQL. - // - // @@ TODO Support conclusion, output, etc. - // - static string - gq_update_check_run (const string& ri, // Repository ID - const string& ci, // Check run node_id - build_state st) - { - ostringstream os; - - os << "mutation {" << '\n' - << "cr0:updateCheckRun(input: {" << '\n' - << " checkRunId: " << gq_str (ci) << ',' << '\n' - << " repositoryId: " << gq_str (ri) << ',' << '\n' - << " status: " << gq_enum (to_string_gh (st)) << '\n' - << "})" << '\n' - // Specify the selection set (fields to be returned). - // - << "{" << '\n' - << " checkRun {" << '\n' - << " id," << '\n' - << " name," << '\n' - << " status" << '\n' - << " }" << '\n' - << "}" << '\n' - << "}" << '\n'; - - return os.str (); - } - - // Serialize a GraphQL operation (query/mutation) into a GraphQL request. - // - // This is essentially a JSON object with a "query" string member containing - // the GraphQL operation. For example: - // - // { "query": "mutation { cr0:createCheckRun(... }" } - // - static string - gq_serialize_request (const string& o) - { - string b; - json::buffer_serializer s (b); - - s.begin_object (); - s.member ("query", o); - s.end_object (); - - return b; - } - - [[noreturn]] void - throw_json (json::parser& p, const string& m) - { - throw json::invalid_json_input ( - p.input_name, - p.line (), p.column (), p.position (), - m); - } - - // Parse a JSON-serialized GraphQL response. - // - // Throw runtime_error if the response indicated errors and - // invalid_json_input if the GitHub response contained invalid JSON. - // - // The response format is defined in the GraphQL spec: - // https://spec.graphql.org/October2021/#sec-Response. - // - // Example response: - // - // { - // "data": {...}, - // "errors": {...} - // } - // - // The contents of `data`, including its opening and closing braces, are - // parsed by the `parse_data` function. - // - // @@ TODO: specify what parse_data may throw (probably only - // invalid_json_input). - // - // @@ TODO errors comes before data in GitHub's responses. - // - static void - gq_parse_response (json::parser& p, - function<void (json::parser&)> parse_data) - { - using event = json::event; - - // True if the data/errors fields are present. - // - // Although the spec merely recommends that the `errors` field, if - // present, comes before the `data` field, assume it always does because - // letting the client parse data in the presence of field errors - // (unexpected nulls) would not make sense. - // - bool dat (false), err (false); - - p.next_expect (event::begin_object); - - while (p.next_expect (event::name, event::end_object)) - { - if (p.name () == "data") - { - dat = true; - - // Currently we're not handling fields that are null due to field - // errors (see below for details) so don't parse any further. - // - if (err) - break; - - parse_data (p); - } - else if (p.name () == "errors") - { - // Don't stop parsing because the error semantics depends on whether - // or not `data` is present. - // - err = true; // Handled below. - } - else - { - // The spec says the response will never contain any top-level fields - // other than data, errors, and extensions. - // - if (p.name () != "extensions") - { - throw_json (p, - "unexpected top-level GraphQL response field: '" + - p.name () + '\''); - } - - p.next_expect_value_skip (); - } - } - - // If the `errors` field was present in the response, error(s) occurred - // before or during execution of the operation. - // - // If the `data` field was not present the errors are request errors which - // occur before execution and are typically the client's fault. - // - // If the `data` field was also present in the response the errors are - // field errors which occur during execution and are typically the GraphQL - // endpoint's fault, and some fields in `data` that should not be are - // likely to be null. - // - if (err) - { - if (dat) - { - // @@ TODO: Consider parsing partial data? - // - throw runtime_error ("field error(s) received from GraphQL endpoint; " - "incomplete data received"); - } - else - throw runtime_error ("request error(s) received from GraphQL endpoint"); - } - } - - // Parse a response to a check_run GraphQL mutation such as `createCheckRun` - // or `updateCheckRun`. - // - // Example response (only the part we need to parse here): - // - // { - // "cr0": { - // "checkRun": { - // "id": "CR_kwDOLc8CoM8AAAAFQ5GqPg", - // "name": "libb2/0.98.1+2/x86_64-linux-gnu/linux_debian_12-gcc_13.1-O3/default/dev/0.17.0-a.1", - // "status": "QUEUED" - // } - // }, - // "cr1": { - // "checkRun": { - // "id": "CR_kwDOLc8CoM8AAAAFQ5GqhQ", - // "name": "libb2/0.98.1+2/x86_64-linux-gnu/linux_debian_12-gcc_13.1/default/dev/0.17.0-a.1", - // "status": "QUEUED" - // } - // } - // } - // - // @@ TODO Handle response errors properly. - // - static vector<check_run> - gq_parse_response_check_runs (json::parser& p) - { - using event = json::event; - - vector<check_run> r; - - parse_graphql_response ( - p, - [&r] (json::parser& p) - { - p.next_expect (event::begin_object); - - // Parse the "cr0".."crN" members (field aliases). - // - while (p.next_expect (event::name, event::end_object)) - { - // Parse `"crN": { "checkRun":`. - // - if (p.name () != "cr" + to_string (r.size ())) - throw_json (p, "unexpected field alias: '" + p.name () + '\''); - p.next_expect (event::begin_object); - p.next_expect_name ("checkRun"); - - r.emplace_back (p); // Parse the check_run object. - - p.next_expect (event::end_object); // Parse end of crN object. - } - }); - - // Our requests always operate on at least one check run so if there were - // none in the data field something went wrong. - // - if (r.empty ()) - throw_json (p, "data object is empty"); - - return r; - } - - // GraphQL serialization functions. - // - // The GraphQL spec: - // https://spec.graphql.org/ - // - // The GitHub GraphQL API reference: - // https://docs.github.com/en/graphql/reference/ - // - - // Check that a string is a valid GraphQL name. - // - // GraphQL names can contain only alphanumeric characters and underscores - // and cannot begin with a digit (so basically a C identifier). - // - // Return the name or throw invalid_argument if it is invalid. - // - // @@ TODO: dangerous API. - // - static const string& - gq_name (const string& v) - { - if (v.empty () || digit (v[0])) - throw invalid_argument ("invalid GraphQL name: '" + v + '\''); - - for (char c: v) - { - if (!alnum (c) && c != '_') - { - throw invalid_argument ("invalid character in GraphQL name: '" + c + - '\''); - } - } - - return v; - } - - // Serialize a string to GraphQL. - // - // Return the serialized string or throw invalid_argument if the string is - // invalid. - // - static string - gq_str (const string& v) - { - // GraphQL strings are the same as JSON strings so we use the JSON - // serializer. - // - string b; - json::buffer_serializer s (b); - - try - { - s.value (v); - } - catch (const json::invalid_json_output&) - { - throw invalid_argument ("invalid GraphQL string: '" + v + '\''); - } - - return b; - } - - // Serialize an int to GraphQL. - // -#if 0 - static string - gq_int (uint64_t v) - { - string b; - json::buffer_serializer s (b); - s.value (v); - return b; - } -#endif - - // Serialize a boolean to GraphQL. - // - static inline string - gq_bool (bool v) - { - return v ? "true" : "false"; - } - - // Check that a string is a valid GraphQL enum value. - // - // GraphQL enum values can be any GraphQL name except for `true`, `false`, - // or `null`. - // - // Return the enum value or throw invalid_argument if it is invalid. - // - // @@ TODO: dangerous API. - // - static const string& - gq_enum (const string& v) - { - if (v == "true" || v == "false" || v == "null") - throw invalid_argument ("invalid GraphQL enum value: '" + v + '\''); - - return gq_name (v); - } -} |