aboutsummaryrefslogtreecommitdiff
path: root/mod/mod-ci-github.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'mod/mod-ci-github.cxx')
-rw-r--r--mod/mod-ci-github.cxx1049
1 files changed, 530 insertions, 519 deletions
diff --git a/mod/mod-ci-github.cxx b/mod/mod-ci-github.cxx
index 7497835..de7d792 100644
--- a/mod/mod-ci-github.cxx
+++ b/mod/mod-ci-github.cxx
@@ -81,668 +81,679 @@ using namespace butl;
using namespace web;
using namespace brep::cli;
-// @@ Let's move everything to the brep namespace and get rid of
-// explicit brep:: qualifications.
-
-brep::ci_github::
-ci_github (const ci_github& r)
- : handler (r),
- options_ (r.initialized_ ? r.options_ : nullptr)
+namespace brep
{
-}
+ // GitHub-specific types.
+ //
+ // @@ TMP A brep::repository here would clash with the pre-existing
+ // brep::repository.
+ //
+ namespace gh
+ {
+ // The "check_suite" object within a check_quite webhook request.
+ //
+ struct check_suite
+ {
+ uint64_t id;
+ string head_branch;
+ string head_sha;
+ string before;
+ string after;
-void brep::ci_github::
-init (scanner& s)
-{
- options_ = make_shared<options::ci_github> (
- s, unknown_mode::fail, unknown_mode::fail);
-}
+ explicit
+ check_suite (json::parser&);
-// The "check_suite" object within a check_quite webhook request.
-//
-struct check_suite
-{
- uint64_t id;
- string head_branch;
- string head_sha;
- string before;
- string after;
+ check_suite () = default;
+ };
+
+ struct repository
+ {
+ string name;
+ string full_name;
+ string default_branch;
- explicit
- check_suite (json::parser&);
+ explicit
+ repository (json::parser&);
- check_suite () = default;
-};
+ repository () = default;
+ };
-struct repository
-{
- string name;
- string full_name;
- string default_branch;
+ struct installation
+ {
+ uint64_t id;
- explicit
- repository (json::parser&);
+ explicit
+ installation (json::parser&);
- repository () = default;
-};
+ installation () = default;
+ };
-struct installation
-{
- uint64_t id;
+ struct check_suite_event
+ {
+ string action;
+ gh::check_suite check_suite;
+ gh::repository repository;
+ gh::installation installation;
- explicit
- installation (json::parser&);
+ explicit
+ check_suite_event (json::parser&);
- installation () = default;
-};
+ check_suite_event () = default;
+ };
-struct check_suite_event
-{
- string action;
- ::check_suite check_suite;
- ::repository repository;
- ::installation installation;
+ struct installation_access_token
+ {
+ string token;
+ timestamp expires_at;
- explicit
- check_suite_event (json::parser&);
+ explicit
+ installation_access_token (json::parser&);
- check_suite_event () = default;
-};
+ installation_access_token () = default;
+ };
-struct installation_access_token
-{
- string token;
- timestamp expires_at;
+ static ostream&
+ operator<< (ostream&, const check_suite&);
- explicit
- installation_access_token (json::parser&);
+ static ostream&
+ operator<< (ostream&, const repository&);
- installation_access_token () = default;
-};
+ static ostream&
+ operator<< (ostream&, const installation&);
-static ostream&
-operator<< (ostream&, const check_suite&);
+ static ostream&
+ operator<< (ostream&, const check_suite_event&);
-static ostream&
-operator<< (ostream&, const repository&);
+ static ostream&
+ operator<< (ostream&, const installation_access_token&);
+ }
-static ostream&
-operator<< (ostream&, const installation&);
+ using namespace gh;
-static ostream&
-operator<< (ostream&, const check_suite_event&);
+ ci_github::
+ ci_github (const ci_github& r)
+ : handler (r),
+ options_ (r.initialized_ ? r.options_ : nullptr)
+ {
+ }
-static ostream&
-operator<< (ostream&, const installation_access_token&);
+ void ci_github::
+ init (scanner& s)
+ {
+ options_ = make_shared<options::ci_github> (
+ s, unknown_mode::fail, unknown_mode::fail);
+ }
-// Read the HTTP response status code from an input stream.
-//
-// Parse the status code from the HTTP status line, skip over the remaining
-// headers (leaving the stream at the beginning of the response body), and
-// return the status code.
-//
-// Throw system_error(EINVAL) if the status line could not be parsed.
-//
-// Note that this implementation is almost identical to that of bpkg's
-// start_curl() function in fetch.cxx.
-//
-static uint16_t
-read_status_code (ifdstream& in)
-{
- // After getting the status code we will read until the empty line
- // (containing just CRLF). Not being able to reach such a line is an error,
- // which is the reason for the exception mask choice. When done, we will
- // restore the original exception mask.
+ // Read the HTTP response status code from an input stream.
//
- // @@ TMP Presumably curl would already have failed if the server's
- // response was malformed, right? So if we get here the only way to
- // get EOF would be an I/O error?
+ // Parse the status code from the HTTP status line, skip over the remaining
+ // headers (leaving the stream at the beginning of the response body), and
+ // return the status code.
//
- ifdstream::iostate es (in.exceptions ());
-
- in.exceptions (
- ifdstream::badbit | ifdstream::failbit | ifdstream::eofbit);
-
- // Parse and return the HTTP status code. Return 0 if the argument is
- // invalid.
+ // Throw system_error(EINVAL) if the status line could not be parsed.
//
- auto status_code = [] (const string& s)
+ // Note that this implementation is almost identical to that of bpkg's
+ // start_curl() function in fetch.cxx.
+ //
+ static uint16_t
+ read_status_code (ifdstream& in)
{
- char* e (nullptr);
- unsigned long c (strtoul (s.c_str (), &e, 10)); // Can't throw.
- assert (e != nullptr);
+ // After getting the status code we will read until the empty line
+ // (containing just CRLF). Not being able to reach such a line is an error,
+ // which is the reason for the exception mask choice. When done, we will
+ // restore the original exception mask.
+ //
+ // @@ TMP Presumably curl would already have failed if the server's
+ // response was malformed, right? So if we get here the only way to
+ // get EOF would be an I/O error?
+ //
+ ifdstream::iostate es (in.exceptions ());
- return *e == '\0' && c >= 100 && c < 600
- ? static_cast<uint16_t> (c)
- : 0;
- };
+ in.exceptions (
+ ifdstream::badbit | ifdstream::failbit | ifdstream::eofbit);
- // Read the CRLF-terminated line from the stream stripping the trailing
- // CRLF.
- //
- auto read_line = [&in] ()
- {
- string l;
- getline (in, l); // Strips the trailing LF (0xA).
+ // Parse and return the HTTP status code. Return 0 if the argument is
+ // invalid.
+ //
+ auto status_code = [] (const string& s)
+ {
+ char* e (nullptr);
+ unsigned long c (strtoul (s.c_str (), &e, 10)); // Can't throw.
+ assert (e != nullptr);
+
+ return *e == '\0' && c >= 100 && c < 600
+ ? static_cast<uint16_t> (c)
+ : 0;
+ };
- // Note that on POSIX CRLF is not automatically translated into LF, so
- // we need to strip CR (0xD) manually.
+ // Read the CRLF-terminated line from the stream stripping the trailing
+ // CRLF.
//
- if (!l.empty () && l.back () == '\r')
- l.pop_back ();
+ auto read_line = [&in] ()
+ {
+ string l;
+ getline (in, l); // Strips the trailing LF (0xA).
- return l;
- };
+ // Note that on POSIX CRLF is not automatically translated into LF, so
+ // we need to strip CR (0xD) manually.
+ //
+ if (!l.empty () && l.back () == '\r')
+ l.pop_back ();
- auto read_status = [&read_line, &status_code] () -> uint16_t
- {
- string l (read_line ());
+ return l;
+ };
- for (;;) // Breakout loop.
+ auto read_status = [&read_line, &status_code] () -> uint16_t
{
- if (l.compare (0, 5, "HTTP/") != 0)
- break;
+ string l (read_line ());
+
+ for (;;) // Breakout loop.
+ {
+ if (l.compare (0, 5, "HTTP/") != 0)
+ break;
- size_t p (l.find (' ', 5)); // The protocol end.
- if (p == string::npos)
- break;
+ size_t p (l.find (' ', 5)); // The protocol end.
+ if (p == string::npos)
+ break;
- p = l.find_first_not_of (' ', p + 1); // The code start.
- if (p == string::npos)
- break;
+ p = l.find_first_not_of (' ', p + 1); // The code start.
+ if (p == string::npos)
+ break;
- size_t e (l.find (' ', p + 1)); // The code end.
- if (e == string::npos)
- break;
+ size_t e (l.find (' ', p + 1)); // The code end.
+ if (e == string::npos)
+ break;
- uint16_t c (status_code (string (l, p, e - p)));
- if (c == 0)
- break;
+ uint16_t c (status_code (string (l, p, e - p)));
+ if (c == 0)
+ break;
- return c;
- }
+ return c;
+ }
- throw_generic_error (
+ throw_generic_error (
EINVAL,
("invalid HTTP response status line '" + l + "'").c_str ());
- };
-
- uint16_t sc (read_status ());
+ };
- if (sc == 100)
- {
- while (!read_line ().empty ()) ; // Skips the interim response.
- sc = read_status (); // Reads the final status code.
- }
+ uint16_t sc (read_status ());
- while (!read_line ().empty ()) ; // Skips headers.
-
- in.exceptions (es);
+ if (sc == 100)
+ {
+ while (!read_line ().empty ()) ; // Skips the interim response.
+ sc = read_status (); // Reads the final status code.
+ }
- return sc;
-}
+ while (!read_line ().empty ()) ; // Skips headers.
-// Send a POST request to the GitHub API endpoint `ep`, parse GitHub's JSON
-// response into `rs` (only for 200 codes), and return the HTTP status code.
-//
-// The endpoint `ep` should not have a leading slash.
-//
-// Pass additional HTTP headers in `hdrs`. For example:
-//
-// "HeaderName: header value"
-//
-template<typename T>
-static uint16_t
-github_post (T& rs, const string& ep, const brep::strings& hdrs)
-{
- // Convert the header values to curl header option/value pairs.
- //
- brep::strings hdr_opts;
+ in.exceptions (es);
- for (const string& h: hdrs)
- {
- hdr_opts.push_back ("--header");
- hdr_opts.push_back (h);
+ return sc;
}
- // Run curl.
+ // Send a POST request to the GitHub API endpoint `ep`, parse GitHub's JSON
+ // response into `rs` (only for 200 codes), and return the HTTP status code.
+ //
+ // The endpoint `ep` should not have a leading slash.
+ //
+ // Pass additional HTTP headers in `hdrs`. For example:
//
- try
+ // "HeaderName: header value"
+ //
+ template<typename T>
+ static uint16_t
+ github_post (T& rs, const string& ep, const strings& hdrs)
{
- // Pass --include to print the HTTP status line (followed by the response
- // headers) so that we can get the response status code.
- //
- // Pass --no-fail to disable the --fail option added by butl::curl which
- // causes curl to exit with status 22 in case of an error HTTP response
- // status code (>= 400) otherwise we can't get the status code.
- //
- // Note that butl::curl also adds --location to make curl follow redirects
- // (which is recommended by GitHub).
- //
- // The API version `2022-11-28` is the only one currently supported. If
- // the X-GitHub-Api-Version header is not passed this version will be
- // chosen by default.
+ // Convert the header values to curl header option/value pairs.
//
- // @@ TMP Although this request does not have a body, can't pass a nullfd
- // stdin because it will cause butl::curl to fail if the method is
- // POST.
- //
- fdpipe errp (fdopen_pipe ()); // stderr pipe.
-
- curl c (path ("-"),
- path ("-"), // Write response to curl::in.
- process::pipe (errp.in.get (), move (errp.out)),
- curl::post, "https://api.github.com/" + ep,
- "--include", // Output response headers for status code.
- "--no-fail", // Don't exit with 22 if response status code >= 400.
- "--header", "Accept: application/vnd.github+json",
- "--header", "X-GitHub-Api-Version: 2022-11-28",
- move (hdr_opts));
+ strings hdr_opts;
- ifdstream err (move (errp.in));
+ for (const string& h: hdrs)
+ {
+ hdr_opts.push_back ("--header");
+ hdr_opts.push_back (h);
+ }
- // Parse the HTTP response.
+ // Run curl.
//
- int sc; // Status code.
try
{
- ifdstream in (c.in.release (), fdstream_mode::skip);
+ // Pass --include to print the HTTP status line (followed by the response
+ // headers) so that we can get the response status code.
+ //
+ // Pass --no-fail to disable the --fail option added by butl::curl which
+ // causes curl to exit with status 22 in case of an error HTTP response
+ // status code (>= 400) otherwise we can't get the status code.
+ //
+ // Note that butl::curl also adds --location to make curl follow redirects
+ // (which is recommended by GitHub).
+ //
+ // The API version `2022-11-28` is the only one currently supported. If
+ // the X-GitHub-Api-Version header is not passed this version will be
+ // chosen by default.
+ //
+ // @@ TMP Although this request does not have a body, can't pass a nullfd
+ // stdin because it will cause butl::curl to fail if the method is
+ // POST.
+ //
+ fdpipe errp (fdopen_pipe ()); // stderr pipe.
- c.out.close (); // No input required.
+ curl c (path ("-"),
+ path ("-"), // Write response to curl::in.
+ process::pipe (errp.in.get (), move (errp.out)),
+ curl::post, "https://api.github.com/" + ep,
+ "--include", // Output response headers for status code.
+ "--no-fail", // Don't exit with 22 if response status code >= 400.
+ "--header", "Accept: application/vnd.github+json",
+ "--header", "X-GitHub-Api-Version: 2022-11-28",
+ move (hdr_opts));
- // Read HTTP status code.
- //
- sc = read_status_code (in);
+ ifdstream err (move (errp.in));
- // Parse the response body if the status code is in the 200 range.
+ // Parse the HTTP response.
//
- if (sc >= 200 && sc < 300)
+ int sc; // Status code.
+ try
{
- json::parser p (in, ep);
- rs = T (p);
- }
+ ifdstream in (c.in.release (), fdstream_mode::skip);
- in.close ();
- }
- catch (const brep::io_error& e)
- {
- // If the process exits with non-zero status, assume the IO error is due
- // to that and fall through.
- //
- if (c.wait ())
+ c.out.close (); // No input required.
+
+ // Read HTTP status code.
+ //
+ sc = read_status_code (in);
+
+ // Parse the response body if the status code is in the 200 range.
+ //
+ if (sc >= 200 && sc < 300)
+ {
+ json::parser p (in, ep);
+ rs = T (p);
+ }
+
+ in.close ();
+ }
+ catch (const io_error& e)
{
- throw_generic_error (
- e.code ().value (),
- (string ("unable to read curl stdout: ") + e.what ()).c_str ());
+ // If the process exits with non-zero status, assume the IO error is due
+ // to that and fall through.
+ //
+ if (c.wait ())
+ {
+ throw_generic_error (
+ e.code ().value (),
+ (string ("unable to read curl stdout: ") + e.what ()).c_str ());
+ }
}
- }
- catch (const json::invalid_json_input& e)
- {
- // If the process exits with non-zero status, assume the JSON error is
- // due to that and fall through.
- //
- if (c.wait ())
+ catch (const json::invalid_json_input& e)
{
- throw_generic_error (
- EINVAL,
- (string ("malformed JSON response from GitHub: ") + e.what ())
- .c_str ());
+ // If the process exits with non-zero status, assume the JSON error is
+ // due to that and fall through.
+ //
+ if (c.wait ())
+ {
+ throw_generic_error (
+ EINVAL,
+ (string ("malformed JSON response from GitHub: ") + e.what ())
+ .c_str ());
+ }
}
- }
- if (!c.wait ())
- {
- string et (err.read_text ());
- throw_generic_error (EINVAL,
- ("non-zero curl exit status: " + et).c_str ());
- }
+ if (!c.wait ())
+ {
+ string et (err.read_text ());
+ throw_generic_error (EINVAL,
+ ("non-zero curl exit status: " + et).c_str ());
+ }
- err.close ();
+ err.close ();
- return sc;
- }
- catch (const process_error& e)
- {
- throw_generic_error (
+ return sc;
+ }
+ catch (const process_error& e)
+ {
+ throw_generic_error (
e.code ().value (),
(string ("unable to execute curl:") + e.what ()).c_str ());
+ }
+ catch (const io_error& e)
+ {
+ // Unable to read diagnostics from stderr.
+ //
+ throw_generic_error (
+ e.code ().value (),
+ (string ("unable to read curl stderr : ") + e.what ()).c_str ());
+ }
}
- catch (const brep::io_error& e)
- {
- // Unable to read diagnostics from stderr.
- //
- throw_generic_error (
- e.code ().value (),
- (string ("unable to read curl stderr : ") + e.what ()).c_str ());
- }
-}
-
-bool brep::ci_github::
-handle (request& rq, response& rs)
-{
- using namespace bpkg;
- HANDLER_DIAG;
+ bool ci_github::
+ handle (request& rq, response&)
+ {
+ using namespace bpkg;
- // @@ TODO
- if (false)
- throw invalid_request (404, "CI request submission disabled");
+ HANDLER_DIAG;
- // Process headers.
- //
- string event;
- {
- bool content_type (false);
+ // @@ TODO
+ if (false)
+ throw invalid_request (404, "CI request submission disabled");
- for (const name_value& h: rq.headers ())
+ // Process headers.
+ //
+ string event;
{
- if (icasecmp (h.name, "x-github-delivery") == 0)
- {
- // @@ TODO Check that delivery UUID has not been received before
- // (replay attack).
- }
- else if (icasecmp (h.name, "content-type") == 0)
- {
- if (!h.value)
- throw invalid_request (400, "missing content-type value");
+ bool content_type (false);
- if (icasecmp (*h.value, "application/json") != 0)
+ for (const name_value& h: rq.headers ())
+ {
+ if (icasecmp (h.name, "x-github-delivery") == 0)
{
- throw invalid_request (400,
- "invalid content-type value: '" + *h.value +
- '\'');
+ // @@ TODO Check that delivery UUID has not been received before
+ // (replay attack).
}
+ else if (icasecmp (h.name, "content-type") == 0)
+ {
+ if (!h.value)
+ throw invalid_request (400, "missing content-type value");
- content_type = true;
- }
- else if (icasecmp (h.name, "x-github-event") == 0)
- {
- if (!h.value)
- throw invalid_request (400, "missing x-github-event value");
-
- event = *h.value;
- }
- }
+ if (icasecmp (*h.value, "application/json") != 0)
+ {
+ throw invalid_request (400,
+ "invalid content-type value: '" + *h.value +
+ '\'');
+ }
- if (!content_type)
- throw invalid_request (400, "missing content-type header");
+ content_type = true;
+ }
+ else if (icasecmp (h.name, "x-github-event") == 0)
+ {
+ if (!h.value)
+ throw invalid_request (400, "missing x-github-event value");
- if (event.empty ())
- throw invalid_request (400, "missing x-github-event header");
- }
+ event = *h.value;
+ }
+ }
- // There is an event (specified in the x-github-event header) and each event
- // contains a bunch of actions (specified in the JSON request body).
- //
- // Note: "GitHub continues to add new event types and new actions to
- // existing event types." As a result we ignore known actions that we are
- // not interested in and log and ignore unknown actions. The thinking here
- // is that we want be "notified" of new actions at which point we can decide
- // whether to ignore them or to handle.
- //
- if (event == "check_suite")
- {
- check_suite_event cs;
- try
- {
- json::parser p (rq.content (64 * 1024), "check_suite webhook");
+ if (!content_type)
+ throw invalid_request (400, "missing content-type header");
- cs = check_suite_event (p);
- }
- catch (const json::invalid_json_input& e)
- {
- throw invalid_request (400, "malformed JSON in request body");
+ if (event.empty ())
+ throw invalid_request (400, "missing x-github-event header");
}
- // @@ TODO: log and ignore unknown.
+ // There is an event (specified in the x-github-event header) and each event
+ // contains a bunch of actions (specified in the JSON request body).
//
- if (cs.action == "requested")
- {
- }
- else if (cs.action == "rerequested")
- {
- // Someone manually requested to re-run the check runs in this check
- // suite.
- }
- else if (cs.action == "completed")
+ // Note: "GitHub continues to add new event types and new actions to
+ // existing event types." As a result we ignore known actions that we are
+ // not interested in and log and ignore unknown actions. The thinking here
+ // is that we want be "notified" of new actions at which point we can decide
+ // whether to ignore them or to handle.
+ //
+ if (event == "check_suite")
{
- // GitHub thinks that "all the check runs in this check suite have
- // completed and a conclusion is available". Looks like this one we
- // ignore?
- }
- else
- throw invalid_request (400, "unsupported action: " + cs.action);
+ check_suite_event cs;
+ try
+ {
+ json::parser p (rq.content (64 * 1024), "check_suite webhook");
- cout << "<check_suite webhook>" << endl << cs << endl;
+ cs = check_suite_event (p);
+ }
+ catch (const json::invalid_json_input& e)
+ {
+ throw invalid_request (400, "malformed JSON in request body");
+ }
- string jwt;
- try
- {
- // Set token's "issued at" time 60 seconds in the past to combat clock
- // drift (as recommended by GitHub).
+ // @@ TODO: log and ignore unknown.
//
- jwt = gen_jwt (
+ if (cs.action == "requested")
+ {
+ }
+ else if (cs.action == "rerequested")
+ {
+ // Someone manually requested to re-run the check runs in this check
+ // suite.
+ }
+ else if (cs.action == "completed")
+ {
+ // GitHub thinks that "all the check runs in this check suite have
+ // completed and a conclusion is available". Looks like this one we
+ // ignore?
+ }
+ else
+ throw invalid_request (400, "unsupported action: " + cs.action);
+
+ cout << "<check_suite webhook>" << endl << cs << endl;
+
+ string jwt;
+ try
+ {
+ // Set token's "issued at" time 60 seconds in the past to combat clock
+ // drift (as recommended by GitHub).
+ //
+ jwt = gen_jwt (
*options_,
options_->ci_github_app_private_key (),
to_string (options_->ci_github_app_id ()),
chrono::seconds (options_->ci_github_jwt_validity_period ()),
chrono::seconds (60));
- cout << "JWT: " << jwt << endl;
- }
- catch (const system_error& e)
- {
- fail << "unable to generate JWT (errno=" << e.code () << "): "
- << e.what ();
- }
-
- // Authenticate to GitHub as an app installation.
- //
- installation_access_token iat;
- try
- {
- // API endpoint.
- //
- string ep ("app/installations/" + to_string (cs.installation.id) +
- "/access_tokens");
-
- int sc (github_post (iat, ep, strings {"Authorization: Bearer " + jwt}));
+ cout << "JWT: " << jwt << endl;
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to generate JWT (errno=" << e.code () << "): "
+ << e.what ();
+ }
- // Possible response status codes from the access_tokens endpoint:
- //
- // 201 Created
- // 401 Requires authentication
- // 403 Forbidden
- // 404 Resource not found
- // 422 Validation failed, or the endpoint has been spammed.
+ // Authenticate to GitHub as an app installation.
//
- // Note that the payloads of non-201 status codes are undocumented.
- //
- if (sc != 201)
+ installation_access_token iat;
+ try
+ {
+ // API endpoint.
+ //
+ string ep ("app/installations/" + to_string (cs.installation.id) +
+ "/access_tokens");
+
+ int sc (github_post (iat, ep, strings {"Authorization: Bearer " + jwt}));
+
+ // Possible response status codes from the access_tokens endpoint:
+ //
+ // 201 Created
+ // 401 Requires authentication
+ // 403 Forbidden
+ // 404 Resource not found
+ // 422 Validation failed, or the endpoint has been spammed.
+ //
+ // Note that the payloads of non-201 status codes are undocumented.
+ //
+ if (sc != 201)
+ {
+ throw runtime_error ("error status code received from GitHub: " +
+ to_string (sc));
+ }
+ }
+ catch (const system_error& e)
{
- throw runtime_error ("error status code received from GitHub: " +
- to_string (sc));
+ fail << "unable to get installation access token (errno=" << e.code ()
+ << "): " << e.what ();
}
+
+ cout << endl << "<installation_access_token>" << endl << iat << endl;
+
+ return true;
}
- catch (const system_error& e)
+ else if (event == "pull_request")
{
- fail << "unable to get installation access token (errno=" << e.code ()
- << "): " << e.what ();
+ throw invalid_request (501, "pull request events not implemented yet");
}
-
- cout << endl << "<installation_access_token>" << endl << iat << endl;
-
- return true;
- }
- else if (event == "pull_request")
- {
- throw invalid_request (501, "pull request events not implemented yet");
+ else
+ throw invalid_request (400, "unexpected event: '" + event + "'");
}
- else
- throw invalid_request (400, "unexpected event: '" + event + "'");
-}
-using event = json::event;
+ using event = json::event;
-// check_suite
-//
-check_suite::
-check_suite (json::parser& p)
-{
- p.next_expect (event::begin_object);
-
- // Skip unknown/uninteresting members.
+ // check_suite
//
- while (p.next_expect (event::name, event::end_object))
+ gh::check_suite::
+ check_suite (json::parser& p)
{
- const string& n (p.name ());
-
- if (n == "id") id = p.next_expect_number<uint64_t> ();
- else if (n == "head_branch") head_branch = p.next_expect_string ();
- else if (n == "head_sha") head_sha = p.next_expect_string ();
- else if (n == "before") before = p.next_expect_string ();
- else if (n == "after") after = p.next_expect_string ();
- else p.next_expect_value_skip ();
- }
-}
+ p.next_expect (event::begin_object);
-static ostream&
-operator<< (ostream& os, const check_suite& cs)
-{
- os << "id: " << cs.id << endl
- << "head_branch: " << cs.head_branch << endl
- << "head_sha: " << cs.head_sha << endl
- << "before: " << cs.before << endl
- << "after: " << cs.after << endl;
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
+ {
+ const string& n (p.name ());
+
+ if (n == "id") id = p.next_expect_number<uint64_t> ();
+ else if (n == "head_branch") head_branch = p.next_expect_string ();
+ else if (n == "head_sha") head_sha = p.next_expect_string ();
+ else if (n == "before") before = p.next_expect_string ();
+ else if (n == "after") after = p.next_expect_string ();
+ else p.next_expect_value_skip ();
+ }
+ }
- return os;
-}
+ static ostream&
+ gh::operator<< (ostream& os, const check_suite& cs)
+ {
+ os << "id: " << cs.id << endl
+ << "head_branch: " << cs.head_branch << endl
+ << "head_sha: " << cs.head_sha << endl
+ << "before: " << cs.before << endl
+ << "after: " << cs.after << endl;
-// repository
-//
-repository::
-repository (json::parser& p)
-{
- p.next_expect (event::begin_object);
+ return os;
+ }
- // Skip unknown/uninteresting members.
+ // repository
//
- while (p.next_expect (event::name, event::end_object))
+ gh::repository::
+ repository (json::parser& p)
{
- const string& n (p.name ());
-
- if (n == "name") name = p.next_expect_string ();
- else if (n == "full_name") full_name = p.next_expect_string ();
- else if (n == "default_branch") default_branch = p.next_expect_string ();
- else p.next_expect_value_skip ();
- }
-}
+ p.next_expect (event::begin_object);
-static ostream&
-operator<< (ostream& os, const repository& rep)
-{
- os << "name: " << rep.name << endl
- << "full_name: " << rep.full_name << endl
- << "default_branch: " << rep.default_branch << endl;
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
+ {
+ const string& n (p.name ());
- return os;
-}
+ if (n == "name") name = p.next_expect_string ();
+ else if (n == "full_name") full_name = p.next_expect_string ();
+ else if (n == "default_branch") default_branch = p.next_expect_string ();
+ else p.next_expect_value_skip ();
+ }
+ }
-// installation
+ static ostream&
+ gh::operator<< (ostream& os, const repository& rep)
+ {
+ os << "name: " << rep.name << endl
+ << "full_name: " << rep.full_name << endl
+ << "default_branch: " << rep.default_branch << endl;
-installation::
-installation (json::parser& p)
-{
- p.next_expect (event::begin_object);
+ return os;
+ }
- // Skip unknown/uninteresting members.
+ // installation
//
- while (p.next_expect (event::name, event::end_object))
+ gh::installation::
+ installation (json::parser& p)
{
- const string& n (p.name ());
+ p.next_expect (event::begin_object);
- if (n == "id") id = p.next_expect_number<uint64_t> ();
- else p.next_expect_value_skip ();
- }
-}
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
+ {
+ const string& n (p.name ());
-static ostream&
-operator<< (ostream& os, const installation& i)
-{
- os << "id: " << i.id << endl;
+ if (n == "id") id = p.next_expect_number<uint64_t> ();
+ else p.next_expect_value_skip ();
+ }
+ }
- return os;
-}
+ static ostream&
+ gh::operator<< (ostream& os, const installation& i)
+ {
+ os << "id: " << i.id << endl;
-// check_suite_event
-//
-check_suite_event::
-check_suite_event (json::parser& p)
-{
- p.next_expect (event::begin_object);
+ return os;
+ }
- // Skip unknown/uninteresting members.
+ // check_suite_event
//
- while (p.next_expect (event::name, event::end_object))
+ gh::check_suite_event::
+ check_suite_event (json::parser& p)
{
- const string& n (p.name ());
+ p.next_expect (event::begin_object);
- if (n == "action") action = p.next_expect_string ();
- else if (n == "check_suite") check_suite = ::check_suite (p);
- else if (n == "repository") repository = ::repository (p);
- else if (n == "installation") installation = ::installation (p);
- else p.next_expect_value_skip ();
- }
-}
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
+ {
+ const string& n (p.name ());
-static ostream&
-operator<< (ostream& os, const check_suite_event& cs)
-{
- os << "action: " << cs.action << endl;
- os << "<check_suite>" << endl << cs.check_suite;
- os << "<repository>" << endl << cs.repository;
- os << "<installation>" << endl << cs.installation;
+ if (n == "action") action = p.next_expect_string ();
+ else if (n == "check_suite") check_suite = gh::check_suite (p);
+ else if (n == "repository") repository = gh::repository (p);
+ else if (n == "installation") installation = gh::installation (p);
+ else p.next_expect_value_skip ();
+ }
+ }
- return os;
-}
+ static ostream&
+ gh::operator<< (ostream& os, const check_suite_event& cs)
+ {
+ os << "action: " << cs.action << endl;
+ os << "<check_suite>" << endl << cs.check_suite;
+ os << "<repository>" << endl << cs.repository;
+ os << "<installation>" << endl << cs.installation;
-// installation_access_token
-//
-// Example JSON:
-//
-// {
-// "token": "ghs_Py7TPcsmsITeVCAWeVtD8RQs8eSos71O5Nzp",
-// "expires_at": "2024-02-15T16:16:38Z",
-// ...
-// }
-//
-installation_access_token::
-installation_access_token (json::parser& p)
-{
- p.next_expect (event::begin_object);
+ return os;
+ }
- // Skip unknown/uninteresting members.
+ // installation_access_token
+ //
+ // Example JSON:
//
- while (p.next_expect (event::name, event::end_object))
+ // {
+ // "token": "ghs_Py7TPcsmsITeVCAWeVtD8RQs8eSos71O5Nzp",
+ // "expires_at": "2024-02-15T16:16:38Z",
+ // ...
+ // }
+ //
+ gh::installation_access_token::
+ installation_access_token (json::parser& p)
{
- const string& n (p.name ());
+ p.next_expect (event::begin_object);
- if (n == "token")
- token = p.next_expect_string ();
- else if (n == "expires_at")
+ // Skip unknown/uninteresting members.
+ //
+ while (p.next_expect (event::name, event::end_object))
{
- const string& s (p.next_expect_string ());
- expires_at = from_string (s.c_str (), "%Y-%m-%dT%TZ", false /* local */);
+ const string& n (p.name ());
+
+ if (n == "token")
+ token = p.next_expect_string ();
+ else if (n == "expires_at")
+ {
+ const string& s (p.next_expect_string ());
+ expires_at = from_string (s.c_str (), "%Y-%m-%dT%TZ", false /* local */);
+ }
+ else p.next_expect_value_skip ();
}
- else p.next_expect_value_skip ();
}
-}
-static ostream&
-operator<< (ostream& os, const installation_access_token& t)
-{
- os << "token: " << t.token << endl;
- os << "expires_at: " << t.expires_at << endl;
+ static ostream&
+ gh::operator<< (ostream& os, const installation_access_token& t)
+ {
+ os << "token: " << t.token << endl;
+ os << "expires_at: ";
+ butl::operator<< (os, t.expires_at) << endl;
- return os;
+ return os;
+ }
}