LCOV - code coverage report
Current view: top level - muse/framework/network/api - fetchapi.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 110 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2026 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "fetchapi.h"
      24              : 
      25              : #include <QBuffer>
      26              : #include <QJSEngine>
      27              : #include <QJSValueIterator>
      28              : #include <QUrl>
      29              : #include <QVariantMap>
      30              : 
      31              : #include "global/progress.h"
      32              : 
      33              : #include "log.h"
      34              : 
      35              : using namespace muse;
      36              : using namespace muse::network;
      37              : using namespace muse::network::api;
      38              : 
      39            0 : FetchApi::FetchApi(muse::api::IApiEngine* e)
      40            0 :     : muse::api::ApiObject(e)
      41              : {
      42            0 :     m_self = engine()->newQObject(this);
      43            0 : }
      44              : 
      45            0 : QJSValue FetchApi::fetch(QJSValue request, QJSValue options)
      46              : {
      47            0 :     QUrl url;
      48            0 :     if (request.isString()) {
      49            0 :         url = QUrl(request.toString());
      50              :     } else {
      51            0 :         NOT_SUPPORTED;
      52              :     }
      53              : 
      54            0 :     muse::api::JsPromise promise = engine()->newPromise();
      55              : 
      56            0 :     if (!url.isValid() || (url.scheme() != QStringLiteral("http") && url.scheme() != QStringLiteral("https"))) {
      57            0 :         promise.reject.call({ QJSValue(QStringLiteral("Invalid url")) });
      58            0 :         return promise.promise;
      59              :     }
      60              : 
      61            0 :     const QString method = stringOption(options, "method", "GET").toUpper();
      62            0 :     LOGDA() << "fetch:" << method << " " << url;
      63              : 
      64            0 :     const RequestHeaders headers = requestHeaders(options);
      65            0 :     const QByteArray body = byteArrayOption(options, "body", QByteArray());
      66              : 
      67            0 :     auto outgoing = std::make_shared<QBuffer>();
      68            0 :     outgoing->setData(body);
      69              : 
      70            0 :     auto incoming = std::make_shared<QBuffer>();
      71            0 :     RetVal<Progress> progress = execRequest(method, url, outgoing, incoming, headers);
      72            0 :     if (!progress.ret) {
      73            0 :         promise.reject.call({ QJSValue(QString::fromStdString(progress.ret.text())) });
      74            0 :         return promise.promise;
      75              :     }
      76              : 
      77            0 :     progress.val.finished().onReceive(this, [this, incoming, resolve = promise.resolve, reject = promise.reject]
      78              :                                       (const ProgressResult& res)
      79              :     {
      80            0 :         if (!res.ret) {
      81            0 :             reject.call({ QJSValue(QString::fromStdString(res.ret.text())) });
      82            0 :             return;
      83              :         }
      84              : 
      85            0 :         const int status = res.ret.data<int>("status", 0);
      86            0 :         const QString statusText = QString::fromStdString(res.ret.data<std::string>("statusText", std::string()));
      87            0 :         const QVariantMap headers = res.ret.data<QVariantMap>("headers", QVariantMap());
      88            0 :         resolve.call({ makeResponse(status, statusText, incoming->data(), headers) });
      89            0 :     });
      90              : 
      91            0 :     return promise.promise;
      92            0 : }
      93              : 
      94            0 : QJSValue FetchApi::toUtf8(const QJSValue& bytes) const
      95              : {
      96            0 :     if (bytes.isUndefined() || bytes.isNull()) {
      97            0 :         return QJSValue(QString());
      98              :     }
      99              : 
     100            0 :     QByteArray data = qjsvalue_cast<QByteArray>(bytes);
     101            0 :     return QJSValue(QString::fromUtf8(data));
     102            0 : }
     103              : 
     104            0 : QString FetchApi::stringOption(const QJSValue& options, const QString& key, const QString& def) const
     105              : {
     106            0 :     if (!options.isObject()) {
     107            0 :         return def;
     108              :     }
     109              : 
     110            0 :     const QJSValue value = options.property(key);
     111            0 :     if (!value.isString()) {
     112            0 :         return def;
     113              :     }
     114              : 
     115            0 :     return value.toString();
     116            0 : }
     117              : 
     118            0 : QByteArray FetchApi::byteArrayOption(const QJSValue& options, const QString& key, const QByteArray& def) const
     119              : {
     120            0 :     if (!options.isObject()) {
     121            0 :         return def;
     122              :     }
     123              : 
     124            0 :     const QJSValue value = options.property(key);
     125            0 :     if (value.isUndefined() || value.isNull()) {
     126            0 :         return def;
     127              :     }
     128              : 
     129            0 :     if (value.isString()) {
     130            0 :         return value.toString().toUtf8();
     131              :     }
     132              : 
     133            0 :     return qjsvalue_cast<QByteArray>(value);
     134            0 : }
     135              : 
     136            0 : RequestHeaders FetchApi::requestHeaders(const QJSValue& options) const
     137              : {
     138            0 :     RequestHeaders headers = configuration()->defaultHeaders();
     139            0 :     if (!options.isObject()) {
     140            0 :         return headers;
     141              :     }
     142              : 
     143            0 :     const QJSValue headersValue = options.property("headers");
     144            0 :     if (!headersValue.isObject()) {
     145            0 :         return headers;
     146              :     }
     147              : 
     148            0 :     QJSValueIterator it(headersValue);
     149            0 :     while (it.hasNext()) {
     150            0 :         it.next();
     151            0 :         if (!it.value().isString()) {
     152            0 :             continue;
     153              :         }
     154              : 
     155            0 :         const QString name = it.name();
     156            0 :         const QString value = it.value().toString();
     157            0 :         headers.rawHeaders.insert(name.toUtf8(), value.toUtf8());
     158            0 :     }
     159              : 
     160            0 :     return headers;
     161            0 : }
     162              : 
     163            0 : RetVal<Progress> FetchApi::execRequest(const QString& method, const QUrl& url,
     164              :                                        QIODevicePtr outgoing, QIODevicePtr incoming,
     165              :                                        const RequestHeaders& headers)
     166              : {
     167            0 :     if (!m_networkManager) {
     168            0 :         m_networkManager = networkManagerCreator()->makeNetworkManager();
     169              :     }
     170              : 
     171            0 :     if (method == QStringLiteral("GET")) {
     172            0 :         return m_networkManager->get(url, incoming, headers);
     173              :     }
     174            0 :     if (method == QStringLiteral("HEAD")) {
     175            0 :         return m_networkManager->head(url, headers);
     176              :     }
     177            0 :     if (method == QStringLiteral("DELETE")) {
     178            0 :         return m_networkManager->del(url, incoming, headers);
     179              :     }
     180              : 
     181            0 :     if (method == QStringLiteral("POST")) {
     182            0 :         return m_networkManager->post(url, outgoing, incoming, headers);
     183              :     }
     184            0 :     if (method == QStringLiteral("PUT")) {
     185            0 :         return m_networkManager->put(url, outgoing, incoming, headers);
     186              :     }
     187            0 :     if (method == QStringLiteral("PATCH")) {
     188            0 :         return m_networkManager->patch(url, outgoing, incoming, headers);
     189              :     }
     190              : 
     191            0 :     return RetVal<Progress>::make_ret(Ret::Code::NotSupported, "Unsupported method: " + method.toStdString());
     192              : }
     193              : 
     194            0 : QJSValue FetchApi::responseFactory()
     195              : {
     196            0 :     if (m_responseFactory.isCallable()) {
     197            0 :         return m_responseFactory;
     198              :     }
     199              : 
     200            0 :     m_responseFactory = engine()->evaluate(QStringLiteral(
     201              :                                                R"(
     202              :         (function (self, status, statusText, body, headers) {
     203              : 
     204              :             return {
     205              :                 status: status,
     206              :                 statusText: statusText,
     207              :                 ok: status >= 200 && status < 300,
     208              :                 headers: {
     209              :                     get: function (name) {
     210              :                         var key = String(name).toLowerCase()
     211              :                         return Object.prototype.hasOwnProperty.call(headers, key) ? headers[key] : null
     212              :                     },
     213              :                     has: function (name) {
     214              :                         return Object.prototype.hasOwnProperty.call(headers, String(name).toLowerCase())
     215              :                     }
     216              :                 },
     217              :                 text: function () {
     218              :                     return Promise.resolve(self.toUtf8(body))
     219              :                 },
     220              :                 json: function () {
     221              :                     return new Promise(function (resolve, reject) {
     222              :                         try {
     223              :                             resolve(JSON.parse(self.toUtf8(body)))
     224              :                         } catch (e) {
     225              :                             reject(e)
     226              :                         }
     227              :                     })
     228              :                 },
     229              :                 arrayBuffer: function () {
     230              :                     return Promise.resolve(body)
     231              :                 }
     232              :             }
     233              :         })
     234            0 :     )"));
     235              : 
     236            0 :     if (m_responseFactory.isError()) {
     237            0 :         LOGE() << "failed to create response factory: " << m_responseFactory.toString();
     238              :     }
     239              : 
     240            0 :     return m_responseFactory;
     241              : }
     242              : 
     243            0 : QJSValue FetchApi::makeResponse(int status, const QString& statusText, const QByteArray& body, const QVariantMap& headers)
     244              : {
     245            0 :     QJSValue headersObj = engine()->newObject();
     246            0 :     for (auto it = headers.cbegin(); it != headers.cend(); ++it) {
     247            0 :         headersObj.setProperty(it.key(), it.value().toString());
     248              :     }
     249              : 
     250            0 :     return responseFactory().call({
     251            0 :         m_self,
     252              :         QJSValue(status),
     253              :         QJSValue(statusText),
     254            0 :         engine()->newArrayBuffer(body),
     255              :         headersObj
     256            0 :     });
     257            0 : }
        

Generated by: LCOV version 2.0-1