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