aboutsummaryrefslogtreecommitdiff
path: root/mod/mod-ci-github-qg.cxx
diff options
context:
space:
mode:
authorFrancois Kritzinger <francois@codesynthesis.com>2024-04-17 11:09:46 +0200
committerFrancois Kritzinger <francois@codesynthesis.com>2024-06-05 09:12:46 +0200
commit3f2a23ab62b5cbb940db7f925b3fff84d688285f (patch)
treef9340d9efac04cd4b884427a0b19c63b3e938c89 /mod/mod-ci-github-qg.cxx
parentc92142ce729c5ef38b25465dd2e7c2d67df05172 (diff)
Get restructure to compile
Diffstat (limited to 'mod/mod-ci-github-qg.cxx')
-rw-r--r--mod/mod-ci-github-qg.cxx509
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);
- }
-}