Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. API wrapper best practices
QtWS25 Last Chance

API wrapper best practices

Scheduled Pinned Locked Moved Solved General and Desktop
6 Posts 4 Posters 1.5k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    Defohin
    wrote on last edited by Defohin
    #1

    Hi guys, I'm new with Qt but I've been reading a lot and I think I'm on the right track, but I have a few questions regarding best practices and if you guys have tips of how I can improve my code a little bit.
    I'm trying to create a wrapper around a simple API that I made locally but will be online pretty soon.
    It's like pastebin, where you can paste text with a title.
    The API is simple, it receives the text and the content and the api key that each user has. (I won't bother explaining that 'cause it has nothing to do with Qt).

    Here is how the class looks like:

    paste.h

    #ifndef PASTE_H
    #define PASTE_H
    
    #include <QObject>
    #include <QNetworkAccessManager>
    #include <QNetworkReply>
    
    class Paste : public QObject
    {
        Q_OBJECT
    
    public:
        enum PasteError {
            UnknownError,
            NetworkError,
            InvalidApiKeyError,
            InvalidJsonResponse
        };
        Q_ENUM(PasteError)
    
        explicit Paste(QObject *parent = 0);
    
        QString getTitle() const;
        void setTitle(const QString &value);
    
        QString getContent() const;
        void setContent(const QString &value);
    
        QString getApiKey() const;
        void setApiKey(const QString &value);
    
    public slots:
        void paste();
    
        void requestFinished();
    #ifndef QT_NO_SSL
        void requestSslErrors(QNetworkReply*, const QList<QSslError> &errors);
    #endif
        void requestError(QNetworkReply::NetworkError code);
        void requestReadyRead();
    
    signals:
        void error(PasteError error);
        void success(const QString& link);
    
    private:
        QString title;
        QString content;
        QString apiKey;
    
        QNetworkAccessManager nam;
        QNetworkReply *reply;
    };
    
    #endif // PASTE_H
    

    paste.cpp

    #include "paste.h"
    #include <QNetworkRequest>
    #include <QUrlQuery>
    #include <QJsonDocument>
    #include <QJsonObject>
    
    static const QString API_URL = "http://127.0.0.1/paste/api";
    
    Paste::Paste(QObject *parent): QObject(parent), title(""), content(""), apiKey("")
    {
        #ifndef QT_NO_SSL
            connect(&nam, &QNetworkAccessManager::sslErrors, this, &Paste::requestSslErrors);
        #endif
    }
    
    QString Paste::getTitle() const
    {
        return title;
    }
    
    void Paste::setTitle(const QString &value)
    {
        title = value;
    }
    
    QString Paste::getContent() const
    {
        return content;
    }
    
    void Paste::setContent(const QString &value)
    {
        content = value;
    }
    
    QString Paste::getApiKey() const
    {
        return apiKey;
    }
    
    void Paste::setApiKey(const QString &value)
    {
        apiKey = value;
    }
    
    void Paste::paste()
    {
        QNetworkRequest request(API_URL);
        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=utf-8");
    
        QUrlQuery query;
        query.addQueryItem("title", getTitle());
        query.addQueryItem("content", getContent());
        query.addQueryItem("api_key", getApiKey());
    
        QByteArray body(query.query(QUrl::FullyEncoded).toUtf8());
    
        reply = nam.post(request, body);
        connect(reply, &QNetworkReply::finished, this, &Paste::requestFinished);
        connect(reply, &QIODevice::readyRead, this, &Paste::requestReadyRead);
    }
    
    void Paste::requestFinished()
    {
        reply->deleteLater();
        reply = Q_NULLPTR;
    }
    
    #ifndef QT_NO_SSL
    void Paste::requestSslErrors(QNetworkReply *, const QList<QSslError> &errors)
    {
        Q_UNUSED(errors);
        reply->ignoreSslErrors();
        emit error(NetworkError);
    }
    #endif
    
    void Paste::requestError(QNetworkReply::NetworkError code)
    {
        Q_UNUSED(code);
        emit error(NetworkError);
    }
    
    void Paste::requestReadyRead()
    {
        QByteArray response = reply->readAll();
        QJsonDocument jsonDocument(QJsonDocument::fromJson(response));
    
        if (jsonDocument.isNull() ||
          ! jsonDocument.isObject()
        ) {
            emit error(InvalidJsonResponse);
            return;
        }
    
        QJsonObject jsonObject = jsonDocument.object();
    
        if (! jsonObject.contains("status") &&
            ! jsonObject.contains("message")
        ) {
            emit error(InvalidJsonResponse);
            return;
        }
    
        QJsonValue status = jsonObject.value("status");
        QJsonValue message = jsonObject.value("message");
    
        if (status == "error") {
            if (message.toString().contains("key")) {
                emit error(InvalidApiKeyError);
                return;
            }
    
            emit error(UnknownError);
            return;
        }
    
        emit success(message.toString());
    }
    

    The questions I have is:

    1. Is it right the way I declared a static const QString as the api url?
    2. Is it needed in that case to instantiate the members with an empty string like on the constructor where I put title("")?
    3. The only thing that Paste::requestFinished is doing is calling the deleteLater of the reply and setting it as a null pointer. Is there another usage for that method or is it wrong/right?
    4. The same thing on Paste::requestSslErrors... read the 3.
    5. On the Paste::requestReadyRead I check for a few things, like if the response is in Json format, if it contains status and message on it. status can be success or error, there are two errors that might occur, one is the wrong api key and the other one is unknown, maybe due to a malformed request and for that reason I'm using a few "ifs". Is it right the way I'm doing or is there a better way of doing that?
    6. You might notice that I created a kind of struct with errors that it might return and I emitted them several times along the code, for instance on the Paste::requestReadyRead I call it and then return, but I don't know if I'm doing it right... I was wondering if the way I'm doing is also right.
    7. The names success and errors are too abstract for signals? Do you have another alternative? Just to remember, the success will return an address link of the paste inside the message and the error will return the error.

    A problem I'm having is to handle network problems on errors cause all the problems that a network might have is coming from the QNetworkReply but I'm having a class already with errors so I don't know how to give to the user the errors if it's a network error. I created an enum named NetworkError but it seems to be really abstract and not informative, what is the best way to integrate both my errors with the errors from the QNetworkReply?

    Thank you all.

    1 Reply Last reply
    0
    • ? Offline
      ? Offline
      A Former User
      wrote on last edited by
      #2

      Hi!

      1. Yes, it's ok.
      2. No, not needed, QStrings get initialized to an empty string automatically.

      I think there is a logic bug in:

      if (! jsonObject.contains("status") &&
              ! jsonObject.contains("message")
          ) {
      

      If I understand your intentions right then it should be || instead of &&.

      D 1 Reply Last reply
      1
      • SGaistS Offline
        SGaistS Offline
        SGaist
        Lifetime Qt Champion
        wrote on last edited by
        #3

        Hi,

        To add to @Wieland, it seems that you only have one public slot with paste all the others are "internal" to you class so you should either make them private or use lambdas.

        Interested in AI ? www.idiap.ch
        Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

        1 Reply Last reply
        1
        • ? A Former User

          Hi!

          1. Yes, it's ok.
          2. No, not needed, QStrings get initialized to an empty string automatically.

          I think there is a logic bug in:

          if (! jsonObject.contains("status") &&
                  ! jsonObject.contains("message")
              ) {
          

          If I understand your intentions right then it should be || instead of &&.

          D Offline
          D Offline
          Defohin
          wrote on last edited by
          #4

          @Wieland I used && cause the Json has to be in that format, with status and message.

          @SGaist I don't get what you mean... the usage for that class is like that:

          Paster paster = new Paster(this);
          paster->setTitle("a title");
          paster->setContent("This is a content");
          paster->setApiKey("abcdef");
          paster->paste();
          
          connect(paster, &Paster::sucess, [] (const QString& link) {
              qDebug() << link;
          });
          
          connect(paster, &Paster::error, [] (PasteError error) {
              qDebug() << error;
          });
          
          jsulmJ 1 Reply Last reply
          0
          • D Defohin

            @Wieland I used && cause the Json has to be in that format, with status and message.

            @SGaist I don't get what you mean... the usage for that class is like that:

            Paster paster = new Paster(this);
            paster->setTitle("a title");
            paster->setContent("This is a content");
            paster->setApiKey("abcdef");
            paster->paste();
            
            connect(paster, &Paster::sucess, [] (const QString& link) {
                qDebug() << link;
            });
            
            connect(paster, &Paster::error, [] (PasteError error) {
                qDebug() << error;
            });
            
            jsulmJ Online
            jsulmJ Online
            jsulm
            Lifetime Qt Champion
            wrote on last edited by
            #5

            @Defohin If you need to have both "status" and "message" in your json then you need to change to ||
            Else you will only print error and return if none of them is inside.

            https://forum.qt.io/topic/113070/qt-code-of-conduct

            1 Reply Last reply
            0
            • D Offline
              D Offline
              Defohin
              wrote on last edited by Defohin
              #6

              I realized what @SGaist said and now all the slots are private.
              I also changed the logic that @jsulm and @Wieland mentioned.
              Removed the QStrings from the constructor
              Now it looks great. Thank you guys.

              1 Reply Last reply
              1

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved