1 /*
2     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "kgraphql.h"
8 #include "kgraphqlminimizer_p.h"
9 
10 #include <QFile>
11 #include <QJsonArray>
12 #include <QJsonDocument>
13 #include <QJsonObject>
14 #include <QNetworkAccessManager>
15 #include <QNetworkReply>
16 #include <QNetworkRequest>
17 #include <QUrl>
18 
19 class KGraphQLRequestPrivate : public QSharedData
20 {
21 public:
22     KGraphQLRequestPrivate();
23 
24     QNetworkRequest m_request;
25     QString m_query;
26     QJsonObject m_variables;
27 };
28 
KGraphQLRequestPrivate()29 KGraphQLRequestPrivate::KGraphQLRequestPrivate()
30 {
31     m_request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
32 }
33 
KGraphQLRequest()34 KGraphQLRequest::KGraphQLRequest() :
35     d(new KGraphQLRequestPrivate)
36 {
37 }
38 
KGraphQLRequest(const QUrl & url)39 KGraphQLRequest::KGraphQLRequest(const QUrl& url) :
40     d(new KGraphQLRequestPrivate)
41 {
42     d->m_request.setUrl(url);
43 }
44 
45 KGraphQLRequest::KGraphQLRequest(const KGraphQLRequest&) = default;
46 KGraphQLRequest::KGraphQLRequest(KGraphQLRequest &&) noexcept = default;
47 KGraphQLRequest::~KGraphQLRequest() = default;
48 KGraphQLRequest& KGraphQLRequest::operator=(const KGraphQLRequest&) = default;
49 
setUrl(const QUrl & url)50 void KGraphQLRequest::setUrl(const QUrl &url)
51 {
52     d.detach();
53     d->m_request.setUrl(url);
54 }
55 
setQuery(const QString & query)56 void KGraphQLRequest::setQuery(const QString &query)
57 {
58     d.detach();
59     d->m_query = query;
60 }
61 
setQueryFromFile(const QString & fileName)62 void KGraphQLRequest::setQueryFromFile(const QString &fileName)
63 {
64     QFile f(fileName);
65     if (!f.open(QFile::ReadOnly)) {
66         qWarning() << "Failed to load GraphQL query from file:" << fileName << f.errorString();
67         return;
68     }
69 
70     d.detach();
71     KGraphQLMinimizer m;
72     d->m_query = QString::fromUtf8(m.minimizeQuery(f.readAll()));
73 }
74 
setVariable(const QString & name,const QJsonValue & value)75 void KGraphQLRequest::setVariable(const QString &name, const QJsonValue &value)
76 {
77     d.detach();
78     d->m_variables.insert(name, value);
79 }
80 
networkRequest()81 QNetworkRequest& KGraphQLRequest::networkRequest()
82 {
83     return d->m_request;
84 }
85 
networkRequest() const86 const QNetworkRequest& KGraphQLRequest::networkRequest() const
87 {
88     return d->m_request;
89 }
90 
rawData() const91 QByteArray KGraphQLRequest::rawData() const
92 {
93     QJsonObject obj;
94     obj.insert(QStringLiteral("query"), d->m_query);
95     if (!d->m_variables.isEmpty()) {
96         obj.insert(QStringLiteral("variables"), d->m_variables);
97     }
98     return QJsonDocument(obj).toJson(QJsonDocument::Compact);
99 }
100 
101 
102 class KGraphQLReplyPrivate
103 {
104 public:
105     QNetworkReply *m_reply = nullptr;
106     QByteArray m_data;
107     QJsonObject m_result;
108 };
109 
KGraphQLReply(QNetworkReply * reply)110 KGraphQLReply::KGraphQLReply(QNetworkReply *reply)
111     : d(new KGraphQLReplyPrivate)
112 {
113     d->m_reply = reply;
114     if (d->m_reply->error() == QNetworkReply::NoError || d->m_reply->error() == QNetworkReply::InternalServerError) {
115         d->m_data = reply->readAll();
116         d->m_result = QJsonDocument::fromJson(d->m_data).object();
117     }
118 }
119 
120 KGraphQLReply::KGraphQLReply(KGraphQLReply &&) noexcept = default;
121 
~KGraphQLReply()122 KGraphQLReply::~KGraphQLReply()
123 {
124     d->m_reply->deleteLater();
125 }
126 
error() const127 KGraphQLReply::Error KGraphQLReply::error() const
128 {
129     if (d->m_reply->error() != QNetworkReply::NoError && d->m_result.isEmpty()) {
130         return NetworkError;
131     }
132     if (d->m_result.contains(QLatin1String("data"))) {
133         return NoError;
134     }
135     return QueryError;
136 }
137 
errorString() const138 QString KGraphQLReply::errorString() const
139 {
140     switch (error()) {
141         case NoError:
142             return {};
143         case NetworkError:
144             return d->m_reply->errorString();
145         case QueryError:
146         {
147             const auto errorArray = d->m_result.value(QLatin1String("errors")).toArray();
148             QStringList errors;
149             errors.reserve(errorArray.size());
150             for (const auto &errorValue : errorArray) {
151                 const auto errorObj = errorValue.toObject();
152                 const auto msg = QString::fromUtf8("%1 (line: %2, column: %3)")
153                     .arg(errorObj.value(QLatin1String("message")).toString())
154                     .arg(errorObj.value(QLatin1String("locations")).toArray().at(0).toObject().value(QLatin1String("line")).toInt())
155                     .arg(errorObj.value(QLatin1String("locations")).toArray().at(0).toObject().value(QLatin1String("column")).toInt());
156                 errors.push_back(msg);
157             }
158             return errors.join(QLatin1Char('\n'));
159         }
160     }
161     return {};
162 }
163 
data() const164 QJsonObject KGraphQLReply::data() const
165 {
166     return d->m_result.value(QLatin1String("data")).toObject();
167 }
168 
networkReply() const169 QNetworkReply* KGraphQLReply::networkReply() const
170 {
171     return d->m_reply;
172 }
173 
rawData() const174 QByteArray KGraphQLReply::rawData() const
175 {
176     return d->m_data;
177 }
178 
179 
query(const KGraphQLRequest & request,QNetworkAccessManager * nam,const std::function<void (const KGraphQLReply &)> & callback)180 void KGraphQL::query(const KGraphQLRequest &request, QNetworkAccessManager *nam, const std::function<void(const KGraphQLReply&)> &callback)
181 {
182     auto reply = nam->post(request.networkRequest(), request.rawData());
183     QObject::connect(reply, &QNetworkReply::finished, nam, [reply, callback]() {
184         callback(KGraphQLReply(reply));
185         reply->deleteLater();
186     });
187 }
188