Posts Tagged ‘cpp’

REST API Development with Qt 6

January 14, 2026

This post describes an experiment using Qt 6.7’s REST APIs to explore Stripe’s payment model, and what I learned building a small desktop developer tool.

Recent Qt releases have included several conveniences for developing clients of remote REST APIs. I recently tried it out with the Stripe payments REST API to get to grips with the Qt REST API in the real world. The overloading of the term API is unhelpful, I find, but hopefully not too confusing here.

As with almost everything I try out, I created Qt desktop tooling as a developer aid to exploring the Stripe API and its behavior. Naming things is hard, but given that I want to put a “Q” in the name, googling “cute stripes” gives lots of hits about fashion, and the other too-obvious-to-say pun, I’ve pushed it to GitHub as “Qashmere“:

setAlternatingRowColors(true);

Developers using REST APIs will generally be familiar with existing tooling such as Postman and Bruno, for synthesizing calls to collections of REST APIs. Indeed, Qashmere uses the Stripe Postman JSON definition to present the collection of APIs and parameters. Such tools have scripting interfaces and state to create workflows that a client of the REST API needs to support, like “create a payment, get the id of the payment back from the REST API and then cancel the payment with the id”, or “create a payment, get the id of the payment back from the REST API and then confirm it by id with a given credit card”.

So why create Qashmere? In addition to REST APIs, Stripe maintains objects which change state over time. The objects remain at REST until acted on by an external force, and when such an action happens a notification is sent to clients about those state changes, giving them a chance to react. I wanted to be able to collect the REST requests/responses and the notified events and present them as they relate to the Stripe objects. Postman doesn’t know about events or about Stripe objects in particular, except that it is possible to write a script in Postman to extract the object which is part of a JSON payload. Postman also doesn’t know that if a Payment Intent is created, there are a subset of next steps which could be in a workflow, such as cancel, capture or confirm payment etc.

Something that I discovered in the course of trying this out is that when I confirm a Payment Intent, a new Charge object is created and sent to me with the event notification system. Experimental experiences like that help build intuition.

Stripe operates with real money, but it also provides for sandboxes where synthetic payments, customers etc can be created and processed with synthetic payment methods and cards. As Qashmere is only useful as a developer tool or learning aid, it only works with Stripe sandboxes.

Events from Stripe are sent to pre-configured web servers owned by the client. The web servers need to have a public IP address, which is obviously not appropriate for a desktop application. A WebSocket API would be more suitable and indeed the stripe cli tool uses a WebSocket to receive events, but the WebSocket protocol is not documented or stable. Luckily the stripe cli tool can be used to relay events to another HTTP server, so Qashmere runs a QHttpServer for that purpose.

Implementation with Qt REST API

The QRestReply wraps a QNetworkReply pointer and provides convenience API for accessing the HTTP return code and for creating a QJsonDocument from the body of the response. It must be created manually if using QNetworkAccessManager directly. However the new QRestAccessManager wraps a QNetworkAccessManager pointer, again to provide convenience APIs and overloads for making requests that are needed in REST APIs (though some less common verbs like OPTIONS and TRACE are not built-in). The QRestAccessManager has conveniences like overloads that provide a way to supply callbacks which already take the QRestReply wrapper object as a parameter. If using a QJsonDocument request overload, the “application/jsonContent-Type is automatically set in the header.

One of the inconveniences of QRestAccessManager is that in Qashmere I use an external definition of the REST API from the Postman definition which includes the HTTP method. Because the QRestAccessManager provides strongly typed API for making requests I need to do something like:

if (method == "POST") {
rest.post(request, requestData, this, replyHandler);
} else if (method == "GET") {
rest.get(request, this, replyHandler);
} else if (method == "DELETE") {
rest.deleteResource(request, this, replyHandler);
}

There is a sendCustomRequest class API which can be used with a string, but it does not have an overload for QJsonDocument, so the convenience of having the Content-Type header set is lost. This may be an oversight in the QRestAccessManager API.

Another missing feature is URL parameter interpolation. Many REST APIs are described as something like /v1/object/:object_id/cancel, and it would be convenient to have a safe way to interpolate the parameters into the URL, such as:

QUrl result = QRestAccessManager::interpolatePathParameters(
"/v1/accounts/:account_id/object/:object_id/cancel", {
{"account_id", "acc_1234"},
{"object_id", "obj_5678"}
}
);

This is needed to avoid bugs such as a user-supplied parameter containing a slash for example.

Coding Con Currency

In recent years I’ve been writing and reading more Typescript/Angular code which consumes REST services, and less C++. I’ve enjoyed the way Promises work in that environment, allowing sequences of REST requests, for example, to be easy to write and read. A test of a pseudo API could await on requests to complete and invoke the next one with something like:

requestFactory.setBaseURL("http://some_service.com");
async testWorkflow(username: string, password: string) {
const loginRequest = requestFactory.makeRequest("/login");
const loginRequestData = new Map();
loginRequestData.setParam("username", username);
loginRequestData.setParam("password", password);
const loginResponse = await requestAPI.post(
loginRequest, loginRequestData);
const bearerToken = loginResponse.getData();
requestAPI.setBearerToken(bearerToken);
const listingRequest = requestFactory.makeRequest("/list_items");
const listingResponse = await requestAPI.get(listingRequest);
const listing = JSON.parse(listingResponse.getData());
const firstItemRequest = requestFactory.makeRequest(
"/retrieve_item/:item_id",
{
item_id: listing[0].item_id
}
);
const firstItem = await requestAPI.get(firstItemRequest);
}

The availability of async functions and the Promise to await on make a test like this quite easy to write, and the in-application use of the API uses the same Promises, so there is little friction between application code and test code.

I wanted to see if I can recreate something like that based on the Qt networking APIs. I briefly tried using C++20 coroutines because they would allow a style closer to async/await, but the integration friction with existing Qt types was higher than I wanted for an experiment.

Using the methods in QtFuture however, we already have a way to create objects representing the response from a REST API. The result is similar to the Typescript example, but with different ergonomics, using .then instead of the async and await keywords.

struct RestRequest
{
QString method;
QString requestUrl;
QHttpHeaders headers;
QHash<QString, QString> urlParams;
QUrlQuery queryParams;
std::variant<QUrlQuery, QJsonDocument> requestData;
};
struct RestResponse
{
QJsonDocument jsonDoc;
QHttpHeaders headers;
QNetworkReply::NetworkError error;
QUrl url;
int statusCode;
};
QFuture<RestResponse> makeRequest(RestRequest restRequest)
{
auto url = interpolatePathParameters(
restRequest.requestUrl,
restRequest.urlParams);
auto request = requestFactory.createRequest(url);
auto requestBodyDoc = extractRequestContent(restRequest.requestData);
auto requestBody = requestBodyDoc.toJson(QJsonDocument::Compact);
auto reply = qRestManager.sendCustomRequest(request,
restRequest.method.toUtf8(),
requestBody,
&qnam,
[](QRestReply &) {});
return QtFuture::connect(reply, &QNetworkReply::finished).then(
[reply]() {
QRestReply restReply(reply);
auto responseDoc = restReply.readJson();
if (!responseDoc) {
throw std::runtime_error("Failed to read response");
}
RestResponse response;
response.jsonDoc = *responseDoc;
response.statusCode = restReply.httpStatus();
response.error = restReply.error();
response.headers = reply->headers();
response.url = reply->url();
return response;
}
);
}

The QRestAccessManager API requires the creation of a dummy response function when creating a custom request because it is not really designed to be used this way. The result is an API accepting a request and returning a QFuture with the QJsonDocument content. While it is possible for a REST endpoint to return something else, we can follow the Qt philosophy of making the most expected case as easy as possible, while leaving most of the rest possible another way. This utility makes writing unit tests relatively straightforward too:

RemoteAPI remoteApi;
remoteApi.setBaseUrl(QUrl("https://dog.ceo"));
auto responseFuture = remoteApi.makeRequest(
{"GET",
"api/breed/:breed/:sub_breed/images/random",
{},
{
{"breed", "wolfhound"},
{"sub_breed", "irish"}
}});
QFutureWatcher<RestResponse> watcher;
QSignalSpy spy(&watcher, &QFutureWatcherBase::finished);
watcher.setFuture(responseFuture);
QVERIFY(spy.wait(10000));
auto jsonObject = responseFuture.result().jsonDoc.object();
QCOMPARE(jsonObject["status"], "success");
QRegularExpression regex(
R"(https://images\.dog\.ceo/breeds/wolfhound-irish/[^.]+.jpg)");
QVERIFY(regex.match(jsonObject["message"].toString()).hasMatch());

The result is quite similar to the Typescript above, but only because we can use spy.wait. In application code, we still need to use .then with a callback, but we can additionally use .onFailed and .onCanceled instead of making multiple signal/slot connections.

With the addition of QtFuture::whenAll, it is easy to make multiple REST requests at once and react when they are all finished, so perhaps something else has been gained too, compared to a signal/slot model:

RemoteAPI remoteApi;
remoteApi.setBaseUrl(QUrl("https://dog.ceo"));
auto responseFuture = remoteApi.requestMultiple({
{
"GET",
"api/breeds/list/all",
},
{"GET",
"api/breed/:breed/:sub_breed/images/random",
{},
{{"breed", "german"}, {"sub_breed", "shepherd"}}},
{"GET",
"api/breed/:breed/:sub_breed/images/random/:num_results",
{},
{{"breed", "wolfhound"},
{"sub_breed", "irish"},
{"num_results", "3"}}},
{"GET", "api/breed/:breed/list", {}, {{"breed", "hound"}}},
});
QFutureWatcher<QList<RestResponse>> watcher;
QSignalSpy spy(&watcher, &QFutureWatcherBase::finished);
watcher.setFuture(responseFuture);
QVERIFY(spy.wait(10000));
auto four_responses = responseFuture.result();
QCOMPARE(four_responses.size(), 4);
QCOMPARE(four_responses[0].jsonDoc.object()["status"], "success");
QVERIFY(four_responses[0].jsonDoc.object()["message"].
toObject()["greyhound"].isArray());
QRegularExpression germanShepherdRegex(
R"(https://images.dog.ceo/breeds/german-shepherd/[^.]+.jpg)");
QCOMPARE(four_responses[1].jsonDoc.object()["status"], "success");
QVERIFY(germanShepherdRegex.match(
four_responses[1].jsonDoc.object()["message"].toString()).hasMatch());
QRegularExpression irishWolfhoundRegex(
R"(https://images.dog.ceo/breeds/wolfhound-irish/[^.]+.jpg)");
QCOMPARE(four_responses[2].jsonDoc.object()["status"], "success");
auto irishWolfhoundList =
four_responses[2].jsonDoc.object()["message"].toArray();
QCOMPARE(irishWolfhoundList.size(), 3);
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[0].toString()).
hasMatch());
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[1].toString()).
hasMatch());
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[2].toString()).
hasMatch());
QCOMPARE(four_responses[3].jsonDoc.object()["status"], "success");
auto houndList = four_responses[3].jsonDoc.object()["message"].toArray();
QCOMPARE_GE(houndList.size(), 7);
QVERIFY(houndList.contains("afghan"));
QVERIFY(houndList.contains("basset"));
QVERIFY(houndList.contains("blood"));
QVERIFY(houndList.contains("english"));
QVERIFY(houndList.contains("ibizan"));
QVERIFY(houndList.contains("plott"));
QVERIFY(houndList.contains("walker"));

setAutoDeleteReplies(false);

I attempted to use new API additions in recent Qt 6 versions to interact with a few real-world REST services. The additions are valuable, but it seems that there are a few places where improvements might be possible. My attempt to make the API feel closer to what developers in other environments might be accustomed to had some success, but I’m not sure QFuture is really intended to be used this way.
Do readers have any feedback? Would using QCoro improve the coroutine experience? Is it very unusual to create an application with QWidgets instead of QML these days? Should I have used PyQt and the python networking APIs?

AST Matchmaking made easy

February 14, 2021

The upcoming version of Clang 12 includes a new traversal mode which can be used for easier matching of AST nodes.

I presented this mode at EuroLLVM and ACCU 2019, but at the time I was calling it “ignoring invisible” mode. The primary aim is to make AST Matchers easier to write by requiring less “activation learning” of the newcomer to the AST Matcher API. I’m analogizing to “activation energy” here – this mode reduces the amount of learning of new concepts must be done before starting to use AST Matchers.

The new mode is a mouthful – IgnoreUnlessSpelledInSource – but it makes AST Matchers easier to use correctly and harder to use incorrectly. Some examples of the mode are available in the AST Matchers reference documentation.

In clang-query, the mode affects both matching and dumping of AST nodes and it is enabled with:

set traversal IgnoreUnlessSpelledInSource

while in the C++ API of AST Matchers, it is enabled by wrapping a matcher in:

traverse(TK_IgnoreUnlessSpelledInSource, ...)

The result is that matching of AST nodes corresponds closely to what is written syntactically in the source, rather than corresponding to the somewhat arbitrary structure implicit in the clang::RecursiveASTVisitor class.

Using this new mode makes it possible to “add features by removing code” in clang-tidy, making the checks more maintainable and making it possible to run checks in all language modes.

Clang does not use this new mode by default.

Implicit nodes in expressions

One of the issues identified is that the Clang AST contains many nodes which must exist in order to satisfy the requirements of the language. For example, a simple function relying on an implicit conversion might look like.

struct A {
    A(int);
    ~A();
};

A f()
{
    return 42;
}

In the new IgnoreUnlessSpelledInSource mode, this is represented as

ReturnStmt
`-IntegerLiteral '42'
and the integer literal can be matched with
returnStmt(hasReturnValue(integerLiteral().bind("returnVal")))

In the default mode, the AST might be (depending on C++ language dialect) represented by something like:

ReturnStmt
`-ExprWithCleanups
  `-CXXConstructExpr
    `-MaterializeTemporaryExpr
      `-ImplicitCastExpr
        `-CXXBindTemporaryExpr
          `-ImplicitCastExpr
            `-CXXConstructExpr
              `-IntegerLiteral '42'

To newcomers to the Clang AST, and to me, it is not obvious what all of the nodes there are for. I can reason that an instance of A must be constructed. However, there are two CXXConstructExprs in this AST and many other nodes, some of which are due to the presence of a user-provided destructor, others due to the temporary object. These kinds of extra nodes appear in most expressions, such as when processing arguments to a function call or constructor, declaring or assigning a variable, converting something to bool in an if condition etc.

There are already AST Matchers such as ignoringImplicit() which skip over some of the implicit nodes in AST Matchers. Still though, a complete matcher for the return value of this return statement looks something like

returnStmt(hasReturnValue(
    ignoringImplicit(
        ignoringElidableConstructorCall(
            ignoringImplicit(
                cxxConstructExpr(hasArgument(0,
                    ignoringImplicit(
                        integerLiteral().bind("returnVal")
                        )
                    ))
                )
            )
        )
    ))

Another mouthful.

There are several problems with this.

  • Typical clang-tidy checks which deal with expressions tend to require extensive use of such ignoring...() matchers. This makes the matcher expressions in such clang-tidy checks quite noisy
  • Different language dialects represent the same C++ code with different AST structures/extra nodes, necessitating testing and implementing the check in multiple language dialects
  • The requirement or possibility to use these intermediate matchers at all is not easily discoverable, nor are the required matchers to saitsfy all language modes easily discoverable
  • If an AST Matcher is written without explicitly ignoring implicit nodes, Clang produces lots of surprising results and incorrect transformations

Implicit declarations nodes

Aside from implicit expression nodes, Clang AST Matchers also match on implicit declaration nodes in the AST. That means that if we wish to make copy constructors in our codebase explicit we might use a matcher such as

cxxConstructorDecl(
    isCopyConstructor()
    ).bind("prepend_explicit")

This will work fine in the new IgnoreUnlessSpelledInSource mode.

However, in the default mode, if we have a struct with a compiler-provided copy constructor such as:

struct Copyable {
    OtherStruct m_o;
    Copyable();
};

we will match the compiler provided copy constructor. When our check inserts explicit at the copy constructor location it will result in:

struct explicit Copyable {
    OtherStruct m_o;
    Copyable();
};

Clearly this is an incorrect transformation despite the transformation code “looking” correct. This AST Matcher API is hard to use correctly and easy to use incorrectly. Because of this, the isImplicit() matcher is typically used in clang-tidy checks to attempt to exclude such transformations, making the matcher expression more complicated.

Implicit template instantiations

Another surpise in the behavior of AST Matchers is that template instantiations are matched by default. That means that if we wish to change class members of type int to type safe_int for example, we might write a matcher something like

fieldDecl(
    hasType(asString("int"))
    ).bind("use_safe_int")

This works fine for non-template code.

If we have a template like

template  
struct TemplStruct {
    TemplStruct() {}
    ~TemplStruct() {}

private:
    T m_t;
};

then clang internally creates an instantiation of the template with a substituted type for each template instantation in our translation unit.

The new IgnoreUnlessSpelledInSource mode ignores those internal instantiations and matches only on the template declaration (ie, with the T un-substituted).

However, in the default mode, our template will be transformed to use safe_int too:

template  
struct TemplStruct {
    TemplStruct() {}
    ~TemplStruct() {}

private:
    safe_int m_t;
};

This is clearly an incorrect transformation. Because of this, isTemplateInstantiation() and similar matchers are often used in clang-tidy to exclude AST matches which produce such transformations.

Matching metaphorical code

C++ has multiple features which are designed to be simple expressions which the compiler expands to something less-convenient to write. Range-based for loops are a good example as they are a metaphor for an explicit loop with calls to begin and end among other things. Lambdas are another good example as they are a metaphor for a callable object. C++20 adds several more, including rewriting use of operator!=(...) to use !operator==(...) and operator<(...) to use the spaceship operator.

[I admit that in writing this blog post I searched for a metaphor for “a device which aids understanding by replacing the thing it describes with something more familiar” before realizing the recursion. I haven’t heard these features described as metaphorical before though…]

All of these metaphorical replacements can be explored in the Clang AST or on CPP Insights.

Matching these internal representations is confusing and can cause incorrect transformations. None of these internal representations are matchable in the new IgnoreUnlessSpelledInSource mode.

In the default matching mode, the CallExprs for begin and end are matched, as are the CXXRecordDecl implicit in the lambda and hidden comparisons within rewritten binary operators such as spaceship (causing bugs in clang-tidy checks).

Easy Mode

This new mode of AST Matching is designed to be easier for users, especially newcomers to the Clang AST, to use and discover while offering protection from typical transformation traps. It will likely be used in my Qt-based Gui Quaplah, but it must be enabled explicitly in existing clang tools.

As usual, feedback is very welcome!


Design a site like this with WordPress.com
Get started