├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── .qmake.conf ├── LICENSE ├── README.md ├── deploy.json ├── doc ├── Doxyfile ├── doc.pro ├── doxme.py ├── generator.dox ├── genericrestreply.dox ├── gh_header.html ├── images │ └── GitHub_Logo.png ├── ipaging.dox ├── makedoc.sh ├── paging.dox ├── pagingmodel.dox ├── qtrestclient.dox ├── requestbuilder.dox ├── restclass.dox ├── restclient.dox └── restreply.dox ├── examples ├── examples.pro └── restclient │ ├── JsonPlaceholderApi │ ├── JsonPlaceholderApi.pro │ ├── api.xml │ ├── api_posts.xml │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindow.ui │ └── post.xml │ ├── SimpleRestClientApp │ ├── SimpleRestClientApp.pro │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ └── mainwindow.ui │ └── restclient.pro ├── mkspecs └── features │ └── qrestbuilder.prf ├── qbs └── Qt │ └── restbuilder │ └── module.qbs ├── qtrestclient.pro ├── src ├── 3rdparty │ ├── optional-lite │ │ ├── LICENSE.txt │ │ └── optional.hpp │ └── variant-lite │ │ ├── LICENSE.txt │ │ └── variant.hpp ├── imports │ ├── imports.pro │ └── restclient │ │ ├── plugins.qmltypes │ │ ├── qmldir │ │ ├── qmlgenericrestreply.cpp │ │ ├── qmlgenericrestreply.h │ │ ├── qmlpaging.cpp │ │ ├── qmlpaging.h │ │ ├── qmlrestclass.cpp │ │ ├── qmlrestclass.h │ │ ├── qmlrestclient.cpp │ │ ├── qmlrestclient.h │ │ ├── qmlrestclientglobal.cpp │ │ ├── qmlrestclientglobal.h │ │ ├── qmlrestreply.cpp │ │ ├── qmlrestreply.h │ │ ├── qtrestclient_plugin.cpp │ │ ├── qtrestclient_plugin.h │ │ └── restclient.pro ├── restclient │ ├── genericrestreply.h │ ├── ipaging.cpp │ ├── ipaging.h │ ├── metacomponent.h │ ├── paging.h │ ├── paging_fwd.h │ ├── pagingmodel.cpp │ ├── pagingmodel.h │ ├── pagingmodel_p.h │ ├── qtrestclient_global.h │ ├── qtrestclient_helpertypes.h │ ├── requestbuilder.cpp │ ├── requestbuilder.h │ ├── requestbuilder_p.h │ ├── restclass.cpp │ ├── restclass.h │ ├── restclass_p.h │ ├── restclient.cpp │ ├── restclient.h │ ├── restclient.pro │ ├── restclient_p.h │ ├── restreply.cpp │ ├── restreply.h │ ├── restreply_p.h │ ├── restreplyawaitable.cpp │ ├── restreplyawaitable.h │ ├── restreplyawaitable_p.h │ ├── simple.h │ ├── standardpaging.cpp │ └── standardpaging_p.h ├── restclientauth │ ├── authrequestbuilder.cpp │ ├── authrequestbuilder.h │ ├── authrestclient.cpp │ ├── authrestclient.h │ ├── authrestclient_p.h │ ├── qtrestclientauth_global.h │ └── restclientauth.pro └── src.pro ├── sync.profile ├── tests ├── auto │ ├── auto.pro │ ├── cmake │ │ ├── CMakeLists.txt │ │ └── cmake.pro │ ├── qml │ │ ├── TestQmlRestClient │ │ │ ├── TestQmlRestClient.pro │ │ │ ├── api_posts.xml │ │ │ ├── post.xml │ │ │ ├── simplepost.xml │ │ │ ├── test_api.xml │ │ │ ├── testmacro.h │ │ │ ├── tst_qmlrestclient.cpp │ │ │ ├── tst_qmlrestclient.qml │ │ │ └── user.xml │ │ └── qml.pro │ ├── restclient │ │ ├── .gitignore │ │ ├── IntegrationTest │ │ │ ├── IntegrationTest.pro │ │ │ ├── jphuser.cpp │ │ │ ├── jphuser.h │ │ │ └── tst_integration.cpp │ │ ├── PagingModelTest │ │ │ ├── PagingModelTest.pro │ │ │ └── tst_pagingmodel.cpp │ │ ├── RequestBuilderTest │ │ │ ├── RequestBuilderTest.pro │ │ │ └── tst_requestbuilder.cpp │ │ ├── RestAwaitablesTest │ │ │ ├── RestAwaitablesTest.pro │ │ │ └── tst_restawaitables.cpp │ │ ├── RestBuilderTest │ │ │ ├── RestBuilderTest.pro │ │ │ ├── api_posts.xml │ │ │ ├── post.xml │ │ │ ├── simplepost.xml │ │ │ ├── test_api.xml │ │ │ ├── testmacro.h │ │ │ ├── tst_restbuilder.cpp │ │ │ ├── user.xml │ │ │ └── user_props.xml │ │ ├── RestClientTest │ │ │ ├── RestClientTest.pro │ │ │ └── tst_restclient.cpp │ │ ├── RestReplyTest │ │ │ ├── RestReplyTest.pro │ │ │ └── tst_restreply.cpp │ │ ├── restclient.pro │ │ ├── testlib │ │ │ ├── httpserver.cpp │ │ │ ├── httpserver.h │ │ │ ├── jphpost.cpp │ │ │ ├── jphpost.h │ │ │ ├── testlib.cpp │ │ │ ├── testlib.h │ │ │ └── testlib.pro │ │ └── tests.pri │ ├── restclientauth │ │ ├── AuthRequestBuilderTest │ │ │ ├── AuthRequestBuilderTest.pro │ │ │ └── tst_authrequestbuilder.cpp │ │ └── restclientauth.pro │ └── testrun.pri ├── global │ └── global.cfg └── tests.pro └── tools ├── qrestbuilder ├── classbuilder.cpp ├── classbuilder.h ├── main.cpp ├── objectbuilder.cpp ├── objectbuilder.h ├── qrestbuilder.pro ├── qrestbuilder.qrc ├── qrestbuilder.xsd ├── restbuilder.cpp ├── restbuilder.h ├── xmlconverter.cpp └── xmlconverter.h └── tools.pro /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | version: 11 | - 5.14.1 12 | platform: 13 | - gcc_64 14 | - android 15 | - wasm_32 16 | - msvc2017_64 17 | - msvc2017 18 | - winrt_x64_msvc2017 19 | - winrt_x86_msvc2017 20 | - winrt_armv7_msvc2017 21 | - mingw73_64 22 | - mingw73_32 23 | - clang_64 24 | - ios 25 | mode: 26 | - json 27 | - generic 28 | 29 | include: 30 | - platform: gcc_64 31 | os: ubuntu-latest 32 | - platform: android 33 | os: ubuntu-latest 34 | - platform: wasm_32 35 | os: ubuntu-latest 36 | emsdk: sdk-fastcomp-1.38.27-64bit 37 | - platform: msvc2017_64 38 | os: windows-latest 39 | - platform: msvc2017 40 | os: windows-latest 41 | - platform: winrt_x64_msvc2017 42 | os: windows-latest 43 | - platform: winrt_x86_msvc2017 44 | os: windows-latest 45 | - platform: winrt_armv7_msvc2017 46 | os: windows-2016 47 | - platform: mingw73_64 48 | os: windows-latest 49 | - platform: mingw73_32 50 | os: windows-latest 51 | extra-flags: CONFIG+=no_coroutine_tests 52 | - platform: clang_64 53 | os: macos-latest 54 | - platform: ios 55 | os: macos-latest 56 | 57 | - mode: json 58 | keysuffix: without-json 59 | qtpackages: qtnetworkauth 60 | config: CONFIG+=no_json_serializer 61 | - mode: generic 62 | keysuffix: with-json 63 | qtpackages: qtnetworkauth,skycoder42.jsonserializer 64 | 65 | runs-on: ${{matrix.os}} 66 | steps: 67 | - uses: actions/checkout@v1 68 | with: 69 | submodules: recursive 70 | - uses: actions/setup-python@v1 71 | - name: actions/cache emsdk 72 | uses: actions/cache@v1 73 | if: matrix.platform == 'wasm_32' 74 | with: 75 | path: emsdk-cache 76 | key: ${{runner.os}}-emsdk-${{matrix.emsdk}} 77 | - uses: mymindstorm/setup-emsdk@v3 78 | if: matrix.platform == 'wasm_32' 79 | with: 80 | version: ${{matrix.emsdk}} 81 | actions-cache-folder: emsdk-cache 82 | - name: actions/cache qt 83 | uses: actions/cache@v1 84 | id: cache-qt 85 | with: 86 | path: qt/${{matrix.version}}/${{matrix.platform}} 87 | key: qt-${{matrix.version}}-${{matrix.platform}}-${{matrix.keysuffix}} 88 | - uses: Skycoder42/action-setup-qt@master 89 | id: qt 90 | with: 91 | version: ${{matrix.version}} 92 | platform: ${{matrix.platform}} 93 | packages: ${{matrix.qtpackages}} 94 | flat-sources: https://install.skycoder42.de/qtmodules/ 95 | cachedir: qt/${{matrix.version}}/${{matrix.platform}} 96 | - name: download qthttpserver precompiled binary 97 | if: steps.cache-qt.outputs.cache-hit != 'true' && matrix.os == 'windows-latest' 98 | shell: bash 99 | run: | 100 | curl -Lo "src/3rdparty/qthttpserver-binary.zip" 'https://github.com/Skycoder42/qt5-httpserver-build/releases/download/${{matrix.version}}/qthttpserver-${{matrix.platform}}-${{matrix.version}}.zip' 101 | 7z x "src/3rdparty/qthttpserver-binary.zip" "-o${{steps.qt.outputs.qtdir}}/.." -aoa -y 102 | - name: build and install qthttpserver 103 | if: steps.cache-qt.outputs.cache-hit != 'true' && matrix.os != 'windows-latest' 104 | working-directory: src/3rdparty/qthttpserver 105 | run: | 106 | qmake 107 | ${{steps.qt.outputs.make}} qmake_all 108 | ${{steps.qt.outputs.make}} 109 | ${{steps.qt.outputs.make}} install 110 | - name: qmake 111 | run: | 112 | qmake CONFIG+=install_ok QT_PLATFORM=${{matrix.platform}} ${{matrix.config}} ${{matrix.extra-flags}} 113 | ${{steps.qt.outputs.make}} qmake_all 114 | - name: make module 115 | run: | 116 | ${{steps.qt.outputs.make}} 117 | ${{steps.qt.outputs.make}} INSTALL_ROOT="${{steps.qt.outputs.installdir}}" install 118 | - name: make tests 119 | if: steps.qt.outputs.tests == 'true' && matrix.mode == 'generic' 120 | run: | 121 | ${{steps.qt.outputs.make}} all 122 | ${{steps.qt.outputs.make}} ${{steps.qt.outputs.testflags}} run-tests 123 | - name: make examples 124 | if: matrix.platform == 'gcc_64' && matrix.mode == 'generic' 125 | run: | 126 | ${{steps.qt.outputs.make}} sub-examples 127 | cd examples && ${{steps.qt.outputs.make}} INSTALL_ROOT="${{steps.qt.outputs.installdir}}" install 128 | - name: make doc 129 | if: matrix.platform == 'gcc_64' && matrix.mode == 'generic' 130 | run: | 131 | ${{steps.qt.outputs.make}} doxygen 132 | cd doc && ${{steps.qt.outputs.make}} INSTALL_ROOT="${{steps.qt.outputs.installdir}}" install 133 | - name: upload module to releases 134 | uses: Skycoder42/action-upload-release@master 135 | if: startsWith(github.ref, 'refs/tags/') && matrix.mode == 'generic' 136 | with: 137 | repo_token: ${{secrets.GITHUB_TOKEN}} 138 | directory: ${{steps.qt.outputs.outdir}}/${{matrix.version}} 139 | asset_name: qtrestclient-${{matrix.platform}}-${{matrix.version}} 140 | tag: ${{github.ref}} 141 | overwrite: true 142 | - name: upload examples to releases 143 | uses: Skycoder42/action-upload-release@master 144 | if: matrix.platform == 'gcc_64' && startsWith(github.ref, 'refs/tags/') && matrix.mode == 'generic' 145 | with: 146 | repo_token: ${{secrets.GITHUB_TOKEN}} 147 | directory: ${{steps.qt.outputs.outdir}}/${{matrix.version}}/${{matrix.platform}}/examples 148 | asset_name: qtrestclient-examples-${{matrix.version}} 149 | tag: ${{github.ref}} 150 | overwrite: true 151 | - name: upload doc to releases 152 | uses: Skycoder42/action-upload-release@master 153 | if: matrix.platform == 'gcc_64' && startsWith(github.ref, 'refs/tags/') && matrix.mode == 'generic' 154 | with: 155 | repo_token: ${{secrets.GITHUB_TOKEN}} 156 | directory: ${{steps.qt.outputs.outdir}}/${{matrix.version}}/${{matrix.platform}}/doc 157 | asset_name: qtrestclient-doc-${{matrix.version}} 158 | tag: ${{github.ref}} 159 | overwrite: true 160 | 161 | deploy: 162 | if: startsWith(github.ref, 'refs/tags/') 163 | needs: build 164 | runs-on: ubuntu-latest 165 | steps: 166 | - uses: actions/setup-python@v1 167 | - uses: Skycoder42/action-deploy-qt@master 168 | with: 169 | token: ${{secrets.GITHUB_TOKEN}} 170 | version: 5.14.1 171 | host: ${{secrets.SSHFS_HOST}} 172 | key: ${{secrets.SSHFS_KEY}} 173 | port: ${{secrets.SSHFS_PORT}} 174 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | *.qmlc 75 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/3rdparty/qthttpserver"] 2 | path = src/3rdparty/qthttpserver 3 | url = https://code.qt.io/qt-labs/qthttpserver.git 4 | -------------------------------------------------------------------------------- /.qmake.conf: -------------------------------------------------------------------------------- 1 | load(qt_build_config) 2 | 3 | CONFIG += warning_clean exceptions examples_need_tools tests_need_tools c++17 4 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS 5 | 6 | MODULE_VERSION_MAJOR = 3 7 | MODULE_VERSION_MINOR = 0 8 | MODULE_VERSION_PATCH = 0 9 | MODULE_VERSION_IMPORT = $${MODULE_VERSION_MAJOR}.$${MODULE_VERSION_MINOR} 10 | MODULE_VERSION = $${MODULE_VERSION_MAJOR}.$${MODULE_VERSION_MINOR}.$${MODULE_VERSION_PATCH} 11 | 12 | # had to be added because std::variant only works on macos 10.14 and above 13 | # remove again once Qt raises the value to 10.14! 14 | QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 15 | QMAKE_IOS_DEPLOYMENT_TARGET = 12.0 16 | 17 | LOGGING_RULES = qt.restclient.*.debug=true;qt.restclientauth.*.debug=true 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016, Felix Barz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "QtRestClient", 3 | "description": "A library for generic JSON-based REST-APIs, with a mechanism to map JSON to Qt objects.", 4 | "modules": [ "QtRestClient", "QtRestClientAuth" ], 5 | "dependencies": [ 6 | ".skycoder42.jsonserializer", 7 | ".qtnetworkauth" 8 | ], 9 | "excludes": [], 10 | "license": { 11 | "name": "BSD-3-Clause", 12 | "path": "LICENSE" 13 | }, 14 | "installs": { 15 | "qbs/Qt/restbuilder": "Tools/QtCreator/share/qtcreator/qbs/share/qbs/modules/Qt/restbuilder" 16 | }, 17 | "hostbuilds": [ 18 | "bin/qrestbuilder*" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /doc/doc.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = aux 2 | 3 | OTHER_FILES += Doxyfile \ 4 | makedoc.sh \ 5 | doxme.py \ 6 | ../README.md \ 7 | *.dox \ 8 | snippets/*.cpp \ 9 | images/* \ 10 | gh_header.html 11 | 12 | mkpath($$OUT_PWD/qtrestclient) 13 | !exists($$OUT_PWD/qtrestclient.qch):write_file($$OUT_PWD/qtrestclient.qch, __NOTHING) 14 | 15 | docTarget.target = doxygen 16 | docTarget.commands = $$PWD/makedoc.sh "$$PWD" "$$MODULE_VERSION" "$$[QT_INSTALL_BINS]" "$$[QT_INSTALL_HEADERS]" "$$[QT_INSTALL_DOCS]" 17 | QMAKE_EXTRA_TARGETS += docTarget 18 | 19 | docInst1.path = $$[QT_INSTALL_DOCS] 20 | docInst1.files = $$OUT_PWD/qtrestclient.qch 21 | docInst1.CONFIG += no_check_exist 22 | docInst2.path = $$[QT_INSTALL_DOCS] 23 | docInst2.files = $$OUT_PWD/qtrestclient 24 | INSTALLS += docInst1 docInst2 25 | -------------------------------------------------------------------------------- /doc/doxme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # $1 The readme to be transformed 3 | # $pwd: dest dir 4 | 5 | import sys 6 | import re 7 | 8 | def readFirst(line, out): 9 | if line[0:2] != "# ": 10 | raise ValueError("Expected first line to start with '# '") 11 | # skip the first line 12 | out.write("[TOC]\n\n") 13 | 14 | readCounter = 0 15 | regexp = re.compile(r'\W') 16 | skip = False 17 | def readMore(line, out): 18 | global skip 19 | if skip: 20 | if line[0:2] == "##": 21 | skip = False 22 | else: 23 | return 24 | 25 | if line.strip() == "## Table of contents": 26 | skip = True 27 | elif line[0:2] == "##": 28 | out.write(line[1:] + " {#" + regexp.sub('-', line.lstrip('#').strip()).lower() + "}\n") 29 | else: 30 | out.write(line + "\n") 31 | 32 | #read args 33 | readme = sys.argv[1] 34 | doxme = "./README.md" 35 | 36 | inFile = open(readme, "r") 37 | outFile = open(doxme, "w") 38 | 39 | isFirst = True 40 | for line in inFile: 41 | if isFirst: 42 | readFirst(line[:-1], outFile) 43 | isFirst = False 44 | else: 45 | readMore(line[:-1], outFile) 46 | 47 | inFile.close(); 48 | outFile.close(); 49 | -------------------------------------------------------------------------------- /doc/genericrestreply.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QtRestClient::GenericRestReply 3 | 4 | @tparam DataClassType The type of the positive result 5 | @tparam ErrorClassType The type of the negative result 6 | 7 | This class is an extension to the RestReply, that includes deserialization into the reply. This 8 | way, you can directly use them without ever caring about the CBOR/JSON in the background. 9 | 10 | @sa RestReply, RestClass 11 | */ 12 | 13 | /*! 14 | @fn QtRestClient::GenericRestReplyBase::onFailed(std::function) 15 | 16 | @param handler The function to be called on failure 17 | @returns A reference to this reply 18 | 19 | The handlers arguments are: 20 | - The HTTP-Status code (int) 21 | - The deserialized Content of the reply (ErrorClassType) 22 | 23 | @sa GenericRestReply::onAllErrors, GenericRestReply::onSucceeded, RestReply::onFailed 24 | */ 25 | 26 | /*! 27 | @fn QtRestClient::GenericRestReplyBase::onFailed(QObject*, std::function) 28 | @param scope A scope to limit the callback to 29 | @copydetails GenericRestReplyBase::onFailed(std::function) 30 | */ 31 | 32 | /*! 33 | @fn QtRestClient::GenericRestReplyBase::onSerializeException 34 | 35 | @param handler The function to be called on an exception 36 | @returns A reference to this reply 37 | 38 | The handlers arguments are: 39 | - The exception thrown by the QJsonSerializer (QtJsonSerializer::Exception) 40 | 41 | @sa GenericRestReplyBase::onAllErrors 42 | */ 43 | 44 | /*! 45 | @fn QtRestClient::GenericRestReplyBase::onAllErrors(const std::function&, std::function) 46 | 47 | @param handler The function to be called on any error 48 | @param failureTransformer A function to be called to extract an error string from the failure data 49 | @returns A reference to this reply 50 | 51 | The handlers arguments are: 52 | - The error string (QString) 53 | - For network error: QNetworkReply::errorString 54 | - For json error: QJsonParseError::errorString 55 | - For failure error: `failureTransformer` 56 | - For serializer error: QJsonSerializerException::what 57 | - The error code (int) 58 | - For network error: QNetworkReply::error 59 | - For json error: QJsonParseError::error 60 | - For failure error: The HTTP-Status code 61 | - For serializer error: 0 (unused) 62 | - The error type (RestReply::Error) 63 | 64 | The failureTransformer arguments are 65 | - The deserialized Content of the reply (ErrorClassType) 66 | - The HTTP-Status code (int) 67 | - _return:_ A string describing the error (QString) 68 | 69 | @sa GenericRestReply::onError, GenericRestReply::onFailed, 70 | GenericRestReply::onSerializeException, RestReply::onAllErrors 71 | */ 72 | 73 | /*! 74 | @fn QtRestClient::GenericRestReplyBase::onAllErrors(QObject*, const std::function&, std::function) 75 | @param scope A scope to limit the callback to 76 | @copydetails GenericRestReply::onAllErrors(const std::function&, std::function) 77 | */ 78 | 79 | /*! 80 | @fn QtRestClient::GenericRestReply::onSucceeded(std::function) 81 | 82 | @param handler The function to be called on success 83 | @returns A reference to this reply 84 | 85 | The handlers arguments are: 86 | - The HTTP-Status code (int) 87 | - The deserialized Content of the reply (DataClassType) 88 | 89 | @sa GenericRestReply::onFailed, RestReply::onSucceeded 90 | */ 91 | 92 | /*! 93 | @fn QtRestClient::GenericRestReply::onSucceeded(QObject*, std::function) 94 | @param scope A scope to limit the callback to 95 | @copydetails GenericRestReply::onSucceeded(std::function) 96 | */ 97 | -------------------------------------------------------------------------------- /doc/gh_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | $treeview 15 | $search 16 | $mathjax 17 | 18 | $extrastylesheet 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
33 |
$projectname 34 |  $projectnumber 35 |
36 |
$projectbrief
37 |
42 |
$projectbrief
43 |
$searchbox
54 | 55 | 56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /doc/images/GitHub_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/QtRestClient/62c3bce0cabe24a9b54b87af43e85da4c5061273/doc/images/GitHub_Logo.png -------------------------------------------------------------------------------- /doc/ipaging.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QtRestClient::IPaging 3 | 4 | Implement this interface, as well as a custom IPagingFactory, if you need to parse a non 5 | standard paging object. You should implement all of the methods correctly. However, you can 6 | still use the paging if you can't provide all of them. Simply return default values in that case. 7 | However, if you want to use the iterate functionality of the paging, you will have to implement 8 | all of them. 9 | 10 | The standard implementation expects CBOR or JSON data of the following format and will return 11 | either a ICborPaging or a IJsonPaging object: 12 | @code{.json} 13 | { 14 | "total": int, 15 | "offset": int, 16 | "limit": int, 17 | "previous": url|null, 18 | "next": url|null, 19 | "items": array 20 | } 21 | @endcode 22 | 23 | Additional fields will be ignored, but are still a valid paging objects. Missing fields or 24 | incompatibale types are not allowed, but wont create an error here. Validation is done by 25 | IPagingFactory::createPaging 26 | 27 | @sa ICborPaging, IJsonPaging, IPagingFactory, Paging 28 | */ 29 | 30 | /*! 31 | @fn QtRestClient::IPaging::total 32 | 33 | @returns The total amount of items for this request, or `std::numeric_limits::max()` if not available 34 | 35 | If you implement total, please note that total means total independently of the paging, and **not** 36 | the number of elements in the paging. It's the maximum number of items that exist for this request on 37 | the server. 38 | */ 39 | 40 | /*! 41 | @fn QtRestClient::IPaging::offset 42 | 43 | @returns The offset of the first item in the paging, or `-1` if not available 44 | 45 | Typically, pagings have indexes. However, since next() alone is sufficient to use a paging, not 46 | all may support it. Indexes means, that any item in a paging has a logical index, to identify it 47 | idependently of the paging object it is in. It's the real item index on the server. To do so, 48 | the offset of the first item in the paging must be known. 49 | 50 | If your paging does support indexes, you must implement the offset() method to return the offset 51 | of the first element in the paging. 52 | */ 53 | 54 | /*! 55 | @class QtRestClient::IPagingFactory 56 | 57 | In order to use paging, you need a factory to create those. A default IPagingFactory 58 | is used to do so. However, if you need to reimplement IPaging, you need to create a factory 59 | as well. 60 | 61 | @sa IPaging, Paging, RestClient::pagingFactory 62 | */ 63 | 64 | /*! 65 | @fn QtRestClient::IPagingFactory::createPaging 66 | 67 | @param serializer A json serializer, if you want to use it for deserialization 68 | @param data The paging CBOR/JSON data to be loaded into a IPaging interface 69 | @return A new paging interface instance 70 | @throws QJsonSerializer::DeserializationException Will be thrown if the passed json data is not 71 | a valid paging object 72 | 73 | When reimplementing this function, make shure to not return `nullptr`, if the creation failed. 74 | throw an exception instead, as specified by this documenation 75 | */ 76 | -------------------------------------------------------------------------------- /doc/makedoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1: $$SRCDIR 3 | # $2: $$VERSION 4 | # $3: $$[QT_INSTALL_BINS] 5 | # $4: $$[QT_INSTALL_HEADERS] 6 | # $5: $$[QT_INSTALL_DOCS] 7 | # $pwd: dest dir 8 | set -e 9 | 10 | scriptDir=$(dirname "$0") 11 | destDir="$(pwd)" 12 | srcDir=$1 13 | version=$2 14 | verTag=$(echo "$version" | sed -e 's/\.//g') 15 | qtBins=$3 16 | qtHeaders=$4 17 | qtDocs=$5 18 | doxyTemplate="$srcDir/Doxyfile" 19 | doxyRes=Doxyfile.generated 20 | readme="$destDir/README.md" 21 | doxme="$scriptDir/doxme.py" 22 | 23 | python3 "$doxme" "$srcDir/../README.md" 24 | 25 | cat "$doxyTemplate" > $doxyRes 26 | echo "PROJECT_NUMBER = \"$version\"" >> $doxyRes 27 | echo "INPUT += \"$readme\"" >> $doxyRes 28 | echo "USE_MDFILE_AS_MAINPAGE = \"$readme\"" >> $doxyRes 29 | echo "OUTPUT_DIRECTORY = \"$destDir\"" >> $doxyRes 30 | echo "QHP_NAMESPACE = \"de.skycoder42.qtrestclient.$verTag\"" >> $doxyRes 31 | echo "QHP_CUST_FILTER_NAME = \"RestClient $version\"" >> $doxyRes 32 | echo "QHP_CUST_FILTER_ATTRS = \"qtrestclient $version\"" >> $doxyRes 33 | echo "QHG_LOCATION = \"$qtBins/qhelpgenerator\"" >> $doxyRes 34 | echo "INCLUDE_PATH += \"$qtHeaders\"" >> $doxyRes 35 | echo "GENERATE_TAGFILE = \"$destDir/qtrestclient/qtrestclient.tags\"" >> $doxyRes 36 | if [ "$DOXY_STYLE" ]; then 37 | echo "HTML_STYLESHEET = \"$DOXY_STYLE\"" >> $doxyRes 38 | fi 39 | if [ "$DOXY_STYLE_EXTRA" ]; then 40 | echo "HTML_EXTRA_STYLESHEET = \"$DOXY_STYLE_EXTRA\"" >> $doxyRes 41 | fi 42 | 43 | for tagFile in $(find "$qtDocs" -name *.tags); do 44 | if [ $(basename "$tagFile") == "qtjsonserializer.tags" ]; then 45 | echo "TAGFILES += \"$tagFile=https://skycoder42.github.io/QJsonSerializer\"" >> $doxyRes 46 | elif [ $(basename "$tagFile") != "qtrestclient.tags" ]; then 47 | echo "TAGFILES += \"$tagFile=https://doc.qt.io/qt-5\"" >> $doxyRes 48 | fi 49 | done 50 | 51 | cd "$srcDir" 52 | doxygen "$destDir/$doxyRes" 53 | -------------------------------------------------------------------------------- /doc/paging.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QtRestClient::Paging 3 | 4 | @tparam T The type the paging object is wrapping 5 | 6 | The paging class is a container class that allows you to access generic paging mechanisms. 7 | 8 | Typically, APIs use paging as a method to reduce the size of replies. Instead of returning a 9 | complete list of elements, only a small chunck (i.e. 10 Elements) is returned, wrapped into a 10 | paging object. The object itself containes links, indexes and more to make it possible to 11 | iterate over them, by sending multiple consecutive requests. 12 | 13 | While most of these paging objects are very similar, they can have different structures. The 14 | Paging class, in combination with the IPaging interface, allows you to access any paging 15 | mechanism in an easy manner. 16 | 17 | @note All links in paging objects will be resolved relative to the clients URL. For absolute 18 | URLs, this means only stuff like headers or additional query parameters are added. Relative 19 | URLs however must be relative to the clients URL. If thats not the case, you can extend the 20 | private class StandardPaging to adjust the URLs, or implement your own IPaging. 21 | 22 | @sa Paging::iterate, IPaging, PagingFactory 23 | */ 24 | 25 | /*! 26 | @fn QtRestClient::Paging::next 27 | 28 | @returns A generic reply, for the sent request, or `nullptr`, if no next element exists 29 | 30 | The returned reply is owned by the caller. You can use it just like any other normal reply, 31 | created by a RestClass 32 | */ 33 | 34 | /*! 35 | @fn QtRestClient::Paging::previous 36 | 37 | @returns A generic reply, for the sent request, or `nullptr`, if no previous element exists 38 | 39 | The returned reply is owned by the caller. You can use it just like any other normal reply, 40 | created by a RestClass 41 | */ 42 | 43 | /*! 44 | @fn QtRestClient::Paging::iterate(const std::function&, qint64, qint64) const 45 | 46 | @param iterator The iterator to be be called for every element iterated over 47 | @param to The upper limit of how far the iteration should go (-1 means no limit) 48 | @param from The lower limit from where the iteration should start 49 | 50 | The paging object will iterate over all it's elements and then automatically call next(), 51 | and perform an iteration on the result of that reply as well. This continues until no next 52 | element is available anymore or the iterator returns `false` to cancel early. 53 | 54 | By setting `to`, you can specify a limit. As soon as the iteration reaches the element with an 55 | index that equals the limit, the iteration is canceled. The element at the `to` index will 56 | **not** be passed to the iterator. 57 | 58 | By setting `from`, you can specify an offset. The iteration will skip all elements, until the 59 | index reaches the offset. The element with the `from` index is the **first** to be passed to 60 | the iterator. Please note, that `from` must not be smaller then Paging::offset. If thats the 61 | case, this function will assert. 62 | 63 | @note Not all pagings support indexes. In that case, `to` and `from` will have no effect. 64 | 65 | The iterators parameters are: 66 | - One element of the deserialized Content of the paging replies (DataClassType) 67 | - The index of the current element or -1 if not supported (int) 68 | - _returns:_ `true` if the iteration should continue, `false` to cancel it prematurely 69 | */ 70 | 71 | /*! 72 | @fn QtRestClient::Paging::iterate(QObject *, const std::function&, qint64, qint64) const 73 | @param scope A scope to limit the callback to 74 | @copydetails Paging::iterate(const std::function&, qint64, qint64) const 75 | */ 76 | 77 | /*! 78 | @fn QtRestClient::Paging::iterate(const std::function &, const std::function &, const std::function&, qint64, qint64) const 79 | 80 | @tparam EO The type of the negative result 81 | @param errorHandler Will be passed to GenericRestReply::onAllErrors for all replies 82 | @param failureTransformer Will be passed to GenericRestReply::onAllErrors for all replies 83 | 84 | @copydetails QtRestClient::Paging::iterate(const std::function&, qint64, qint64) const 85 | */ 86 | 87 | /*! 88 | @fn QtRestClient::Paging::iterate(QObject *, const std::function &, const std::function &, const std::function&, qint64, qint64) const 89 | @param scope A scope to limit the callback to 90 | @copydetails Paging::iterate(const std::function &, const std::function &, const std::function&, qint64, qint64) const 91 | */ 92 | 93 | /*! 94 | @fn QtRestClient::Paging::iterate(const std::function&, const std::function&, const std::function&, const std::function&, qint64, qint64) const 95 | 96 | @tparam EO The type of the negative result 97 | @param failureHandler Will be passed to GenericRestReply::onFailed for all replies 98 | @param errorHandler Will be passed to GenericRestReply::onError for all replies 99 | @param exceptionHandler Will be passed to GenericRestReply::onSerializeException for all replies 100 | 101 | @copydetails QtRestClient::Paging::iterate(const std::function&, qint64, qint64) const 102 | */ 103 | 104 | /*! 105 | @fn QtRestClient::Paging::iterate(QObject *, const std::function&, const std::function&, const std::function&, const std::function&, qint64, qint64) const 106 | @param scope A scope to limit the callback to 107 | @copydetails Paging::iterate(const std::function&, const std::function&, const std::function&, const std::function&, qint64, qint64) const 108 | */ 109 | -------------------------------------------------------------------------------- /doc/qtrestclient.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @typedef QtRestClient::HeaderHash 3 | 4 | @sa RestClient::globalHeaders, RequestBuilder::addHeaders, QNetworkRequest::setRawHeader 5 | */ 6 | 7 | /*! 8 | @namespace QtRestClient::Auth 9 | 10 | @brief The namespace for the QNetworkAuth extensions 11 | */ 12 | 13 | /*! 14 | @namespace de::skycoder42::QtRestClient 15 | 16 | @brief The QML import for the QtRestClient QML module 17 | 18 | Current Version
19 |         3.0 20 | 21 | Available Types 22 | - QtRestClient (singleton) 23 | - RestReply (uncreatable) 24 | - GenericRestReply (uncreatable) 25 | - Paging (uncreatable) 26 | - RestClient 27 | - RestClass 28 | */ 29 | -------------------------------------------------------------------------------- /examples/examples.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS = restclient 4 | -------------------------------------------------------------------------------- /examples/restclient/JsonPlaceholderApi/JsonPlaceholderApi.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += core gui widgets restclient 4 | 5 | TARGET = JsonPlaceholderApi 6 | 7 | SOURCES += main.cpp \ 8 | mainwindow.cpp 9 | 10 | HEADERS += mainwindow.h 11 | 12 | FORMS += mainwindow.ui 13 | 14 | REST_API_FILES += post.xml \ 15 | api.xml \ 16 | api_posts.xml 17 | 18 | target.path = $$[QT_INSTALL_EXAMPLES]/restclient/$$TARGET 19 | !install_ok: INSTALLS += target 20 | -------------------------------------------------------------------------------- /examples/restclient/JsonPlaceholderApi/api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | api_posts.h 4 | https://jsonplaceholder.typicode.com 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/restclient/JsonPlaceholderApi/api_posts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | post.h 4 | posts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/restclient/JsonPlaceholderApi/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/restclient/JsonPlaceholderApi/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include 4 | using namespace QtRestClient; 5 | 6 | MainWindow::MainWindow(QWidget *parent) : 7 | QWidget(parent), 8 | ui(new Ui::MainWindow), 9 | api(new ExampleApi(this)) 10 | { 11 | ui->setupUi(this); 12 | api->restClient()->setModernAttributes(); 13 | } 14 | 15 | MainWindow::~MainWindow() 16 | { 17 | delete ui; 18 | } 19 | 20 | void MainWindow::on_reloadButton_clicked() 21 | { 22 | ui->postsTreeWidget->clear(); 23 | ui->reloadButton->setEnabled(false); 24 | api->posts()->listPosts()->onSucceeded([this](int code, QList posts){ 25 | ui->loadStatusLabel->setText(QStringLiteral("Status: %1").arg(code)); 26 | ui->reloadButton->setEnabled(true); 27 | for(const auto& post : qAsConst(posts)) { 28 | new QTreeWidgetItem(ui->postsTreeWidget, { 29 | QString::number(post.id()), 30 | QString::number(post.userId()), 31 | post.title(), 32 | post.body() 33 | }); 34 | } 35 | })->onAllErrors([this](QString error, int code, RestReply::ErrorType type){ 36 | onError(true, error, code, type); 37 | }); 38 | } 39 | 40 | void MainWindow::onError(bool isLoad, const QString& error, int code, RestReply::ErrorType type) 41 | { 42 | if(isLoad) 43 | ui->loadStatusLabel->setText(QStringLiteral("Status: %1").arg(type == RestReply::FailureError ? code : -1)); 44 | else 45 | ui->editStatusLabel->setText(QString::number(type == RestReply::FailureError ? code : -1)); 46 | 47 | QMessageBox::critical(this, 48 | QStringLiteral("Error of type %1").arg(type), 49 | QStringLiteral("Code: %1\nError Text: %2").arg(code).arg(error)); 50 | } 51 | 52 | void MainWindow::on_addButton_clicked() 53 | { 54 | api->posts()->savePost(getPost())->onSucceeded([this](int code, Post post){ 55 | ui->editStatusLabel->setText(QString::number(code)); 56 | setPost(post); 57 | })->onAllErrors([this](QString error, int code, RestReply::ErrorType type){ 58 | onError(false, error, code, type); 59 | }); 60 | } 61 | 62 | void MainWindow::on_updateButton_clicked() 63 | { 64 | api->posts()->updatePost(getPost(), ui->idSpinBox->value())->onSucceeded([this](int code, Post post){ 65 | ui->editStatusLabel->setText(QString::number(code)); 66 | setPost(post); 67 | })->onAllErrors([this](QString error, int code, RestReply::ErrorType type){ 68 | onError(false, error, code, type); 69 | }); 70 | } 71 | 72 | void MainWindow::on_deleteButton_clicked() 73 | { 74 | api->posts()->deletePost(ui->idSpinBox->value())->onSucceeded([this](int code){ 75 | ui->editStatusLabel->setText(QString::number(code)); 76 | clearPost(); 77 | })->onAllErrors([this](QString error, int code, RestReply::ErrorType type){ 78 | onError(false, error, code, type); 79 | }); 80 | } 81 | 82 | Post MainWindow::getPost() const 83 | { 84 | Post p; 85 | p.setId(ui->idSpinBox->value()); 86 | p.setUserId(ui->userIDSpinBox->value()); 87 | p.setTitle(ui->titleLineEdit->text()); 88 | p.setBody(ui->bodyLineEdit->text()); 89 | return p; 90 | } 91 | 92 | void MainWindow::setPost(const Post &post) 93 | { 94 | ui->replyIdBox->setValue(post.id()); 95 | ui->replyUserBox->setValue(post.userId()); 96 | ui->replyTitleBox->setText(post.title()); 97 | ui->replyBodyBox->setText(post.body()); 98 | } 99 | 100 | void MainWindow::clearPost() 101 | { 102 | ui->replyIdBox->clear(); 103 | ui->replyUserBox->clear(); 104 | ui->replyTitleBox->clear(); 105 | ui->replyBodyBox->clear(); 106 | } 107 | -------------------------------------------------------------------------------- /examples/restclient/JsonPlaceholderApi/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class MainWindow; 9 | } 10 | 11 | class MainWindow : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit MainWindow(QWidget *parent = nullptr); 17 | ~MainWindow(); 18 | 19 | private slots: 20 | void on_reloadButton_clicked(); 21 | void on_addButton_clicked(); 22 | void on_updateButton_clicked(); 23 | void on_deleteButton_clicked(); 24 | 25 | private: 26 | Ui::MainWindow *ui; 27 | ExampleApi *api; 28 | 29 | Post getPost() const; 30 | void setPost(const Post &post); 31 | void clearPost(); 32 | void onError(bool isLoad, const QString& error, int code, QtRestClient::RestReply::ErrorType type); 33 | }; 34 | 35 | #endif // MAINWINDOW_H 36 | -------------------------------------------------------------------------------- /examples/restclient/JsonPlaceholderApi/post.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/restclient/SimpleRestClientApp/SimpleRestClientApp.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += core gui widgets restclient network 4 | 5 | TARGET = SimpleRestClientApp 6 | 7 | SOURCES += main.cpp \ 8 | mainwindow.cpp 9 | 10 | HEADERS += mainwindow.h 11 | 12 | FORMS += mainwindow.ui 13 | 14 | target.path = $$[QT_INSTALL_EXAMPLES]/restclient/$$TARGET 15 | !install_ok: INSTALLS += target 16 | -------------------------------------------------------------------------------- /examples/restclient/SimpleRestClientApp/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/restclient/SimpleRestClientApp/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | #include 5 | #include 6 | #include 7 | using namespace QtRestClient; 8 | 9 | Q_DECLARE_OPERATORS_FOR_FLAGS(QCborValue::DiagnosticNotationOptions) 10 | 11 | MainWindow::MainWindow(QWidget *parent) : 12 | QMainWindow{parent}, 13 | _ui{new Ui::MainWindow{}}, 14 | _client{new RestClient{this}} 15 | { 16 | _ui->setupUi(this); 17 | 18 | _client->setBaseUrl(QStringLiteral("http://api.example.com")); 19 | 20 | connect(_client->manager(), &QNetworkAccessManager::authenticationRequired, 21 | this, &MainWindow::authenticate); 22 | } 23 | 24 | MainWindow::~MainWindow() = default; 25 | 26 | void MainWindow::on_cborButton_toggled(bool checked) 27 | { 28 | if (checked) 29 | _client->setDataMode(RestClient::DataMode::Cbor); 30 | } 31 | 32 | void MainWindow::on_jsonButton_toggled(bool checked) 33 | { 34 | if (checked) 35 | _client->setDataMode(RestClient::DataMode::Json); 36 | } 37 | 38 | void MainWindow::on_addParamButton_clicked() 39 | { 40 | auto item = new QTreeWidgetItem(_ui->paramTreeWidget); 41 | item->setFlags(item->flags() | Qt::ItemIsEditable); 42 | _ui->paramTreeWidget->setCurrentItem(item); 43 | _ui->paramTreeWidget->editItem(item); 44 | } 45 | 46 | void MainWindow::on_removeParamButton_clicked() 47 | { 48 | auto current = _ui->paramTreeWidget->currentItem(); 49 | if (current) 50 | delete current; 51 | } 52 | 53 | void MainWindow::on_addHeaderButton_clicked() 54 | { 55 | auto item = new QTreeWidgetItem(_ui->headersTreeWidget); 56 | item->setFlags(item->flags() | Qt::ItemIsEditable); 57 | _ui->headersTreeWidget->setCurrentItem(item); 58 | _ui->headersTreeWidget->editItem(item); 59 | } 60 | 61 | void MainWindow::on_removeHeaderButton_clicked() 62 | { 63 | auto current = _ui->headersTreeWidget->currentItem(); 64 | if (current) 65 | delete current; 66 | } 67 | 68 | void MainWindow::on_pushButton_clicked() 69 | { 70 | _ui->tabWidget->setCurrentWidget(_ui->replyTab); 71 | 72 | try { 73 | QVariantHash params; 74 | for (auto i = 0; i < _ui->paramTreeWidget->topLevelItemCount(); i++) { 75 | auto item = _ui->paramTreeWidget->topLevelItem(i); 76 | params.insert(item->text(0), item->text(1)); 77 | } 78 | 79 | HeaderHash headers; 80 | for (auto i = 0; i < _ui->headersTreeWidget->topLevelItemCount(); i++) { 81 | auto item = _ui->headersTreeWidget->topLevelItem(i); 82 | headers.insert(item->text(0).toUtf8(), item->text(1).toUtf8()); 83 | } 84 | 85 | auto body = parseBody(); 86 | RestReply *reply; 87 | if (std::holds_alternative(body)) { 88 | reply = _client->rootClass()->callRaw(_ui->verbComboBox->currentText().toUtf8(), 89 | QUrl{_ui->urlLineEdit->text()}, 90 | std::get(body), 91 | params, 92 | headers); 93 | } else if(std::holds_alternative(body)) { 94 | reply = _client->rootClass()->callRaw(_ui->verbComboBox->currentText().toUtf8(), 95 | QUrl{_ui->urlLineEdit->text()}, 96 | std::get(body), 97 | params, 98 | headers); 99 | } else { 100 | reply = _client->rootClass()->callRaw(_ui->verbComboBox->currentText().toUtf8(), 101 | QUrl{_ui->urlLineEdit->text()}, 102 | params, 103 | headers); 104 | } 105 | 106 | reply->onSucceeded(this, [=](int status, const RestReply::DataType &value){ 107 | _ui->codeLineEdit->setText(QString::number(status)); 108 | _ui->networkErrorLineEdit->clear(); 109 | writeBody(value); 110 | QTimer::singleShot(2000, this, &MainWindow::zeroBars); 111 | }); 112 | reply->onFailed(this, [=](int status, const RestReply::DataType &value){ 113 | _ui->codeLineEdit->setText(QString::number(status)); 114 | _ui->networkErrorLabel->setText(tr("Request failure:")); 115 | _ui->networkErrorLineEdit->setText(tr("Request Failed! See JSON for more details!")); 116 | writeBody(value); 117 | QTimer::singleShot(2000, this, &MainWindow::zeroBars); 118 | }); 119 | reply->onError(this, [=](const QString &errorString, int code, RestReply::Error type){ 120 | _ui->codeLineEdit->setText(QString::number(code)); 121 | switch (type) { 122 | case RestReply::Error::Network: 123 | _ui->networkErrorLabel->setText(tr("Network error:")); 124 | break; 125 | case RestReply::Error::Parser: 126 | _ui->networkErrorLabel->setText(tr("JSON/CBOR parse error:")); 127 | break; 128 | case RestReply::Error::Failure: 129 | _ui->networkErrorLabel->setText(tr("Request failure:")); 130 | break; 131 | case RestReply::Error::Deserialization: 132 | _ui->networkErrorLabel->setText(tr("Deserialization error:")); 133 | break; 134 | } 135 | _ui->networkErrorLineEdit->setText(errorString); 136 | _ui->replyJsonEdit->clear(); 137 | QTimer::singleShot(2000, this, &MainWindow::zeroBars); 138 | }); 139 | 140 | _ui->requestUrlLineEdit->setText(reply->networkReply()->url().toString()); 141 | connect(reply, &RestReply::uploadProgress, this, [this](int c, int m){ 142 | _ui->uploadBar->setMaximum(m); 143 | _ui->uploadBar->setValue(c); 144 | }); 145 | connect(reply, &RestReply::downloadProgress, this, [this](int c, int m){ 146 | _ui->downloadBar->setMaximum(m); 147 | _ui->downloadBar->setValue(c); 148 | }); 149 | } catch (QJsonParseError &e) { 150 | _ui->codeLineEdit->setText(QString::number(e.error)); 151 | _ui->networkErrorLineEdit->setText(e.errorString()); 152 | _ui->replyJsonEdit->clear(); 153 | } catch (std::exception &e) { 154 | _ui->codeLineEdit->clear(); 155 | _ui->networkErrorLineEdit->setText(QString::fromUtf8(e.what())); 156 | _ui->replyJsonEdit->clear(); 157 | } 158 | } 159 | 160 | void MainWindow::authenticate(QNetworkReply *, QAuthenticator *auth) 161 | { 162 | auth->setUser(_ui->usernameLineEdit->text()); 163 | auth->setPassword(_ui->passwordLineEdit->text()); 164 | } 165 | 166 | void MainWindow::zeroBars() 167 | { 168 | _ui->uploadBar->setMaximum(100); 169 | _ui->uploadBar->setValue(0); 170 | _ui->downloadBar->setMaximum(100); 171 | _ui->downloadBar->setValue(0); 172 | } 173 | 174 | RestReply::DataType MainWindow::parseBody() const 175 | { 176 | const auto data = _ui->bodyJsonEdit->toPlainText(); 177 | if (data.isEmpty()) 178 | return std::nullopt; 179 | 180 | QJsonParseError error; 181 | auto doc = QJsonDocument::fromJson(data.toUtf8(), &error); 182 | QJsonValue body = QJsonValue::Null; 183 | if (error.error != QJsonParseError::NoError) 184 | throw error; 185 | else if (doc.isObject()) 186 | body = doc.object(); 187 | else if (doc.isArray()) 188 | body = doc.array(); 189 | 190 | if (_client->dataMode() == RestClient::DataMode::Cbor) 191 | return QCborValue::fromJsonValue(body); 192 | else 193 | return body; 194 | } 195 | 196 | void MainWindow::writeBody(const RestReply::DataType &data) 197 | { 198 | if (std::holds_alternative(data)) 199 | _ui->replyJsonEdit->setPlainText(std::get(data).toDiagnosticNotation(QCborValue::LineWrapped | QCborValue::ExtendedFormat)); 200 | else if (std::holds_alternative(data)) { 201 | const auto jValue = std::get(data); 202 | QJsonDocument doc; 203 | if (jValue.isObject()) 204 | doc = QJsonDocument{jValue.toObject()}; 205 | else 206 | doc = QJsonDocument{jValue.toArray()}; 207 | _ui->replyJsonEdit->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented))); 208 | } else 209 | _ui->replyJsonEdit->clear(); 210 | } 211 | -------------------------------------------------------------------------------- /examples/restclient/SimpleRestClientApp/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Ui { 11 | class MainWindow; 12 | } 13 | 14 | class MainWindow : public QMainWindow 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit MainWindow(QWidget *parent = nullptr); 20 | ~MainWindow() override; 21 | 22 | private slots: 23 | void on_cborButton_toggled(bool checked); 24 | void on_jsonButton_toggled(bool checked); 25 | void on_addParamButton_clicked(); 26 | void on_removeParamButton_clicked(); 27 | void on_addHeaderButton_clicked(); 28 | void on_removeHeaderButton_clicked(); 29 | void on_pushButton_clicked(); 30 | 31 | void authenticate(QNetworkReply *reply, QAuthenticator *auth); 32 | void zeroBars(); 33 | 34 | private: 35 | QScopedPointer _ui; 36 | QtRestClient::RestClient *_client; 37 | 38 | QtRestClient::RestReply::DataType parseBody() const; 39 | void writeBody(const QtRestClient::RestReply::DataType &data); 40 | }; 41 | 42 | #endif // MAINWINDOW_H 43 | -------------------------------------------------------------------------------- /examples/restclient/restclient.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | QT_FOR_CONFIG += core network jsonserializer 3 | 4 | SUBDIRS += \ 5 | SimpleRestClientApp 6 | 7 | !no_json_serializer { 8 | SUBDIRS += \ 9 | #JsonPlaceholderApi 10 | } 11 | -------------------------------------------------------------------------------- /mkspecs/features/qrestbuilder.prf: -------------------------------------------------------------------------------- 1 | qtPrepareTool(QMAKE_QRESTBUILDER, qrestbuilder) 2 | 3 | isEmpty(QRESTBUILDER_DIR):QRESTBUILDER_DIR = . 4 | isEmpty(MOC_DIR):MOC_DIR = . 5 | 6 | debug_and_release { 7 | CONFIG(debug, debug|release):SUFFIX = /debug 8 | CONFIG(release, debug|release):SUFFIX = /release 9 | } 10 | 11 | QRESTBUILDER_DIR = $$QRESTBUILDER_DIR$$SUFFIX 12 | 13 | { 14 | qrestbuilder_c.name = QRESTBUILDER ${QMAKE_FILE_IN}.h 15 | qrestbuilder_c.input = REST_API_FILES 16 | qrestbuilder_c.variable_out = QRESTBUILDER_HEADERS 17 | qrestbuilder_c.commands = $$QMAKE_QRESTBUILDER --in ${QMAKE_FILE_IN} --header ${QMAKE_FILE_OUT} --impl $$QRESTBUILDER_DIR/${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} 18 | qrestbuilder_c.output = $$QRESTBUILDER_DIR/${QMAKE_FILE_BASE}$${first(QMAKE_EXT_H)} 19 | qrestbuilder_c.CONFIG += target_predeps 20 | qrestbuilder_c.depends += $$QMAKE_QRESTBUILDER_EXE 21 | QMAKE_EXTRA_COMPILERS += qrestbuilder_c 22 | 23 | qrestbuilder_m.name = QRESTBUILDER_MOC ${QMAKE_FILE_IN} 24 | qrestbuilder_m.input = QRESTBUILDER_HEADERS 25 | qrestbuilder_m.variable_out = GENERATED_SOURCES 26 | qrestbuilder_m.commands = ${QMAKE_FUNC_mocCmdBase} ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} 27 | qrestbuilder_m.output = $$MOC_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} 28 | qrestbuilder_m.CONFIG += target_predeps 29 | qrestbuilder_m.depends += $$QMAKE_QRESTBUILDER_EXE $$WIN_INCLUDETEMP $$moc_predefs.output 30 | qrestbuilder_m.dependency_type = TYPE_C 31 | QMAKE_EXTRA_COMPILERS += qrestbuilder_m 32 | 33 | qrestbuilder_s.name = QRESTBUILDER ${QMAKE_FILE_IN}.cpp 34 | qrestbuilder_s.input = QRESTBUILDER_HEADERS 35 | qrestbuilder_s.variable_out = GENERATED_SOURCES 36 | qrestbuilder_s.commands = $$escape_expand(\\n) # force creation of rule 37 | qrestbuilder_s.output = $$QRESTBUILDER_DIR/${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} 38 | qrestbuilder_s.CONFIG += target_predeps 39 | QMAKE_EXTRA_COMPILERS += qrestbuilder_s 40 | } 41 | 42 | INCLUDEPATH += $$absolute_path($$QRESTBUILDER_DIR, $$OUT_PWD) 43 | QMAKE_DIR_REPLACE += QRESTBUILDER_DIR 44 | QMAKE_DIR_REPLACE_SANE += QRESTBUILDER_DIR 45 | -------------------------------------------------------------------------------- /qbs/Qt/restbuilder/module.qbs: -------------------------------------------------------------------------------- 1 | import qbs 2 | import qbs.FileInfo 3 | 4 | Module { 5 | Depends { name: "Qt.restclient" } 6 | 7 | property string qrestbuilderName: "qrestbuilder" 8 | 9 | version: Qt.restclient.version 10 | 11 | FileTagger { 12 | fileTags: ["restclient-xml"] 13 | patterns: ["*.rc.xml"] 14 | } 15 | 16 | Rule { 17 | inputs: ["restclient-xml"] 18 | 19 | Artifact { 20 | filePath: input.baseName + ".h" 21 | fileTags: ["hpp"] 22 | } 23 | Artifact { 24 | filePath: input.baseName + ".cpp" 25 | fileTags: ["cpp"] 26 | } 27 | 28 | prepare: { 29 | var cmd = new Command(); 30 | cmd.description = "generating rest-api class" + input.fileName; 31 | cmd.highlight = "codegen"; 32 | cmd.program = FileInfo.joinPaths(product.moduleProperty("Qt.core", "binPath"), 33 | product.moduleProperty("Qt.restbuilder", "qrestbuilderName")); 34 | cmd.arguments = [ 35 | "--in", input.filePath, 36 | "--header", outputs["hpp"][0].filePath, 37 | "--impl", outputs["cpp"][0].filePath 38 | ]; 39 | return cmd; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /qtrestclient.pro: -------------------------------------------------------------------------------- 1 | load(qt_parts) 2 | 3 | SUBDIRS += doc 4 | 5 | doxygen.target = doxygen 6 | doxygen.CONFIG = recursive 7 | doxygen.recurse_target = doxygen 8 | doxygen.recurse += doc 9 | QMAKE_EXTRA_TARGETS += doxygen 10 | 11 | runtests.target = run-tests 12 | runtests.CONFIG = recursive 13 | runtests.recurse_target = run-tests 14 | runtests.recurse += sub_tests sub_src sub_tools 15 | QMAKE_EXTRA_TARGETS += runtests 16 | 17 | DISTFILES += .qmake.conf \ 18 | sync.profile \ 19 | qbs/Qt/restbuilder/* 20 | -------------------------------------------------------------------------------- /src/3rdparty/optional-lite/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/3rdparty/variant-lite/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/imports/imports.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | restclient 5 | -------------------------------------------------------------------------------- /src/imports/restclient/qmldir: -------------------------------------------------------------------------------- 1 | module de.skycoder42.RestClient 2 | plugin declarative_restclient 3 | classname QtRestClientDeclarativeModule 4 | typeinfo plugins.qmltypes 5 | depends QtQml 2.13 6 | depends QtQml.Models 2.13 7 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlgenericrestreply.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlgenericrestreply.h" 2 | #include 3 | using namespace QtJsonSerializer; 4 | 5 | QtRestClient::QmlGenericRestReply::QmlGenericRestReply(SerializerBase *serializer, QJSEngine *engine, int returnType, int errorType, QtRestClient::RestReply *reply) : 6 | QObject{reply}, 7 | _serializer{serializer}, 8 | _engine{engine}, 9 | _returnType{returnType}, 10 | _errorType{errorType}, 11 | _reply{reply} 12 | {} 13 | 14 | QString QtRestClient::QmlGenericRestReply::returnType() const 15 | { 16 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 17 | return QString::fromUtf8(QMetaType::typeName(_returnType)); 18 | #else 19 | return QString::fromUtf8(QMetaType(_returnType).name()); 20 | #endif 21 | } 22 | 23 | QString QtRestClient::QmlGenericRestReply::errorType() const 24 | { 25 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 26 | return QString::fromUtf8(QMetaType::typeName(_errorType)); 27 | #else 28 | return QString::fromUtf8(QMetaType(_errorType).name()); 29 | #endif 30 | } 31 | 32 | void QtRestClient::QmlGenericRestReply::addCompletedHandler(const QJSValue &completedHandler) 33 | { 34 | if (!checkOk(completedHandler)) 35 | return; 36 | 37 | _reply->onCompleted([completedHandler](int code){ 38 | auto fn = completedHandler; 39 | fn.call({code}); 40 | }); 41 | } 42 | 43 | void QtRestClient::QmlGenericRestReply::addSucceededHandler(const QJSValue &succeededHandler) 44 | { 45 | if (!checkOk(succeededHandler)) 46 | return; 47 | 48 | QPointer serializer{_serializer}; 49 | QPointer engine{_engine}; 50 | QPointer reply{_reply}; 51 | auto type = _returnType; 52 | 53 | _reply->onSucceeded([this, succeededHandler, serializer, engine, reply, type](int code, const RestReply::DataType &data) { 54 | if (!serializer || !engine) 55 | return; 56 | try { 57 | auto fn = succeededHandler; 58 | std::visit(__private::overload { 59 | [&](std::nullopt_t) { 60 | fn.call({code, QJSValue{}}); 61 | }, 62 | [&](const auto &vData) { 63 | const auto var = serializer->deserializeGeneric(vData, type, reply); 64 | fn.call({code, engine->toScriptValue(var)}); 65 | } 66 | }, data); 67 | } catch (std::exception &e) { 68 | qmlWarning(this) << "ERROR:" << e.what(); 69 | } 70 | }); 71 | } 72 | 73 | void QtRestClient::QmlGenericRestReply::addFailedHandler(const QJSValue &failedHandler) 74 | { 75 | if (!checkOk(failedHandler)) 76 | return; 77 | 78 | QPointer serializer{_serializer}; 79 | QPointer engine{_engine}; 80 | QPointer reply{_reply}; 81 | auto type = _errorType; 82 | _reply->onFailed([this, failedHandler, serializer, engine, reply, type](int code, const RestReply::DataType &data) { 83 | if(!serializer || !engine) 84 | return; 85 | try { 86 | auto fn = failedHandler; 87 | std::visit(__private::overload { 88 | [&](std::nullopt_t) { 89 | fn.call({code, QJSValue{}}); 90 | }, 91 | [&](const auto &vData) { 92 | try { 93 | const auto var = serializer->deserializeGeneric(vData, type, reply); 94 | fn.call({code, engine->toScriptValue(var)}); 95 | } catch (DeserializationException &e) { 96 | fn.call({code, QString::fromUtf8(e.message())}); 97 | throw; 98 | } 99 | } 100 | }, data); 101 | } catch (std::exception &e) { 102 | qmlWarning(this) << "ERROR:" << e.what(); 103 | } 104 | }); 105 | } 106 | 107 | void QtRestClient::QmlGenericRestReply::addErrorHandler(const QJSValue &errorHandler) 108 | { 109 | if (!checkOk(errorHandler)) 110 | return; 111 | 112 | _reply->onError([errorHandler](const QString &error, int code, RestReply::Error type){ 113 | auto fn = errorHandler; 114 | fn.call({error, code, static_cast(type)}); 115 | }); 116 | } 117 | 118 | bool QtRestClient::QmlGenericRestReply::checkOk(const QJSValue &fn) const 119 | { 120 | if (!_reply) { 121 | qmlWarning(this) << "Cannot assign a handler to an invalid reply"; 122 | return false; 123 | } 124 | 125 | if (!fn.isCallable()) { 126 | qmlWarning(this) << "Passed JS object is not a callable function!"; 127 | return false; 128 | } 129 | 130 | return true; 131 | } 132 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlgenericrestreply.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_QMLGENERICRESTREPLY_H 2 | #define QTRESTCLIENT_QMLGENERICRESTREPLY_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | #ifdef DOXYGEN_RUN 14 | namespace de::skycoder42::QtRestClient { 15 | 16 | /*! @brief The QML version of ::QtRestClient::GenericRestReply 17 | * 18 | * @extends QtQml.QtObject 19 | * @since 2.0 20 | * 21 | * This is a special version that is only used by classes generated by qrestbuilder 22 | */ 23 | class GenericRestReply 24 | #else 25 | namespace QtRestClient { 26 | 27 | class QmlGenericRestReply : public QObject 28 | #endif 29 | { 30 | Q_OBJECT 31 | 32 | /*! @brief The original ::QtRestClient::RestReply that this one wrapps 33 | * 34 | * @default{auto} 35 | * @accessors{ 36 | * @memberAc{reply} 37 | * @readonlyAc 38 | * } 39 | * @sa ::QtRestClient::RestReply 40 | */ 41 | Q_PROPERTY(QtRestClient::RestReply *reply MEMBER _reply CONSTANT) 42 | /*! @brief The primary reply type returned by this request 43 | * 44 | * @default{auto} 45 | * @accessors{ 46 | * @memberAc{returnType} 47 | * @readonlyAc 48 | * } 49 | */ 50 | Q_PROPERTY(QString returnType READ returnType CONSTANT) 51 | /*! @brief The error reply type returned by this request 52 | * 53 | * @default{auto} 54 | * @accessors{ 55 | * @memberAc{errorType} 56 | * @readonlyAc 57 | * } 58 | */ 59 | Q_PROPERTY(QString errorType READ errorType CONSTANT) 60 | 61 | public: 62 | //! @private 63 | Q_INVOKABLE QmlGenericRestReply(QtJsonSerializer::SerializerBase *serializer, 64 | QJSEngine *engine, 65 | int returnType, 66 | int errorType, 67 | QtRestClient::RestReply *reply); 68 | 69 | //! @private 70 | QString returnType() const; 71 | //! @private 72 | QString errorType() const; 73 | 74 | public Q_SLOTS: 75 | /*! @brief Add a method to be called when the request has been completed 76 | * 77 | * @param completedHandler a JS callback to be called with the reply 78 | * 79 | * The handlers arguments are: 80 | * - The HTTP-Status code (int) 81 | */ 82 | void addCompletedHandler(const QJSValue &completedHandler); 83 | /*! @brief Add a method to be called when the request has been completed successfully 84 | * 85 | * @param succeededHandler a JS callback to be called with the reply 86 | * 87 | * The handlers arguments are: 88 | * - The HTTP-Status code (int) 89 | * - The deserialized Content of the reply (returnType) 90 | */ 91 | void addSucceededHandler(const QJSValue &succeededHandler); 92 | /*! @brief Add a method to be called if the request has failed 93 | * 94 | * @param failedHandler a JS callback to be called with the reply 95 | * 96 | * The handlers arguments are: 97 | * - The HTTP-Status code (int) 98 | * - The deserialized Content of the reply (errorType) 99 | */ 100 | void addFailedHandler(const QJSValue &failedHandler); 101 | /*! @brief Add a method to be called if an error occured 102 | * 103 | * @param errorHandler a JS callback to be called with the reply 104 | * 105 | * The handlers arguments are: 106 | * - The error string (string) 107 | * - The error code (int) 108 | * - The error type (::QtRestClient::RestReply::Error) 109 | */ 110 | void addErrorHandler(const QJSValue &errorHandler); 111 | 112 | private: 113 | QtJsonSerializer::SerializerBase *_serializer; 114 | QJSEngine *_engine; 115 | int _returnType; 116 | int _errorType; 117 | RestReply* _reply; 118 | 119 | bool checkOk(const QJSValue &fn) const; 120 | }; 121 | 122 | } 123 | 124 | Q_DECLARE_METATYPE(QtRestClient::QmlGenericRestReply*) 125 | 126 | #endif // QTRESTCLIENT_QMLGENERICRESTREPLY_H 127 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlpaging.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlpaging.h" 2 | #include 3 | #include 4 | #include 5 | using namespace QtRestClient; 6 | 7 | QmlPaging::QmlPaging(IPaging *iPaging, RestClient *client, QJSEngine *engine) : 8 | _engine(engine), 9 | _client(client), 10 | _paging(iPaging) 11 | {} 12 | 13 | QmlPaging QmlPaging::create(RestClient *client, QJSEngine *engine, const RestReply::DataType &data) 14 | { 15 | auto iPaging = std::visit(__private::overload { 16 | [](std::nullopt_t) -> IPaging* { 17 | return nullptr; 18 | }, 19 | [&](const auto &vData) { 20 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 21 | return client->pagingFactory()->createPaging(client->serializer(), vData); 22 | #else 23 | return client->pagingFactory()->createPaging(vData); 24 | #endif 25 | } 26 | }, data); 27 | return QmlPaging{iPaging, client, engine}; 28 | } 29 | 30 | bool QmlPaging::isValid() const 31 | { 32 | return static_cast(_paging); 33 | } 34 | 35 | RestReply *QmlPaging::next() 36 | { 37 | if (_paging->hasNext()) { 38 | return new RestReply{ 39 | _client->builder() 40 | .updateFromRelativeUrl(_paging->next(), true) 41 | .send(), 42 | _client 43 | }; 44 | } else 45 | return nullptr; 46 | } 47 | 48 | RestReply *QmlPaging::previous() 49 | { 50 | if (_paging->hasPrevious()) { 51 | return new RestReply{ 52 | _client->builder() 53 | .updateFromRelativeUrl(_paging->previous(), true) 54 | .send(), 55 | _client 56 | }; 57 | } else 58 | return nullptr; 59 | } 60 | 61 | QVariantList QmlPaging::items() const 62 | { 63 | return std::visit([](const auto &items){ 64 | return items.toVariantList(); 65 | }, _paging->items()); 66 | } 67 | 68 | int QmlPaging::total() const 69 | { 70 | return static_cast(_paging->total()); 71 | } 72 | 73 | int QmlPaging::offset() const 74 | { 75 | return static_cast(_paging->offset()); 76 | } 77 | 78 | bool QmlPaging::hasNext() const 79 | { 80 | return _paging->hasNext(); 81 | } 82 | 83 | QUrl QmlPaging::nextUrl() const 84 | { 85 | return _paging->next(); 86 | } 87 | 88 | bool QmlPaging::hasPrevious() const 89 | { 90 | return _paging->hasPrevious(); 91 | } 92 | 93 | QUrl QmlPaging::previousUrl() const 94 | { 95 | return _paging->previous(); 96 | } 97 | 98 | QVariantMap QmlPaging::properties() const 99 | { 100 | return _paging->properties(); 101 | } 102 | 103 | void QmlPaging::iterate(const QJSValue &iterator, int to, int from) 104 | { 105 | iterate(iterator, {}, {}, to, from); 106 | } 107 | 108 | void QmlPaging::iterate(const QJSValue &iterator, const QJSValue &failureHandler, const QJSValue &errorHandler, int to, int from) 109 | { 110 | if (!iterator.isCallable()) { 111 | qWarning() << "iterator parameter must be a function"; 112 | return; 113 | } 114 | if (!failureHandler.isUndefined() && !failureHandler.isCallable()) { 115 | qWarning() << "failureHandler parameter must be a function or undefined"; 116 | return; 117 | } 118 | if (!errorHandler.isUndefined() && !errorHandler.isCallable()) { 119 | qWarning() << "errorHandler parameter must be a function or undefined"; 120 | return; 121 | } 122 | if (from < _paging->offset()) { 123 | qWarning() << "from must not be smaller then offset (from:" << from << ", offset:" << _paging->offset() << ")"; 124 | return; 125 | } 126 | 127 | auto index = internalIterate(iterator, from, to); 128 | if (index < 0) 129 | return; 130 | 131 | // calc total limit -> only if supports indexes 132 | auto max = std::numeric_limits::max(); 133 | if (_paging->offset() >= 0) { 134 | if (to >= 0) 135 | max = std::min(static_cast(to), _paging->total()); 136 | else 137 | max = _paging->total(); 138 | } 139 | 140 | // continue to the next one 141 | if (index < max && _paging->hasNext()) { 142 | QPointer client{_client}; 143 | QPointer engine{_engine}; 144 | 145 | auto reply = next()->onSucceeded([client, engine, iterator, failureHandler, errorHandler, to, index](int, const RestReply::DataType &data) { 146 | if (!client || !engine) 147 | return; 148 | auto paging = create(client, engine, data); 149 | if (paging.isValid()) 150 | paging.iterate(iterator, failureHandler, errorHandler, to, index); 151 | }); 152 | 153 | if (failureHandler.isCallable()) { 154 | reply->onFailed([engine, failureHandler](int code, const RestReply::DataType &data) { 155 | if (!engine) 156 | return; 157 | auto fn = failureHandler; 158 | auto jsValue = std::visit(__private::overload { 159 | [](std::nullopt_t) { 160 | return QJSValue{}; 161 | }, 162 | [&](const auto &vData) { 163 | return engine->toScriptValue(vData); 164 | } 165 | }, data); 166 | fn.call({code, jsValue}); 167 | }); 168 | } 169 | if (errorHandler.isCallable()) { 170 | reply->onError([errorHandler](const QString &error, int code, RestReply::Error type) { 171 | auto fn = errorHandler; 172 | fn.call({error, code, static_cast(type)}); 173 | }); 174 | } 175 | } 176 | } 177 | 178 | int QmlPaging::internalIterate(QJSValue iterator, int from, int to) const 179 | { 180 | return std::visit([&](const auto &items) { 181 | // handle all items in this paging 182 | auto offset = _paging->offset(); 183 | auto count = static_cast(items.size()); 184 | auto start = 0ll; 185 | auto max = count; 186 | if (offset >= 0) {// has indexes 187 | // from 188 | start = std::max(static_cast(from), offset) - offset; 189 | // to 190 | if(to >= 0) 191 | max = std::min(static_cast(to), max + offset) - offset; 192 | } 193 | 194 | // iterate over used items 195 | qint64 i; 196 | auto canceled = false; 197 | for (i = start; i < max; i++) { 198 | auto item = items[i].toVariant(); 199 | auto index = offset >= 0 ? i + offset : -1; 200 | auto res = iterator.call({ 201 | _engine->toScriptValue(item), 202 | static_cast(index) 203 | }); 204 | if (!res.toBool()) { 205 | canceled = true; 206 | break; 207 | } 208 | } 209 | 210 | if (canceled) 211 | return -1; 212 | else if (offset >= 0) 213 | return static_cast(offset + i); 214 | else 215 | return 0; 216 | }, _paging->items()); 217 | } 218 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlpaging.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_QMLPAGING_H 2 | #define QTRESTCLIENT_QMLPAGING_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef DOXYGEN_RUN 12 | namespace de::skycoder42::QtRestClient { 13 | 14 | /*! @brief The QML version of ::QtRestClient::Paging 15 | * 16 | * @since 2.0 17 | */ 18 | class Paging 19 | #else 20 | namespace QtRestClient { 21 | 22 | class QmlPaging 23 | #endif 24 | { 25 | Q_GADGET 26 | 27 | /*! Specifies, whether the paging object is valid and accessable 28 | * 29 | * @default{`false`} 30 | * @accessors{ 31 | * @memberAc{valid} 32 | * @readonlyAc 33 | * } 34 | */ 35 | Q_PROPERTY(bool valid READ isValid CONSTANT) 36 | /*! @copybrief ::QtRestClient::IPaging::items 37 | * 38 | * @default{empty} 39 | * @accessors{ 40 | * @memberAc{items} 41 | * @readonlyAc 42 | * } 43 | * @sa ::QtRestClient::IPaging::items 44 | */ 45 | Q_PROPERTY(QVariantList items READ items CONSTANT) 46 | /*! @copybrief ::QtRestClient::IPaging::total 47 | * 48 | * @default{`INT_MAX`} 49 | * @accessors{ 50 | * @memberAc{total} 51 | * @readonlyAc 52 | * } 53 | * @sa ::QtRestClient::IPaging::total 54 | */ 55 | Q_PROPERTY(int total READ total CONSTANT) 56 | /*! @copybrief ::QtRestClient::IPaging::offset 57 | * 58 | * @default{`-1`} 59 | * @accessors{ 60 | * @memberAc{offset} 61 | * @readonlyAc 62 | * } 63 | * @sa ::QtRestClient::IPaging::offset 64 | */ 65 | Q_PROPERTY(int offset READ offset CONSTANT) 66 | /*! @copybrief ::QtRestClient::IPaging::hasNext 67 | * 68 | * @default{`false`} 69 | * @accessors{ 70 | * @memberAc{hasNext} 71 | * @readonlyAc 72 | * } 73 | * @sa ::QtRestClient::IPaging::hasNext 74 | */ 75 | Q_PROPERTY(bool hasNext READ hasNext CONSTANT) 76 | /*! @copybrief ::QtRestClient::IPaging::next 77 | * 78 | * @default{invalid} 79 | * @accessors{ 80 | * @memberAc{nextUrl} 81 | * @readonlyAc 82 | * } 83 | * @sa ::QtRestClient::IPaging::next 84 | */ 85 | Q_PROPERTY(QUrl nextUrl READ nextUrl CONSTANT) 86 | /*! @copybrief ::QtRestClient::IPaging::hasPrevious 87 | * 88 | * @default{`false`} 89 | * @accessors{ 90 | * @memberAc{hasPrevious} 91 | * @readonlyAc 92 | * } 93 | * @sa ::QtRestClient::IPaging::hasPrevious 94 | */ 95 | Q_PROPERTY(bool hasPrevious READ hasPrevious CONSTANT) 96 | /*! @copybrief ::QtRestClient::IPaging::previous 97 | * 98 | * @default{invalid} 99 | * @accessors{ 100 | * @memberAc{previousUrl} 101 | * @readonlyAc 102 | * } 103 | * @sa ::QtRestClient::IPaging::previous 104 | */ 105 | Q_PROPERTY(QUrl previousUrl READ previousUrl CONSTANT) 106 | /*! @copybrief ::QtRestClient::IPaging::properties 107 | * 108 | * @default{empty} 109 | * @accessors{ 110 | * @memberAc{properties} 111 | * @readonlyAc 112 | * } 113 | * @sa ::QtRestClient::IPaging::properties 114 | */ 115 | Q_PROPERTY(QVariantMap properties READ properties CONSTANT) 116 | 117 | public: 118 | //! @private 119 | explicit QmlPaging() = default; 120 | //! @private 121 | explicit QmlPaging(IPaging *iPaging, RestClient *client, QJSEngine *engine); 122 | 123 | //! @private 124 | static QmlPaging create(RestClient *client, QJSEngine *engine, const RestReply::DataType &data); 125 | 126 | //! @private 127 | bool isValid() const; 128 | 129 | //! Performs a request for the next paging object 130 | Q_INVOKABLE QtRestClient::RestReply *next(); 131 | //! Performs a request for the previous paging object 132 | Q_INVOKABLE QtRestClient::RestReply *previous(); 133 | 134 | //! @private 135 | QVariantList items() const; 136 | //! @private 137 | int total() const; 138 | //! @private 139 | int offset() const; 140 | //! @private 141 | bool hasNext() const; 142 | //! @private 143 | QUrl nextUrl() const; 144 | //! @private 145 | bool hasPrevious() const; 146 | //! @private 147 | QUrl previousUrl() const; 148 | //! @private 149 | QVariantMap properties() const; 150 | 151 | public Q_SLOTS: 152 | /*! Iterates over all paging objects 153 | * 154 | * @param iterator The iterator to be be called for every element iterated over 155 | * @param to The upper limit of how far the iteration should go (-1 means no limit) 156 | * @param from The lower limit from where the iteration should start 157 | * 158 | * @sa ::QtRestClient::Paging::iterate 159 | */ 160 | void iterate(const QJSValue &iterator, 161 | int to = -1, int from = 0); 162 | /*! Iterates over all paging objects 163 | * 164 | * @param iterator The iterator to be be called for every element iterated over 165 | * @param failureHandler Will be passed to GenericRestReply::onFailed for all replies 166 | * @param errorHandler Will be passed to GenericRestReply::onError for all replies 167 | * @param to The upper limit of how far the iteration should go (-1 means no limit) 168 | * @param from The lower limit from where the iteration should start 169 | * 170 | * @sa ::QtRestClient::Paging::iterate 171 | */ 172 | void iterate(const QJSValue &iterator, 173 | const QJSValue &failureHandler, 174 | const QJSValue &errorHandler, 175 | int to = -1, int from = 0); 176 | 177 | private: 178 | QJSEngine *_engine = nullptr; 179 | RestClient *_client = nullptr; 180 | QSharedPointer _paging; 181 | 182 | int internalIterate(QJSValue iterator, int from, int to) const; 183 | }; 184 | 185 | } 186 | 187 | Q_DECLARE_METATYPE(QtRestClient::QmlPaging) 188 | 189 | #endif // QTRESTCLIENT_QMLPAGING_H 190 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlrestclient.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlrestclient.h" 2 | using namespace QtRestClient; 3 | 4 | QmlRestClient::QmlRestClient(QObject *parent) : 5 | RestClient{parent} 6 | {} 7 | 8 | QQmlListProperty QmlRestClient::classes() 9 | { 10 | return {this, &_childClasses}; 11 | } 12 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlrestclient.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_QMLRESTCLIENT_H 2 | #define QTRESTCLIENT_QMLRESTCLIENT_H 3 | 4 | #include 5 | #include 6 | 7 | #include "qmlrestclass.h" 8 | 9 | #ifdef DOXYGEN_RUN 10 | namespace de::skycoder42::QtRestClient { 11 | 12 | /*! @brief The QML version of ::QtRestClient::RestClient 13 | * 14 | * @since 2.0 15 | */ 16 | class RestClient : public ::QtRestClient::RestClient 17 | { 18 | Q_OBJECT 19 | 20 | /*! @brief A helper property to declare RestClasses for the client 21 | * 22 | * @default{empty} 23 | * @accessors{ 24 | * @memberAc{classes} 25 | * } 26 | * @sa de::skycoder42::QtRestClient::RestClass 27 | */ 28 | Q_PROPERTY(QQmlListProperty classes READ classes) 29 | #else 30 | namespace QtRestClient { 31 | 32 | class QmlRestClient : public RestClient 33 | { 34 | Q_OBJECT 35 | 36 | Q_PROPERTY(QQmlListProperty classes READ classes) 37 | #endif 38 | 39 | Q_CLASSINFO("DefaultProperty", "classes") 40 | 41 | public: 42 | //! Default constructor with object parent 43 | explicit QmlRestClient(QObject *parent = nullptr); 44 | 45 | //! @private 46 | QQmlListProperty classes(); 47 | 48 | private: 49 | QList _childClasses; 50 | }; 51 | 52 | } 53 | 54 | #endif // QTRESTCLIENT_QMLRESTCLIENT_H 55 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlrestclientglobal.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlrestclientglobal.h" 2 | using namespace QtRestClient; 3 | 4 | QmlRestClientGlobal::QmlRestClientGlobal(QJSEngine *engine, QObject *parent) : 5 | QObject(parent), 6 | _engine(engine) 7 | {} 8 | 9 | bool QmlRestClientGlobal::addGlobalApi(const QString &name, RestClient *client) 10 | { 11 | return QtRestClient::addGlobalApi(name, client); 12 | } 13 | 14 | void QmlRestClientGlobal::removeGlobalApi(const QString &name, bool deleteClient) 15 | { 16 | QtRestClient::removeGlobalApi(name, deleteClient); 17 | } 18 | 19 | RestClient *QmlRestClientGlobal::apiClient(const QString &name) 20 | { 21 | return QtRestClient::apiClient(name); 22 | } 23 | 24 | QmlRestClass *QmlRestClientGlobal::apiRootClass(const QString &name, QObject *parent) 25 | { 26 | auto api = QtRestClient::apiClient(name); 27 | if(!api) 28 | return nullptr; 29 | 30 | auto qClass = new QmlRestClass(api); 31 | qClass->classBegin(); 32 | qClass->componentComplete(); 33 | qClass->setParent(parent); 34 | return qClass; 35 | } 36 | 37 | QmlRestClass *QmlRestClientGlobal::createApiClass(const QString &name, const QString &path, QObject *parent) 38 | { 39 | auto api = QtRestClient::apiClient(name); 40 | if(!api) 41 | return nullptr; 42 | 43 | auto qClass = new QmlRestClass(api); 44 | qClass->classBegin(); 45 | qClass->setPath(path); 46 | qClass->componentComplete(); 47 | qClass->setParent(parent); 48 | return qClass; 49 | } 50 | 51 | QmlPaging QmlRestClientGlobal::createPaging(RestClient *client, const QVariantMap &data) 52 | { 53 | return QmlPaging::create(client, _engine, QJsonObject::fromVariantMap(data)); 54 | } 55 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlrestclientglobal.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_QMLRESTCLIENTGLOBAL_H 2 | #define QTRESTCLIENT_QMLRESTCLIENTGLOBAL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "qmlrestclass.h" 8 | #include "qmlpaging.h" 9 | 10 | #ifdef DOXYGEN_RUN 11 | namespace de::skycoder42::QtRestClient { 12 | 13 | /*! @brief A QML singleton to access global QtRestClient functionality and to create Paging objects 14 | * 15 | * @extends QtQml.QtObject 16 | * @since 2.0 17 | */ 18 | class QtRestClient 19 | #else 20 | namespace QtRestClient { 21 | 22 | class QmlRestClientGlobal : public QObject 23 | #endif 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | //! @private 29 | explicit QmlRestClientGlobal(QJSEngine *engine, QObject *parent = nullptr); 30 | 31 | //! Makes the given API available under the given name 32 | Q_INVOKABLE bool addGlobalApi(const QString &name, QtRestClient::RestClient *client); 33 | //! Removes a previously added API from the global list 34 | Q_INVOKABLE void removeGlobalApi(const QString &name, bool deleteClient = true); 35 | //! Returns the client for given API name 36 | Q_INVOKABLE QtRestClient::RestClient *apiClient(const QString &name); 37 | #ifdef DOXYGEN_RUN 38 | //! Returns the clients root class for the given API name 39 | Q_INVOKABLE RestClass *apiRootClass(const QString &name, QObject *parent = nullptr); 40 | #else 41 | Q_INVOKABLE QtRestClient::QmlRestClass *apiRootClass(const QString &name, QObject *parent = nullptr); 42 | #endif 43 | #ifdef DOXYGEN_RUN 44 | //! Creates a new API class based on the client for the given API name 45 | Q_INVOKABLE RestClass *createApiClass(const QString &name, const QString &path, QObject *parent = nullptr); 46 | #else 47 | Q_INVOKABLE QtRestClient::QmlRestClass *createApiClass(const QString &name, const QString &path, QObject *parent = nullptr); 48 | #endif 49 | 50 | #ifdef DOXYGEN_RUN 51 | //! Creates a new QML Paging object for the given client from the given object 52 | Q_INVOKABLE Paging createPaging(QtRestClient::RestClient *client, const QVariantMap &data); 53 | #else 54 | Q_INVOKABLE QtRestClient::QmlPaging createPaging(QtRestClient::RestClient *client, const QVariantMap &data); 55 | #endif 56 | 57 | private: 58 | QJSEngine *_engine; 59 | }; 60 | 61 | } 62 | 63 | #endif // QTRESTCLIENT_QMLRESTCLIENTGLOBAL_H 64 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlrestreply.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlrestreply.h" 2 | #include 3 | #include 4 | using namespace QtRestClient; 5 | 6 | QmlRestReply::QmlRestReply(RestReply *reply, QJSEngine *engine, QObject *parent) : 7 | QObject{parent}, 8 | _reply{reply}, 9 | _engine{engine} 10 | {} 11 | 12 | void QmlRestReply::addCompletedHandler(const QJSValue &completedHandler) 13 | { 14 | if (!checkOk(completedHandler)) 15 | return; 16 | 17 | _reply->onCompleted([completedHandler](int code){ 18 | auto fn = completedHandler; 19 | fn.call({code}); 20 | }); 21 | } 22 | 23 | void QmlRestReply::addSucceededHandler(const QJSValue &succeededHandler) 24 | { 25 | if (!checkOk(succeededHandler)) 26 | return; 27 | 28 | QPointer engine{_engine}; 29 | QPointer reply{_reply}; 30 | 31 | _reply->onSucceeded([this, succeededHandler, engine, reply](int code, const RestReply::DataType &data) { 32 | if (!engine) 33 | return; 34 | try { 35 | auto fn = succeededHandler; 36 | std::visit(__private::overload { 37 | [&](std::nullopt_t) { 38 | fn.call({code, QJSValue{}}); 39 | }, 40 | [&](const auto &vData) { 41 | fn.call({code, engine->toScriptValue(vData.toVariant())}); 42 | } 43 | }, data); 44 | } catch (std::exception &e) { 45 | qmlWarning(this) << "ERROR:" << e.what(); 46 | } 47 | }); 48 | } 49 | 50 | void QmlRestReply::addFailedHandler(const QJSValue &failedHandler) 51 | { 52 | if (!checkOk(failedHandler)) 53 | return; 54 | 55 | QPointer engine{_engine}; 56 | QPointer reply{_reply}; 57 | 58 | _reply->onFailed([this, failedHandler, engine, reply](int code, const RestReply::DataType &data) { 59 | if (!engine) 60 | return; 61 | try { 62 | auto fn = failedHandler; 63 | std::visit(__private::overload { 64 | [&](std::nullopt_t) { 65 | fn.call({code, QJSValue{}}); 66 | }, 67 | [&](const auto &vData) { 68 | fn.call({code, engine->toScriptValue(vData.toVariant())}); 69 | } 70 | }, data); 71 | } catch (std::exception &e) { 72 | qmlWarning(this) << "ERROR:" << e.what(); 73 | } 74 | }); 75 | } 76 | 77 | void QmlRestReply::addErrorHandler(const QJSValue &errorHandler) 78 | { 79 | if (!checkOk(errorHandler)) 80 | return; 81 | 82 | _reply->onError([errorHandler](const QString &error, int code, RestReply::Error type){ 83 | auto fn = errorHandler; 84 | fn.call({error, code, static_cast(type)}); 85 | }); 86 | } 87 | 88 | bool QmlRestReply::checkOk(const QJSValue &fn) const 89 | { 90 | if (!_reply) { 91 | qmlWarning(this) << "Cannot assign a handler to an invalid reply"; 92 | return false; 93 | } 94 | 95 | if (!fn.isCallable()) { 96 | qmlWarning(this) << "Passed JS object is not a callable function!"; 97 | return false; 98 | } 99 | 100 | return true; 101 | } 102 | -------------------------------------------------------------------------------- /src/imports/restclient/qmlrestreply.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_QMLRESTREPLY_H 2 | #define QTRESTCLIENT_QMLRESTREPLY_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #ifdef DOXYGEN_RUN 12 | namespace de::skycoder42::QtRestClient { 13 | 14 | /*! @brief The QML version of ::QtRestClient::RestReply 15 | * 16 | * @extends QtQml.QtObject 17 | * @since 3.0 18 | * 19 | * This is a special version that is returned by the RestClass to be able to set handlers. 20 | * The original reply can be accessed via the RestReply::reply property. 21 | */ 22 | class RestReply 23 | #else 24 | namespace QtRestClient { 25 | 26 | class QmlRestReply : public QObject 27 | #endif 28 | { 29 | Q_OBJECT 30 | 31 | /*! @brief The original ::QtRestClient::RestReply that this one wrapps 32 | * 33 | * @default{auto} 34 | * @accessors{ 35 | * @memberAc{reply} 36 | * @readonlyAc 37 | * } 38 | * @sa ::QtRestClient::RestReply 39 | */ 40 | Q_PROPERTY(QtRestClient::RestReply *reply MEMBER _reply CONSTANT) 41 | 42 | public: 43 | //! @private 44 | explicit QmlRestReply(RestReply *reply, 45 | QJSEngine *engine, 46 | QObject *parent = nullptr); 47 | 48 | public Q_SLOTS: 49 | /*! @brief Add a method to be called when the request has been completed 50 | * 51 | * @param completedHandler a JS callback to be called with the reply 52 | * 53 | * The handlers arguments are: 54 | * - The HTTP-Status code (int) 55 | */ 56 | void addCompletedHandler(const QJSValue &completedHandler); 57 | /*! @brief Add a method to be called when the request has been completed successfully 58 | * 59 | * @param succeededHandler a JS callback to be called with the reply 60 | * 61 | * The handlers arguments are: 62 | * - The HTTP-Status code (int) 63 | * - The deserialized Content of the reply (deserialized to a JS object) 64 | */ 65 | void addSucceededHandler(const QJSValue &succeededHandler); 66 | /*! @brief Add a method to be called if the request has failed 67 | * 68 | * @param failedHandler a JS callback to be called with the reply 69 | * 70 | * The handlers arguments are: 71 | * - The HTTP-Status code (int) 72 | * - The deserialized Content of the reply (deserialized to a JS object) 73 | */ 74 | void addFailedHandler(const QJSValue &failedHandler); 75 | /*! @brief Add a method to be called if an error occured 76 | * 77 | * @param errorHandler a JS callback to be called with the reply 78 | * 79 | * The handlers arguments are: 80 | * - The error string (string) 81 | * - The error code (int) 82 | * - The error type (::QtRestClient::RestReply::Error) 83 | */ 84 | void addErrorHandler(const QJSValue &errorHandler); 85 | 86 | private: 87 | QPointer _reply; 88 | QJSEngine *_engine; 89 | 90 | bool checkOk(const QJSValue &fn) const; 91 | }; 92 | 93 | } 94 | 95 | #endif // QMLRESTREPLY_H 96 | -------------------------------------------------------------------------------- /src/imports/restclient/qtrestclient_plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "qtrestclient_plugin.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "qmlrestreply.h" 9 | #include "qmlrestclass.h" 10 | #include "qmlrestclient.h" 11 | #include "qmlpaging.h" 12 | #include "qmlrestclientglobal.h" 13 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 14 | #include "qmlgenericrestreply.h" 15 | #endif 16 | 17 | namespace { 18 | 19 | QObject *create_qtrestclient(QQmlEngine *qmlEngine, QJSEngine *jsEngine) 20 | { 21 | return new QtRestClient::QmlRestClientGlobal(jsEngine, qmlEngine); 22 | } 23 | 24 | } 25 | 26 | QtRestClientDeclarativeModule::QtRestClientDeclarativeModule(QObject *parent) : 27 | QQmlExtensionPlugin(parent) 28 | {} 29 | 30 | void QtRestClientDeclarativeModule::registerTypes(const char *uri) 31 | { 32 | Q_ASSERT(qstrcmp(uri, "de.skycoder42.RestClient") == 0); 33 | 34 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 35 | qRegisterMetaType(); 36 | #endif 37 | qRegisterMetaType(); 38 | 39 | //Version 3.0 40 | qmlRegisterUncreatableType(uri, 3, 0, "RestReply", QStringLiteral("RestReplies can only be returned from restclass methods")); 41 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 42 | qmlRegisterUncreatableType(uri, 3, 0, "GenericRestReply", QStringLiteral("GenericRestReplies can only be returned from generated restclass methods")); 43 | #endif 44 | qmlRegisterUncreatableType(uri, 3, 0, "Paging", QStringLiteral("Pagings can only be returned from the QtRestClient singleton")); 45 | 46 | qmlRegisterType(uri, 3, 0, "RestClient"); 47 | qmlRegisterType(uri, 3, 0, "RestClass"); 48 | qmlRegisterType(uri, 3, 0, "PagingModel"); 49 | 50 | qmlRegisterSingletonType(uri, 3, 0, "QtRestClient", create_qtrestclient); 51 | 52 | // Check to make shure no module update is forgotten 53 | static_assert(VERSION_MAJOR == 3 && VERSION_MINOR == 0, "QML module version needs to be updated"); 54 | } 55 | -------------------------------------------------------------------------------- /src/imports/restclient/qtrestclient_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_PLUGIN_H 2 | #define QTRESTCLIENT_PLUGIN_H 3 | 4 | #include 5 | 6 | class QtRestClientDeclarativeModule : public QQmlExtensionPlugin 7 | { 8 | Q_OBJECT 9 | Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) 10 | 11 | public: 12 | QtRestClientDeclarativeModule(QObject *parent = nullptr); 13 | void registerTypes(const char *uri) override; 14 | }; 15 | 16 | #endif // QTRESTCLIENT_PLUGIN_H 17 | -------------------------------------------------------------------------------- /src/imports/restclient/restclient.pro: -------------------------------------------------------------------------------- 1 | QT += core qml quick restclient 2 | CXX_MODULE = restclient 3 | TARGETPATH = de/skycoder42/RestClient 4 | TARGET = declarative_restclient 5 | IMPORT_VERSION = $$MODULE_VERSION_IMPORT 6 | DEFINES += "VERSION_MAJOR=$$MODULE_VERSION_MAJOR" 7 | DEFINES += "VERSION_MINOR=$$MODULE_VERSION_MINOR" 8 | 9 | HEADERS += \ 10 | qmlrestreply.h \ 11 | qtrestclient_plugin.h \ 12 | qmlrestclass.h \ 13 | qmlrestclient.h \ 14 | qmlrestclientglobal.h \ 15 | qmlpaging.h 16 | 17 | SOURCES += \ 18 | qmlrestreply.cpp \ 19 | qtrestclient_plugin.cpp \ 20 | qmlrestclass.cpp \ 21 | qmlrestclient.cpp \ 22 | qmlrestclientglobal.cpp \ 23 | qmlpaging.cpp 24 | 25 | !no_json_serializer { 26 | HEADERS += \ 27 | qmlgenericrestreply.h 28 | 29 | SOURCES += \ 30 | qmlgenericrestreply.cpp 31 | } 32 | 33 | OTHER_FILES += qmldir 34 | 35 | CONFIG += qmlcache 36 | load(qml_plugin) 37 | 38 | generate_qmltypes { 39 | # run again to overwrite module env 40 | ldpath.name = LD_LIBRARY_PATH 41 | ldpath.value = "$$shadowed($$dirname(_QMAKE_CONF_))/lib/:$$[QT_INSTALL_LIBS]:$$(LD_LIBRARY_PATH)" 42 | qmlpath.name = QML2_IMPORT_PATH 43 | qmlpath.value = "$$shadowed($$dirname(_QMAKE_CONF_))/qml/:$$[QT_INSTALL_QML]:$$(QML2_IMPORT_PATH)" 44 | PLGDUMP_ENV = ldpath qmlpath 45 | QT_TOOL_ENV = ldpath qmlpath 46 | qtPrepareTool(QMLPLUGINDUMP, qmlplugindump) 47 | QT_TOOL_ENV = 48 | 49 | #overwrite the target deps as make target is otherwise not detected 50 | qmltypes.depends = ../../../qml/$$TARGETPATH/$(TARGET) 51 | OLDDMP = $$take_first(qmltypes.commands) 52 | qmltypes.commands = $$QMLPLUGINDUMP $${qmltypes.commands} 53 | message("replaced $$OLDDMP with $$QMLPLUGINDUMP") 54 | 55 | mfirst.target = all 56 | mfirst.depends += qmltypes 57 | QMAKE_EXTRA_TARGETS += mfirst 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/restclient/ipaging.cpp: -------------------------------------------------------------------------------- 1 | #include "ipaging.h" 2 | #include 3 | using namespace QtRestClient; 4 | 5 | IPaging::IPaging() = default; 6 | 7 | IPaging::~IPaging() = default; 8 | 9 | qint64 IPaging::total() const 10 | { 11 | return std::numeric_limits::max(); 12 | } 13 | 14 | qint64 IPaging::offset() const 15 | { 16 | return -1; 17 | } 18 | 19 | bool IPaging::hasPrevious() const 20 | { 21 | return false; 22 | } 23 | 24 | QUrl IPaging::previous() const 25 | { 26 | return QUrl(); 27 | } 28 | 29 | IPagingFactory::IPagingFactory() = default; 30 | 31 | IPagingFactory::~IPagingFactory() = default; 32 | 33 | std::variant ICborPaging::items() const 34 | { 35 | return cborItems(); 36 | } 37 | 38 | std::variant ICborPaging::originalData() const 39 | { 40 | return originalCbor(); 41 | } 42 | 43 | std::variant IJsonPaging::items() const 44 | { 45 | return jsonItems(); 46 | } 47 | 48 | std::variant IJsonPaging::originalData() const 49 | { 50 | return originalJson(); 51 | } 52 | -------------------------------------------------------------------------------- /src/restclient/ipaging.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_IPAGING_H 2 | #define QTRESTCLIENT_IPAGING_H 3 | 4 | #include "QtRestClient/qtrestclient_global.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 14 | namespace QtJsonSerializer { 15 | class SerializerBase; 16 | } 17 | #endif 18 | 19 | namespace QtRestClient { 20 | 21 | //! Interface to parse generic paging objects and operate on them 22 | class Q_RESTCLIENT_EXPORT IPaging 23 | { 24 | Q_DISABLE_COPY(IPaging) 25 | public: 26 | IPaging(); 27 | virtual ~IPaging(); 28 | 29 | //! Returns the items of this paging object, i.e. it's data 30 | virtual std::variant items() const = 0; 31 | 32 | //! Returns the total number of objects there are 33 | virtual qint64 total() const; 34 | //! Returns the offset this paging begins at 35 | virtual qint64 offset() const; 36 | //! Returns true, if there is a next paging object 37 | virtual bool hasNext() const = 0; 38 | //! Returns the link to the next paging object 39 | virtual QUrl next() const = 0; 40 | //! Returns true, if there is a previous paging object 41 | virtual bool hasPrevious() const; 42 | //! Returns the link to the previous paging object 43 | virtual QUrl previous() const; 44 | //! Returns a hash containing all properties of the original JSON 45 | virtual QVariantMap properties() const = 0; 46 | //! Returns the original JSON element parsed 47 | virtual std::variant originalData() const = 0; 48 | }; 49 | 50 | //! Interface to parse generic CBOR paging objects and operate on them 51 | class Q_RESTCLIENT_EXPORT ICborPaging : public IPaging 52 | { 53 | public: 54 | //! @copybrief IPaging::items 55 | virtual QCborArray cborItems() const = 0; 56 | //! @copybrief IPaging::originalData 57 | virtual QCborValue originalCbor() const = 0; 58 | 59 | std::variant items() const final; 60 | std::variant originalData() const final; 61 | }; 62 | 63 | //! Interface to parse generic JSON paging objects and operate on them 64 | class Q_RESTCLIENT_EXPORT IJsonPaging : public IPaging 65 | { 66 | public: 67 | //! @copybrief IPaging::items 68 | virtual QJsonArray jsonItems() const = 0; 69 | //! @copybrief IPaging::originalData 70 | virtual QJsonValue originalJson() const = 0; 71 | 72 | std::variant items() const final; 73 | std::variant originalData() const final; 74 | }; 75 | 76 | //! A factory interface to create IPaging instances from raw data 77 | class Q_RESTCLIENT_EXPORT IPagingFactory 78 | { 79 | Q_DISABLE_COPY(IPagingFactory) 80 | 81 | public: 82 | IPagingFactory(); 83 | virtual ~IPagingFactory(); 84 | 85 | //! Creates a new paging object of the given data 86 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 87 | virtual IPaging *createPaging(QtJsonSerializer::SerializerBase *serializer, const std::variant &data) const = 0; 88 | #else 89 | virtual IPaging *createPaging(const std::variant &data) const = 0; 90 | #endif 91 | }; 92 | 93 | } 94 | 95 | Q_DECLARE_METATYPE(QtRestClient::IPaging*) 96 | 97 | #endif // QTRESTCLIENT_IPAGING_H 98 | -------------------------------------------------------------------------------- /src/restclient/metacomponent.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_METACOMPONENT_H 2 | #define QTRESTCLIENT_METACOMPONENT_H 3 | 4 | #include "QtRestClient/qtrestclient_global.h" 5 | #include "QtRestClient/qtrestclient_helpertypes.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace QtRestClient::__private { 12 | 13 | template 14 | using EnableGadgetType = typename std::enable_if::value>::type; 15 | 16 | template 17 | using EnableObjectType = typename std::enable_if::value>::type; 18 | 19 | template 20 | class MetaComponent : public std::false_type 21 | { 22 | public: 23 | static inline void deleteLater(T) {} 24 | static inline void deleteAllLater(const QList &) {} 25 | }; 26 | 27 | template 28 | class MetaComponent> : public std::true_type 29 | { 30 | public: 31 | static inline void deleteLater(T) {} 32 | static inline void deleteAllLater(const QList &) {} 33 | }; 34 | 35 | template 36 | class MetaComponent> : public std::true_type 37 | { 38 | public: 39 | static inline void deleteLater(T *obj) { 40 | if (obj) 41 | obj->deleteLater(); 42 | } 43 | static inline void deleteAllLater(const QList &list) { 44 | for (T *obj : list) { 45 | if (obj) 46 | obj->deleteLater(); 47 | } 48 | } 49 | }; 50 | 51 | template 52 | class MetaComponent> : public std::true_type 53 | { 54 | public: 55 | static inline void deleteLater(T *gad) { 56 | delete gad; 57 | } 58 | static inline void deleteAllLater(const QList &list) { 59 | qDeleteAll(list); 60 | } 61 | }; 62 | 63 | } 64 | 65 | #endif // QTRESTCLIENT_METACOMPONENT_H 66 | -------------------------------------------------------------------------------- /src/restclient/paging_fwd.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_PAGING_FWD_H 2 | #define QTRESTCLIENT_PAGING_FWD_H 3 | 4 | #include "QtRestClient/qtrestclient_global.h" 5 | 6 | #include "QtRestClient/ipaging.h" 7 | #include "QtRestClient/restclient.h" 8 | #include "QtRestClient/restreply.h" 9 | #include "QtRestClient/metacomponent.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | namespace QtRestClient { 19 | 20 | template 21 | class GenericRestReply; 22 | 23 | namespace __private { 24 | template 25 | class PagingData; 26 | } 27 | 28 | //! A class to access generic paging objects 29 | template 30 | class Paging 31 | { 32 | public: 33 | //! Default Constructor 34 | Paging(); 35 | //! Copy Constructor 36 | Paging(const Paging &other); 37 | //! Move Constructor 38 | Paging(Paging &&other) noexcept; 39 | //! Copy assignment operator 40 | Paging &operator=(const Paging &other); 41 | //! Move assignment operator 42 | Paging &operator=(Paging &&other) noexcept; 43 | //! Constructs a paging from the interface, the data and a client 44 | Paging(IPaging *iPaging, const QList &data, RestClient *client); 45 | 46 | //! Returns true, if the current paging object is a valid one 47 | bool isValid() const; 48 | //! Returns the internally used IPaging instance 49 | IPaging *iPaging() const; 50 | 51 | //! @copybrief IPaging::items 52 | QList items() const; 53 | 54 | //! @copybrief IPaging::total 55 | qint64 total() const; 56 | //! @copybrief IPaging::offset 57 | qint64 offset() const; 58 | 59 | //! @copybrief IPaging::hasNext 60 | bool hasNext() const; 61 | //! Performs a request for the next paging object 62 | template 63 | GenericRestReply, EO> *next() const; 64 | //! @copybrief IPaging::next 65 | QUrl nextUrl() const; 66 | 67 | //! @copybrief IPaging::hasPrevious 68 | bool hasPrevious() const; 69 | //! Performs a request for the previous paging object 70 | template 71 | GenericRestReply, EO> *previous() const; 72 | //! @copybrief IPaging::previous 73 | QUrl previousUrl() const; 74 | 75 | //! Iterates over all paging objects 76 | void iterate(const std::function &iterator, 77 | qint64 to = -1, 78 | qint64 from = 0) const; 79 | //! @copybrief Paging::iterate(const std::function &, qint64, qint64) const 80 | void iterate(QObject *scope, 81 | const std::function &iterator, 82 | qint64 to = -1, 83 | qint64 from = 0) const; 84 | //! Iterates over all paging objects, with error handling 85 | template 86 | void iterate(const std::function &iterator, 87 | const std::function &errorHandler, 88 | const std::function &failureTransformer = {}, 89 | qint64 to = -1, 90 | qint64 from = 0) const; 91 | //! @copybrief Paging::iterate(const std::function &, const std::function &, const std::function &, qint64, qint64) const 92 | template 93 | void iterate(QObject *scope, 94 | const std::function &iterator, 95 | const std::function &errorHandler, 96 | const std::function &failureTransformer = {}, 97 | qint64 to = -1, 98 | qint64 from = 0) const; 99 | //! @copybrief Paging::iterate(const std::function &, const std::function &, const std::function &, qint64, qint64) const 100 | template 101 | void iterate(const std::function &iterator, 102 | const std::function &failureHandler, 103 | const std::function &errorHandler = {}, 104 | const std::function &exceptionHandler = {}, 105 | qint64 to = -1, 106 | qint64 from = 0) const; 107 | //! @copybrief Paging::iterate(const std::function &, const std::function &, const std::function &, const std::function &, qint64, qint64) const 108 | template 109 | void iterate(QObject *scope, 110 | const std::function &iterator, 111 | const std::function &failureHandler, 112 | const std::function &errorHandler = {}, 113 | const std::function &exceptionHandler = {}, 114 | qint64 to = -1, 115 | qint64 from = 0) const; 116 | 117 | //! @copybrief IPaging::properties 118 | QVariantMap properties() const; 119 | 120 | //! Deletes all items this paging object is holding (QObjects only) 121 | void deleteAllItems() const; 122 | 123 | private: 124 | QSharedDataPointer<__private::PagingData> d; 125 | 126 | qint64 internalIterate(const std::function &iterator, qint64 to, qint64 from) const; 127 | qint64 calcMax(qint64 to) const; 128 | }; 129 | 130 | Q_RESTCLIENT_EXPORT Q_DECLARE_LOGGING_CATEGORY(logPaging) 131 | 132 | } 133 | 134 | #endif // QTRESTCLIENT_PAGING_FWD_H 135 | -------------------------------------------------------------------------------- /src/restclient/pagingmodel_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_PAGINGMODEL_P_H 2 | #define QTRESTCLIENT_PAGINGMODEL_P_H 3 | 4 | #include "pagingmodel.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace QtRestClient { 14 | 15 | class Q_RESTCLIENT_EXPORT PagingModelPrivate : public QAbstractItemModelPrivate 16 | { 17 | Q_DECLARE_PUBLIC(PagingModel) 18 | public: 19 | int typeId = QMetaType::UnknownType; 20 | QScopedPointer fetcher {}; 21 | std::optional nextUrl; 22 | QVariantList data; 23 | 24 | QHash pagingRoleNames; 25 | QStringList columns; 26 | QHash> roleMapping; //column -> (role -> property) 27 | 28 | void clearData(); 29 | void generateRoleNames(); 30 | void requestNext(); 31 | void processReply(int code, const RestReply::DataType &data); 32 | void processPaging(IPaging *paging); 33 | void processError(const QString &message, int code, RestReply::Error errorType); 34 | }; 35 | 36 | Q_DECLARE_LOGGING_CATEGORY(logPagingModel) 37 | 38 | } 39 | 40 | #endif // QTRESTCLIENT_PAGINGMODEL_P_H 41 | -------------------------------------------------------------------------------- /src/restclient/qtrestclient_global.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_GLOBAL_H 2 | #define QTRESTCLIENT_GLOBAL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifndef QT_STATIC 12 | # if defined(QT_BUILD_RESTCLIENT_LIB) 13 | # define Q_RESTCLIENT_EXPORT Q_DECL_EXPORT 14 | # else 15 | # define Q_RESTCLIENT_EXPORT Q_DECL_IMPORT 16 | # endif 17 | #else 18 | # define Q_RESTCLIENT_EXPORT 19 | #endif 20 | 21 | #if QT_CONFIG(thread) && QT_CONFIG(future) 22 | #define QT_RESTCLIENT_USE_ASYNC 23 | #endif 24 | 25 | //! The Namespace containing all classes of the QtRestClient module 26 | namespace QtRestClient { 27 | 28 | class RestClient; 29 | class RestClass; 30 | 31 | //! A typedef for a collection of HTTP request headers 32 | using HeaderHash = QHash; 33 | 34 | //! Makes the given API available under the given name 35 | Q_RESTCLIENT_EXPORT bool addGlobalApi(const QString &name, RestClient *client); 36 | //! Removes a previously added API from the global list 37 | Q_RESTCLIENT_EXPORT void removeGlobalApi(const QString &name, bool deleteClient = true); 38 | //! Returns the client for given API name 39 | Q_RESTCLIENT_EXPORT RestClient *apiClient(const QString &name); 40 | //! Returns the clients root class for the given API name 41 | Q_RESTCLIENT_EXPORT RestClass *apiRootClass(const QString &name); 42 | //! Creates a new API class based on the client for the given API name 43 | Q_RESTCLIENT_EXPORT RestClass *createApiClass(const QString &name, const QString &path, QObject *parent = nullptr); 44 | 45 | Q_RESTCLIENT_EXPORT Q_DECLARE_LOGGING_CATEGORY(logGlobal) 46 | 47 | } 48 | 49 | #endif // QTRESTCLIENT_GLOBAL_H 50 | -------------------------------------------------------------------------------- /src/restclient/requestbuilder.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_REQUESTBUILDER_H 2 | #define QTRESTCLIENT_REQUESTBUILDER_H 3 | 4 | #include "QtRestClient/qtrestclient_global.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #ifdef QT_RESTCLIENT_USE_ASYNC 14 | #include 15 | #endif 16 | 17 | #include 18 | #include 19 | 20 | namespace QtRestClient { 21 | 22 | struct RequestBuilderPrivate; 23 | //! A helper class to build QUrl and QNetworkRequest objects 24 | class Q_RESTCLIENT_EXPORT RequestBuilder 25 | { 26 | public: 27 | //! A simple interface to add custom extensions to the building process 28 | class Q_RESTCLIENT_EXPORT IExtender 29 | { 30 | Q_DISABLE_COPY(IExtender) 31 | public: 32 | IExtender(); 33 | virtual ~IExtender(); 34 | 35 | //! Perform additional operations on the URL 36 | virtual void extendUrl(QUrl &url) const; 37 | //! Specifies, whether extendRequest() always requires the `body` parameter 38 | virtual bool requiresBody() const; 39 | //! Perform additional operations on the request 40 | virtual void extendRequest(QNetworkRequest &request, QByteArray &verb, QByteArray *body) const; 41 | }; 42 | 43 | //! Constructs a builder with the given base url 44 | RequestBuilder(const QUrl &baseUrl, QNetworkAccessManager *nam = nullptr); 45 | //! Copy constructor 46 | RequestBuilder(const RequestBuilder &other); 47 | //! Move constructor 48 | RequestBuilder(RequestBuilder &&other) noexcept; 49 | //! Copy assignment operator 50 | RequestBuilder &operator=(const RequestBuilder &other); 51 | //! Move assignment operator 52 | RequestBuilder &operator=(RequestBuilder &&other) noexcept; 53 | ~RequestBuilder(); 54 | 55 | //! Sets the network access manager to be used for send() 56 | RequestBuilder &setNetworkAccessManager(QNetworkAccessManager *nam); 57 | //! Sets the extender to use for extending the build 58 | RequestBuilder &setExtender(IExtender *extender); 59 | 60 | //! Sets the credentails of the URL 61 | RequestBuilder &setCredentials(QString user, QString password = {}); 62 | //! Sets the version of the API 63 | RequestBuilder &setVersion(QVersionNumber version); 64 | //! appends a path segment to the builders path 65 | RequestBuilder &addPath(const QString &pathSegment); 66 | //! @copydoc RequestBuilder::addPath(const QString &) 67 | RequestBuilder &addPath(const QStringList &pathSegment); 68 | //! Enables the trailing slash for the generated URL 69 | RequestBuilder &trailingSlash(bool enable = true); 70 | //! Adds a parameter to the URL 71 | RequestBuilder &addParameter(const QString &name, const QString &value); 72 | //! Adds parameters to the URL 73 | RequestBuilder &addParameters(const QUrlQuery ¶meters); 74 | //! Sets the fragment part of the URL 75 | RequestBuilder &setFragment(QString fragment); 76 | //! Adds a HTTP header to be added to the network request 77 | RequestBuilder &addHeader(const QByteArray &name, const QByteArray &value); 78 | //! Adds HTTP headers to be added to the network request 79 | RequestBuilder &addHeaders(const HeaderHash &headers); 80 | 81 | //! Updates the builder from the (relative) URL and includes all of it's elements 82 | RequestBuilder &updateFromRelativeUrl(const QUrl &url, bool mergeQuery = false, bool keepFragment = false); 83 | 84 | //! Sets the given attribute on the generated network request 85 | RequestBuilder &setAttribute(QNetworkRequest::Attribute attribute, const QVariant &value); 86 | //! Sets the given attributes on the generated network request 87 | RequestBuilder &setAttributes(const QHash &attributes); 88 | #ifndef QT_NO_SSL 89 | //! Sets the ssl configuration to be used by the network request 90 | RequestBuilder &setSslConfig(QSslConfiguration sslConfig); 91 | #endif 92 | 93 | //! Sets the content of the generated network request 94 | RequestBuilder &setBody(QByteArray body, const QByteArray &contentType, bool setAccept = true); 95 | //! @copybrief RequestBuilder::setBody(QByteArray, const QByteArray &, bool) 96 | RequestBuilder &setBody(QCborValue body, bool setAccept = true); 97 | //! @copybrief RequestBuilder::setBody(QByteArray, const QByteArray &, bool) 98 | RequestBuilder &setBody(const QJsonValue &body, bool setAccept = true); 99 | //! Sets the HTTP-Verb to be used by the generated network request 100 | RequestBuilder &setVerb(QByteArray verb); 101 | //! Sets the "Accept" HTTP-header to the given mimetype 102 | RequestBuilder &setAccept(const QByteArray &mimeType); 103 | //! @copydoc setAccept(const QByteArray &) 104 | RequestBuilder &setAccept(const QMimeType &mimeType); 105 | 106 | //! Adds a post parameter to the body 107 | RequestBuilder &addPostParameter(const QString &name, const QString &value); 108 | //! Adds post parameters to the body 109 | RequestBuilder &addPostParameters(const QUrlQuery ¶meters); 110 | 111 | //! Creates a URL from the builder settings 112 | QUrl buildUrl() const; 113 | //! Creates a network request from the builder settings 114 | QNetworkRequest build() const; 115 | //! Creates a network request and sends it with the builder settings 116 | QNetworkReply *send() const; 117 | #ifdef QT_RESTCLIENT_USE_ASYNC 118 | //! Asynchronously creates a network request and sends it with the builder settings 119 | QFuture sendAsync() const; 120 | #endif 121 | 122 | private: 123 | QSharedDataPointer d; 124 | }; 125 | 126 | } 127 | 128 | #endif // QTRESTCLIENT_REQUESTBUILDER_H 129 | -------------------------------------------------------------------------------- /src/restclient/requestbuilder_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_REQUESTBUILDER_P_H 2 | #define QTRESTCLIENT_REQUESTBUILDER_P_H 3 | 4 | #include "requestbuilder.h" 5 | #include "restclass.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace QtRestClient { 12 | 13 | struct Q_RESTCLIENT_EXPORT RequestBuilderPrivate : public QSharedData 14 | { 15 | static const QByteArray ContentType; 16 | static const QByteArray ContentTypeCbor; 17 | static const QByteArray ContentTypeJson; 18 | static const QByteArray ContentTypeUrlEncoded; 19 | static const QByteArray Accept; 20 | 21 | RequestBuilderPrivate(const QUrl &baseUrl, QNetworkAccessManager *nam); 22 | RequestBuilderPrivate(const RequestBuilderPrivate &other) = default; 23 | 24 | QPointer nam; 25 | QSharedPointer extender; 26 | 27 | QUrl base; 28 | QVersionNumber version; 29 | QString user; 30 | QString pass; 31 | QStringList path; 32 | bool trailingSlash = false; 33 | QUrlQuery query; 34 | QString fragment; 35 | HeaderHash headers; 36 | QHash attributes; 37 | #ifndef QT_NO_SSL 38 | QSslConfiguration sslConfig; 39 | #endif 40 | QByteArray body; 41 | QByteArray verb; 42 | QUrlQuery postQuery; 43 | 44 | void prepareRequest(QNetworkRequest &request, QByteArray *sBody) const; 45 | }; 46 | 47 | Q_DECLARE_LOGGING_CATEGORY(logBuilder) 48 | 49 | } 50 | 51 | #endif // QTRESTCLIENT_REQUESTBUILDER_P_H 52 | -------------------------------------------------------------------------------- /src/restclient/restclass_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_RESTCLASS_P_H 2 | #define QTRESTCLIENT_RESTCLASS_P_H 3 | 4 | #include "restclass.h" 5 | 6 | #include 7 | 8 | namespace QtRestClient { 9 | 10 | class Q_RESTCLIENT_EXPORT RestClassPrivate : public QObjectPrivate 11 | { 12 | Q_DECLARE_PUBLIC(RestClass) 13 | public: 14 | RestClient *client; 15 | QStringList subPath; 16 | 17 | static QUrlQuery hashToQuery(const QVariantHash &hash); 18 | }; 19 | 20 | } 21 | 22 | #endif // QTRESTCLIENT_RESTCLASS_P_H 23 | -------------------------------------------------------------------------------- /src/restclient/restclient.pro: -------------------------------------------------------------------------------- 1 | TARGET = QtRestClient 2 | 3 | QT = core network core-private 4 | MODULE_CONFIG += qrestbuilder 5 | 6 | !no_json_serializer { 7 | !qtHaveModule(jsonserializer): warning("Unable to find QtJsonSerializer module. To build without it, add \"CONFIG+=no_json_serializer\" to your qmake command line") 8 | QT += jsonserializer 9 | } else { 10 | MODULE_DEFINES += Q_RESTCLIENT_NO_JSON_SERIALIZER 11 | DEFINES += Q_RESTCLIENT_NO_JSON_SERIALIZER 12 | } 13 | 14 | HEADERS += \ 15 | pagingmodel.h \ 16 | pagingmodel_p.h \ 17 | qtrestclient_helpertypes.h \ 18 | requestbuilder_p.h \ 19 | restclass_p.h \ 20 | restclient_p.h \ 21 | restreply_p.h \ 22 | qtrestclient_global.h \ 23 | ipaging.h \ 24 | requestbuilder.h \ 25 | restclass.h \ 26 | restclient.h \ 27 | restreply.h \ 28 | standardpaging_p.h \ 29 | restreplyawaitable.h \ 30 | restreplyawaitable_p.h 31 | 32 | !no_json_serializer { 33 | HEADERS += \ 34 | metacomponent.h \ 35 | paging_fwd.h \ 36 | paging.h \ 37 | genericrestreply.h \ 38 | simple.h 39 | } 40 | 41 | SOURCES += \ 42 | pagingmodel.cpp \ 43 | requestbuilder.cpp \ 44 | restclass.cpp \ 45 | restclient.cpp \ 46 | restreply.cpp \ 47 | standardpaging.cpp \ 48 | ipaging.cpp \ 49 | restreplyawaitable.cpp 50 | 51 | load(qt_module) 52 | 53 | FEATURES += ../../mkspecs/features/qrestbuilder.prf 54 | features.files = $$FEATURES 55 | features.path = $$[QT_HOST_DATA]/mkspecs/features/ 56 | INSTALLS += features 57 | 58 | win32 { 59 | QMAKE_TARGET_PRODUCT = "$$TARGET" 60 | QMAKE_TARGET_COMPANY = "Skycoder42" 61 | QMAKE_TARGET_COPYRIGHT = "Felix Barz" 62 | } else:mac { 63 | QMAKE_TARGET_BUNDLE_PREFIX = "de.skycoder42." 64 | } 65 | -------------------------------------------------------------------------------- /src/restclient/restclient_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_QRESTCLIENT_P_H 2 | #define QTRESTCLIENT_QRESTCLIENT_P_H 3 | 4 | #include "restclient.h" 5 | #include "standardpaging_p.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 12 | #include 13 | #endif 14 | 15 | #include 16 | 17 | namespace QtRestClient { 18 | 19 | class Q_RESTCLIENT_EXPORT RestClientPrivate : public QObjectPrivate 20 | { 21 | Q_DECLARE_PUBLIC(RestClient) 22 | public: 23 | using DataMode = RestClient::DataMode; 24 | 25 | static QReadWriteLock globalApiLock; 26 | static QHash globalApis; 27 | 28 | QUrl baseUrl; 29 | QVersionNumber apiVersion; 30 | HeaderHash headers; 31 | QUrlQuery query; 32 | QHash attribs; 33 | QAtomicPointer threadLock {nullptr}; 34 | #ifndef QT_NO_SSL 35 | QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); 36 | #endif 37 | #ifdef QT_RESTCLIENT_USE_ASYNC 38 | QPointer asyncPool; 39 | #endif 40 | 41 | QNetworkAccessManager *nam = nullptr; 42 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 43 | QtJsonSerializer::SerializerBase *serializer = nullptr; 44 | #else 45 | DataMode dataMode = DataMode::Json; 46 | #endif 47 | 48 | QScopedPointer pagingFactory {}; 49 | 50 | RestClass *rootClass = nullptr; 51 | 52 | ~RestClientPrivate() override; 53 | }; 54 | 55 | } 56 | 57 | #endif // QTRESTCLIENT_QRESTCLIENT_P_H 58 | -------------------------------------------------------------------------------- /src/restclient/restreply_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_RESTREPLY_P_H 2 | #define QTRESTCLIENT_RESTREPLY_P_H 3 | 4 | #include "restreply.h" 5 | 6 | #include 7 | #include 8 | #ifdef QT_RESTCLIENT_USE_ASYNC 9 | #include 10 | #endif 11 | 12 | #include 13 | 14 | namespace QtRestClient { 15 | 16 | class Q_RESTCLIENT_EXPORT AsyncHelper : public QObject 17 | { 18 | Q_OBJECT 19 | public: 20 | AsyncHelper(std::function &&fn); 21 | 22 | public Q_SLOTS: 23 | void exec(); 24 | 25 | private: 26 | std::function _fn; 27 | }; 28 | 29 | class Q_RESTCLIENT_EXPORT RestReplyPrivate : public QObjectPrivate, public QRunnable 30 | { 31 | public: 32 | Q_DECLARE_PUBLIC(RestReply) 33 | 34 | using DataType = RestReply::DataType; 35 | using Error = RestReply::Error; 36 | 37 | static const QByteArray PropertyBuffer; 38 | 39 | static QNetworkReply *compatSend(QNetworkAccessManager *nam, 40 | const QNetworkRequest &request, 41 | const QByteArray &verb, 42 | const QByteArray &body); 43 | #ifdef QT_RESTCLIENT_USE_ASYNC 44 | static void compatSendAsync(QFutureInterface futureIf, 45 | QNetworkAccessManager *nam, 46 | const QNetworkRequest &request, 47 | const QByteArray &verb, 48 | const QByteArray &body); 49 | #endif 50 | 51 | QPointer networkReply; 52 | bool autoDelete = true; 53 | bool allowEmptyReplies = false; 54 | #ifdef QT_RESTCLIENT_USE_ASYNC 55 | QFutureWatcher *watcher = nullptr; 56 | QThreadPool *asyncPool = nullptr; 57 | #endif 58 | std::chrono::milliseconds retryDelay {-1}; 59 | 60 | RestReplyPrivate(); 61 | 62 | void connectReply(); 63 | 64 | void _q_replyFinished(); 65 | void _q_retryReply(); 66 | #ifndef QT_NO_SSL 67 | void _q_handleSslErrors(const QList &errors); 68 | #endif 69 | 70 | void run() override; 71 | }; 72 | 73 | Q_DECLARE_LOGGING_CATEGORY(logReply) 74 | 75 | } 76 | 77 | #endif // QTRESTCLIENT_RESTREPLY_P_H 78 | -------------------------------------------------------------------------------- /src/restclient/restreplyawaitable.cpp: -------------------------------------------------------------------------------- 1 | #include "restreplyawaitable.h" 2 | #include "restreplyawaitable_p.h" 3 | 4 | #include 5 | using namespace QtRestClient; 6 | 7 | RestReplyAwaitable::RestReplyAwaitable(RestReply *reply) : 8 | d{new RestReplyAwaitablePrivate{}} 9 | { 10 | d->reply = reply; 11 | } 12 | 13 | RestReplyAwaitable::RestReplyAwaitable(RestReplyAwaitable &&other) noexcept 14 | { 15 | d.swap(other.d); 16 | } 17 | 18 | RestReplyAwaitable &RestReplyAwaitable::operator=(RestReplyAwaitable &&other) noexcept 19 | { 20 | d.swap(other.d); 21 | return *this; 22 | } 23 | 24 | RestReplyAwaitable::~RestReplyAwaitable() = default; 25 | 26 | void RestReplyAwaitable::prepare(const std::function &resume) 27 | { 28 | d->reply->onSucceeded(d->reply, [this, resume](RestReply::DataType data){ 29 | d->errorResult.reset(); 30 | d->successResult = std::move(data); 31 | resume(); 32 | }); 33 | d->reply->onFailed(d->reply, [this, resume](int code, const RestReply::DataType &data){ 34 | d->errorResult.reset(new AwaitedException { 35 | code, 36 | RestReply::Error::Failure, 37 | std::visit(__private::overload { 38 | [](std::nullopt_t) { 39 | return QVariant{}; 40 | }, 41 | [](auto vData) { 42 | return vData.toVariant(); 43 | } 44 | }, data)}); 45 | resume(); 46 | }); 47 | d->reply->onError([this, resume](const QString &errorString, int code, RestReply::Error type) { 48 | d->errorResult.reset(new AwaitedException{code, type, errorString}); 49 | resume(); 50 | }); 51 | } 52 | 53 | RestReplyAwaitable::type RestReplyAwaitable::result() 54 | { 55 | if(d->errorResult) { 56 | d->errorResult->raise(); 57 | Q_UNREACHABLE(); 58 | } else 59 | return d->successResult; 60 | } 61 | 62 | 63 | 64 | AwaitedException::AwaitedException(int code, RestReply::Error type, QVariant data) : 65 | _code{code}, 66 | _type{type}, 67 | _data{std::move(data)} 68 | {} 69 | 70 | int AwaitedException::errorCode() const 71 | { 72 | return _code; 73 | } 74 | 75 | RestReply::Error AwaitedException::errorType() const 76 | { 77 | return _type; 78 | } 79 | 80 | QVariant AwaitedException::errorData() const 81 | { 82 | return _data; 83 | } 84 | 85 | QVariantMap AwaitedException::errorObject() const 86 | { 87 | return _data.toMap(); 88 | } 89 | 90 | QVariantList AwaitedException::errorArray() const 91 | { 92 | return _data.toList(); 93 | } 94 | 95 | QString AwaitedException::errorString() const 96 | { 97 | return _data.toString(); 98 | } 99 | 100 | QString AwaitedException::errorString(const std::function &failureTransformer) const 101 | { 102 | if(_type == RestReply::Error::Failure) 103 | return failureTransformer(errorObject(), _code); 104 | else 105 | return errorString(); 106 | } 107 | 108 | QString AwaitedException::errorString(const std::function &failureTransformer) const 109 | { 110 | if(_type == RestReply::Error::Failure) 111 | return failureTransformer(errorArray(), _code); 112 | else 113 | return errorString(); 114 | } 115 | 116 | const char *AwaitedException::what() const noexcept 117 | { 118 | if (_msg.isEmpty()) { 119 | auto mEnum = QMetaEnum::fromType(); 120 | _msg = QByteArray(mEnum.valueToKey(static_cast(_type))) + ": " + 121 | QByteArray::number(_code) + ", " + 122 | _data.toString().toUtf8(); 123 | } 124 | return _msg.constData(); 125 | } 126 | 127 | void AwaitedException::raise() const 128 | { 129 | throw *this; 130 | } 131 | 132 | ExceptionBase *AwaitedException::clone() const 133 | { 134 | return new AwaitedException{*this}; 135 | } 136 | -------------------------------------------------------------------------------- /src/restclient/restreplyawaitable_p.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTREPLYAWAITABLE_P_H 2 | #define RESTREPLYAWAITABLE_P_H 3 | 4 | #include "restreplyawaitable.h" 5 | 6 | #include 7 | 8 | namespace QtRestClient { 9 | 10 | class RestReplyAwaitablePrivate 11 | { 12 | Q_DISABLE_COPY(RestReplyAwaitablePrivate) 13 | public: 14 | RestReplyAwaitablePrivate() = default; 15 | 16 | QPointer reply{}; 17 | 18 | RestReply::DataType successResult = std::nullopt; 19 | QScopedPointer errorResult{}; 20 | }; 21 | 22 | } 23 | 24 | #endif // RESTREPLYAWAITABLE_P_H 25 | -------------------------------------------------------------------------------- /src/restclient/standardpaging.cpp: -------------------------------------------------------------------------------- 1 | #include "standardpaging_p.h" 2 | #include "qtrestclient_helpertypes.h" 3 | using namespace QtRestClient; 4 | 5 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 6 | #include 7 | using namespace QtJsonSerializer; 8 | #endif 9 | 10 | QVariantMap StandardCborPaging::properties() const 11 | { 12 | return _data.toVariant().toMap(); 13 | } 14 | 15 | QCborArray StandardCborPaging::cborItems() const 16 | { 17 | return _items; 18 | } 19 | 20 | QCborValue StandardCborPaging::originalCbor() const 21 | { 22 | return _data; 23 | } 24 | 25 | QVariantMap StandardJsonPaging::properties() const 26 | { 27 | return _data.toVariant().toMap(); 28 | } 29 | 30 | QJsonArray StandardJsonPaging::jsonItems() const 31 | { 32 | return _items; 33 | } 34 | 35 | QJsonValue StandardJsonPaging::originalJson() const 36 | { 37 | return _data; 38 | } 39 | 40 | // ------------- Factory Implementation ------------- 41 | 42 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 43 | IPaging *StandardPagingFactory::createPaging(SerializerBase *, const std::variant &data) const 44 | #else 45 | IPaging *StandardPagingFactory::createPaging(const std::variant &data) const 46 | #endif 47 | { 48 | return std::visit(__private::overload { 49 | [](const QCborValue &value) -> IPaging* { 50 | const auto map = value.toMap(); 51 | auto paging = new StandardCborPaging{}; 52 | paging->_total = map[QStringLiteral("total")].toInteger(paging->_total); 53 | paging->_offset = map[QStringLiteral("offset")].toInteger(paging->_offset); 54 | if (const auto url = extractUrl(map[QStringLiteral("next")]); url) 55 | paging->_next = *url; 56 | if (const auto url = extractUrl(map[QStringLiteral("previous")]); url) 57 | paging->_prev = *url; 58 | paging->_items = map[QStringLiteral("items")].toArray(); 59 | paging->_data = value; 60 | return paging; 61 | }, 62 | [](const QJsonValue &value) -> IPaging* { 63 | const auto obj = value.toObject(); 64 | auto paging = new StandardJsonPaging{}; 65 | paging->_total = obj[QStringLiteral("total")].toInt(static_cast(paging->_total)); 66 | paging->_offset = obj[QStringLiteral("offset")].toInt(static_cast(paging->_offset)); 67 | if (const auto url = extractUrl(obj[QStringLiteral("next")]); url) 68 | paging->_next = *url; 69 | if (const auto url = extractUrl(obj[QStringLiteral("previous")]); url) 70 | paging->_prev = *url; 71 | paging->_items = obj[QStringLiteral("items")].toArray(); 72 | paging->_data = value; 73 | return paging; 74 | } 75 | }, data); 76 | } 77 | 78 | std::optional StandardPagingFactory::extractUrl(const std::variant &value) 79 | { 80 | return std::visit(__private::overload { 81 | [](const QCborValue &data) -> std::optional { 82 | if (data.isUrl()) 83 | return data.toUrl(); 84 | else 85 | return std::nullopt; 86 | }, 87 | [](const QJsonValue &data) -> std::optional { 88 | if (data.isString()) { 89 | QUrl url{data.toString()}; 90 | if (url.isValid()) 91 | return url; 92 | else 93 | return std::nullopt; 94 | } else 95 | return std::nullopt; 96 | } 97 | }, value); 98 | } 99 | -------------------------------------------------------------------------------- /src/restclient/standardpaging_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENT_STANDARDPAGING_P_H 2 | #define QTRESTCLIENT_STANDARDPAGING_P_H 3 | 4 | #include "QtRestClient/qtrestclient_global.h" 5 | #include "QtRestClient/ipaging.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace QtRestClient { 13 | 14 | template 15 | class StandardPagingBase : public TPagingBase 16 | { 17 | static_assert (std::is_base_of_v, "TPagingBase must inherit or be the IPaging interface"); 18 | friend class StandardPagingFactory; 19 | public: 20 | qint64 total() const override; 21 | qint64 offset() const override; 22 | bool hasNext() const override; 23 | QUrl next() const override; 24 | bool hasPrevious() const override; 25 | QUrl previous() const override; 26 | 27 | protected: 28 | qint64 _total = std::numeric_limits::max(); 29 | qint64 _offset = -1; 30 | QUrl _prev; 31 | QUrl _next; 32 | }; 33 | 34 | class Q_RESTCLIENT_EXPORT StandardCborPaging : public StandardPagingBase 35 | { 36 | friend class StandardPagingFactory; 37 | public: 38 | QVariantMap properties() const override; 39 | QCborArray cborItems() const override; 40 | QCborValue originalCbor() const override; 41 | 42 | private: 43 | QCborArray _items; 44 | QCborValue _data; 45 | }; 46 | 47 | class Q_RESTCLIENT_EXPORT StandardJsonPaging : public StandardPagingBase 48 | { 49 | friend class StandardPagingFactory; 50 | public: 51 | QVariantMap properties() const override; 52 | QJsonArray jsonItems() const override; 53 | QJsonValue originalJson() const override; 54 | 55 | private: 56 | QJsonArray _items; 57 | QJsonValue _data; 58 | }; 59 | 60 | class Q_RESTCLIENT_EXPORT StandardPagingFactory : public IPagingFactory 61 | { 62 | public: 63 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 64 | IPaging *createPaging(QtJsonSerializer::SerializerBase *serializer, const std::variant &data) const override; 65 | #else 66 | IPaging *createPaging(const std::variant &data) const override; 67 | #endif 68 | 69 | private: 70 | static std::optional extractUrl(const std::variant &value); 71 | }; 72 | 73 | // ------------- generic implementation ------------- 74 | 75 | template 76 | qint64 StandardPagingBase::total() const 77 | { 78 | return _total; 79 | } 80 | 81 | template 82 | qint64 StandardPagingBase::offset() const 83 | { 84 | return _offset; 85 | } 86 | 87 | template 88 | bool StandardPagingBase::hasNext() const 89 | { 90 | return _next.isValid(); 91 | } 92 | 93 | template 94 | QUrl StandardPagingBase::next() const 95 | { 96 | return _next; 97 | } 98 | 99 | template 100 | bool StandardPagingBase::hasPrevious() const 101 | { 102 | return _prev.isValid(); 103 | } 104 | 105 | template 106 | QUrl StandardPagingBase::previous() const 107 | { 108 | return _prev; 109 | } 110 | 111 | } 112 | 113 | #endif // QTRESTCLIENT_STANDARDPAGING_P_H 114 | -------------------------------------------------------------------------------- /src/restclientauth/authrequestbuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "authrequestbuilder.h" 2 | #include 3 | using namespace QtRestClient; 4 | using namespace QtRestClient::Auth; 5 | 6 | namespace QtRestClient::Auth { 7 | 8 | class AuthExtenderPrivate 9 | { 10 | public: 11 | QPointer oAuth; 12 | }; 13 | 14 | Q_LOGGING_CATEGORY(logAuthExtender, "qt.restclientauth.AuthExtender") 15 | 16 | } 17 | 18 | AuthExtender::AuthExtender(QAbstractOAuth *oAuth) : 19 | d{new AuthExtenderPrivate{}} 20 | { 21 | d->oAuth = oAuth; 22 | } 23 | 24 | AuthExtender::~AuthExtender() = default; 25 | 26 | bool AuthExtender::requiresBody() const 27 | { 28 | return true; 29 | } 30 | 31 | void AuthExtender::extendRequest(QNetworkRequest &request, QByteArray &verb, QByteArray *body) const 32 | { 33 | Q_ASSERT(body); 34 | d->oAuth->prepareRequest(&request, verb, *body); 35 | qCDebug(logAuthExtender) << "Added authorization data to request"; 36 | } 37 | 38 | 39 | 40 | AuthRequestBuilder::AuthRequestBuilder(const QUrl &baseUrl, QAbstractOAuth *oAuth, QNetworkAccessManager *nam) : 41 | RequestBuilder{baseUrl, nam} 42 | { 43 | if (oAuth) 44 | setOAuth(oAuth, !nam); 45 | } 46 | 47 | AuthRequestBuilder &AuthRequestBuilder::setOAuth(QAbstractOAuth *oAuth, bool replaceNam) 48 | { 49 | setExtender(new AuthExtender{oAuth}); 50 | if (replaceNam) 51 | setNetworkAccessManager(oAuth->networkAccessManager()); 52 | return *this; 53 | } 54 | 55 | AuthRequestBuilder::AuthRequestBuilder(const AuthRequestBuilder &other) = default; 56 | 57 | AuthRequestBuilder::AuthRequestBuilder(AuthRequestBuilder &&other) noexcept = default; 58 | 59 | AuthRequestBuilder &AuthRequestBuilder::operator=(const AuthRequestBuilder &other) = default; 60 | 61 | AuthRequestBuilder &AuthRequestBuilder::operator=(AuthRequestBuilder &&other) noexcept = default; 62 | 63 | AuthRequestBuilder::~AuthRequestBuilder() = default; 64 | 65 | AuthRequestBuilder::AuthRequestBuilder(RequestBuilder &&extendedBase) : 66 | RequestBuilder{std::move(extendedBase)} 67 | {} 68 | -------------------------------------------------------------------------------- /src/restclientauth/authrequestbuilder.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENTAUTH_AUTHREQUESTBUILDER_H 2 | #define QTRESTCLIENTAUTH_AUTHREQUESTBUILDER_H 3 | 4 | #include "QtRestClientAuth/qtrestclientauth_global.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace QtRestClient::Auth { 14 | 15 | class AuthExtenderPrivate; 16 | //! An extender that adds OAuth information to a request 17 | class Q_RESTCLIENTAUTH_EXPORT AuthExtender : public RequestBuilder::IExtender 18 | { 19 | public: 20 | //! Constructs the extender from an abstract OAuth interface 21 | AuthExtender(QAbstractOAuth *oAuth); 22 | ~AuthExtender() override; 23 | 24 | bool requiresBody() const override; 25 | void extendRequest(QNetworkRequest &request, QByteArray &verb, QByteArray *body) const override; 26 | 27 | private: 28 | QScopedPointer d; 29 | }; 30 | 31 | //! An extension of the RequestBuilder that uses a QAbstractOAuth to create authenticated requests 32 | class Q_RESTCLIENTAUTH_EXPORT AuthRequestBuilder : public RequestBuilder 33 | { 34 | public: 35 | //! Constructs a builder with the given base url and an OAuth instance to authenticate requests with 36 | AuthRequestBuilder(const QUrl &baseUrl, QAbstractOAuth *oAuth = nullptr, QNetworkAccessManager *nam = nullptr); 37 | //! Copy constructor 38 | AuthRequestBuilder(const AuthRequestBuilder &other); 39 | //! Move constructor 40 | AuthRequestBuilder(AuthRequestBuilder &&other) noexcept; 41 | //! Copy assignment operator 42 | AuthRequestBuilder &operator=(const AuthRequestBuilder &other); 43 | //! Move assignment operator 44 | AuthRequestBuilder &operator=(AuthRequestBuilder &&other) noexcept; 45 | ~AuthRequestBuilder(); 46 | 47 | //! Sets the OAuth instance to authenticate requests created via build() or send() 48 | AuthRequestBuilder &setOAuth(QAbstractOAuth *oAuth, bool replaceNam = true); 49 | 50 | private: 51 | friend class AuthRestClient; 52 | AuthRequestBuilder(RequestBuilder &&extendedBase); 53 | }; 54 | 55 | Q_DECLARE_LOGGING_CATEGORY(logAuthExtender) 56 | 57 | } 58 | 59 | #endif // QTRESTCLIENTAUTH_AUTHREQUESTBUILDER_H 60 | -------------------------------------------------------------------------------- /src/restclientauth/authrestclient.cpp: -------------------------------------------------------------------------------- 1 | #include "authrestclient.h" 2 | #include "authrestclient_p.h" 3 | #include "authrequestbuilder.h" 4 | #include 5 | using namespace QtRestClient; 6 | using namespace QtRestClient::Auth; 7 | 8 | AuthRestClient::AuthRestClient(QAbstractOAuth *oAuth, QObject *parent) : 9 | AuthRestClient{DataMode::Json, oAuth, parent} 10 | {} 11 | 12 | AuthRestClient::AuthRestClient(RestClient::DataMode dataMode, QAbstractOAuth *oAuth, QObject *parent) : 13 | AuthRestClient{*new AuthRestClientPrivate{}, parent} 14 | { 15 | setupOAuth(oAuth); 16 | setDataMode(dataMode); 17 | } 18 | 19 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 20 | AuthRestClient::AuthRestClient(QtJsonSerializer::SerializerBase *serializer, QAbstractOAuth *oAuth, QObject *parent) : 21 | AuthRestClient{*new AuthRestClientPrivate{}, parent} 22 | { 23 | setupOAuth(oAuth); 24 | setSerializer(serializer); 25 | } 26 | #endif 27 | 28 | QAbstractOAuth *AuthRestClient::oAuth() const 29 | { 30 | Q_D(const AuthRestClient); 31 | return d->oAuth; 32 | } 33 | 34 | AuthRequestBuilder AuthRestClient::authBuilder() const 35 | { 36 | return AuthRequestBuilder{builder()}; 37 | } 38 | 39 | RequestBuilder AuthRestClient::builder() const 40 | { 41 | Q_D(const AuthRestClient); 42 | return RestClient::builder() 43 | .setExtender(new AuthExtender{d->oAuth}); 44 | } 45 | 46 | AuthRestClient::AuthRestClient(AuthRestClientPrivate &dd, QObject *parent) : 47 | RestClient{dd, parent} 48 | {} 49 | 50 | void AuthRestClient::setupOAuth(QAbstractOAuth *oAuth) 51 | { 52 | Q_D(AuthRestClient); 53 | d->oAuth = oAuth; 54 | d->oAuth->setParent(this); 55 | d->nam = oAuth->networkAccessManager(); 56 | d->nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); 57 | } 58 | -------------------------------------------------------------------------------- /src/restclientauth/authrestclient.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENTAUTH_AUTHRESTCLIENT_H 2 | #define QTRESTCLIENTAUTH_AUTHRESTCLIENT_H 3 | 4 | #include 5 | 6 | #include "QtRestClientAuth/qtrestclientauth_global.h" 7 | #include "QtRestClientAuth/authrequestbuilder.h" 8 | 9 | namespace QtRestClient::Auth { 10 | 11 | class AuthRestClientPrivate; 12 | //! An extension of the RestClient that uses a AuthRequestBuilder to create authenticated requests 13 | class Q_RESTCLIENTAUTH_EXPORT AuthRestClient : public RestClient 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | //! Constructor with the OAuth instance to use for authenticating requests 19 | explicit AuthRestClient(QAbstractOAuth *oAuth, QObject *parent = nullptr); 20 | //! Constructor with the data mode and an OAuth instance to use for authenticating requests 21 | explicit AuthRestClient(DataMode dataMode, QAbstractOAuth *oAuth, QObject *parent = nullptr); 22 | #ifndef Q_RESTCLIENT_NO_JSON_SERIALIZER 23 | //! Constructor with a serializer and an OAuth instance to use for authenticating requests 24 | explicit AuthRestClient(QtJsonSerializer::SerializerBase *serializer, QAbstractOAuth *oAuth, QObject *parent = nullptr); 25 | #endif 26 | 27 | //! Returns the used OAuth instance 28 | QAbstractOAuth *oAuth() const; 29 | 30 | //! Returns the same as builder(), but as AuthRequestBuilder instance 31 | AuthRequestBuilder authBuilder() const; 32 | RequestBuilder builder() const override; 33 | 34 | protected: 35 | //! @private 36 | AuthRestClient(AuthRestClientPrivate &dd, QObject *parent); 37 | //! @private 38 | void setupOAuth(QAbstractOAuth *oAuth); 39 | 40 | private: 41 | Q_DECLARE_PRIVATE(AuthRestClient) 42 | }; 43 | 44 | } 45 | 46 | #endif // QTRESTCLIENTAUTH_AUTHRESTCLIENT_H 47 | -------------------------------------------------------------------------------- /src/restclientauth/authrestclient_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENTAUTH_AUTHRESTCLIENT_P_H 2 | #define QTRESTCLIENTAUTH_AUTHRESTCLIENT_P_H 3 | 4 | #include "authrestclient.h" 5 | 6 | #include 7 | 8 | namespace QtRestClient::Auth { 9 | 10 | class AuthRestClientPrivate : public RestClientPrivate 11 | { 12 | Q_DECLARE_PUBLIC(AuthRestClient) 13 | public: 14 | QAbstractOAuth *oAuth = nullptr; 15 | }; 16 | 17 | } 18 | 19 | #endif // QTRESTCLIENTAUTH_AUTHRESTCLIENT_P_H 20 | -------------------------------------------------------------------------------- /src/restclientauth/qtrestclientauth_global.h: -------------------------------------------------------------------------------- 1 | #ifndef QTRESTCLIENTAUTH_GLOBAL_H 2 | #define QTRESTCLIENTAUTH_GLOBAL_H 3 | 4 | #include 5 | 6 | #ifndef QT_STATIC 7 | # if defined(QT_BUILD_RESTCLIENTAUTH_LIB) 8 | # define Q_RESTCLIENTAUTH_EXPORT Q_DECL_EXPORT 9 | # else 10 | # define Q_RESTCLIENTAUTH_EXPORT Q_DECL_IMPORT 11 | # endif 12 | #else 13 | # define Q_RESTCLIENTAUTH_EXPORT 14 | #endif 15 | 16 | #endif // QTRESTCLIENTAUTH_GLOBAL_H 17 | -------------------------------------------------------------------------------- /src/restclientauth/restclientauth.pro: -------------------------------------------------------------------------------- 1 | TARGET = QtRestClientAuth 2 | 3 | QT = core network networkauth restclient restclient-private 4 | 5 | HEADERS = \ 6 | authrequestbuilder.h \ 7 | authrestclient.h \ 8 | authrestclient_p.h \ 9 | qtrestclientauth_global.h 10 | 11 | SOURCES = \ 12 | authrequestbuilder.cpp \ 13 | authrestclient.cpp 14 | 15 | load(qt_module) 16 | 17 | win32 { 18 | QMAKE_TARGET_PRODUCT = "$$TARGET" 19 | QMAKE_TARGET_COMPANY = "Skycoder42" 20 | QMAKE_TARGET_COPYRIGHT = "Felix Barz" 21 | } else:mac { 22 | QMAKE_TARGET_BUNDLE_PREFIX = "de.skycoder42." 23 | } 24 | -------------------------------------------------------------------------------- /src/src.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += restclient \ 4 | imports 5 | 6 | imports.depends += restclient 7 | 8 | !wasm { 9 | SUBDIRS += restclientauth 10 | restclientauth.depends += restclient 11 | imports.depends += restclientauth 12 | } 13 | 14 | QMAKE_EXTRA_TARGETS += run-tests 15 | -------------------------------------------------------------------------------- /sync.profile: -------------------------------------------------------------------------------- 1 | %modules = ( 2 | "QtRestClient" => "$basedir/src/restclient", 3 | "QtRestClientAuth" => "$basedir/src/restclientauth", 4 | ); 5 | 6 | $publicclassregexp = "QtRestClient::(?!PagingData|__private).+"; 7 | 8 | %classnames = ( 9 | "simple.h" => "Simple", 10 | ); 11 | -------------------------------------------------------------------------------- /tests/auto/auto.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += cmake \ 4 | restclient \ 5 | qml 6 | 7 | qml.depends += restclient 8 | 9 | !wasm { 10 | SUBDIRS += restclientauth 11 | restclientauth.depends += restclient 12 | } 13 | 14 | cmake.CONFIG += no_run-tests_target 15 | prepareRecursiveTarget(run-tests) 16 | QMAKE_EXTRA_TARGETS += run-tests 17 | -------------------------------------------------------------------------------- /tests/auto/cmake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | project(qmake_cmake_files) 5 | 6 | enable_testing() 7 | 8 | find_package(Qt5Core REQUIRED) 9 | 10 | include("${_Qt5CTestMacros}") 11 | 12 | test_module_includes( 13 | RestClient QRestClient 14 | ) 15 | -------------------------------------------------------------------------------- /tests/auto/cmake/cmake.pro: -------------------------------------------------------------------------------- 1 | 2 | # Cause make to do nothing. 3 | TEMPLATE = subdirs 4 | 5 | CMAKE_QT_MODULES_UNDER_TEST = restclient 6 | 7 | CONFIG += ctest_testcase 8 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/TestQmlRestClient.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += restclient 4 | CONFIG += qmltestcase console 5 | 6 | TARGET = tst_qmlrestclient 7 | 8 | HEADERS += \ 9 | testmacro.h 10 | 11 | SOURCES += tst_qmlrestclient.cpp 12 | 13 | REST_API_FILES += \ 14 | user.xml \ 15 | post.xml \ 16 | api_posts.xml \ 17 | test_api.xml \ 18 | simplepost.xml 19 | 20 | importFiles.path = . 21 | DEPLOYMENT += importFiles 22 | 23 | DISTFILES += \ 24 | tst_qmlrestclient.qml 25 | 26 | DEFINES += SRCDIR=\\\"$$_PRO_FILE_PWD_/\\\" 27 | 28 | LIB_PWD = $$OUT_PWD/../../restclient/testlib 29 | include(../../restclient/tests.pri) 30 | 31 | win32:msvc:CONFIG(debug, debug|release) { 32 | QMAKE_EXTRA_TARGETS -= runtarget 33 | runtarget_dummy.target = run-tests 34 | QMAKE_EXTRA_TARGETS += runtarget_dummy 35 | } 36 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/api_posts.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | post.h 9 | 10 | posts 11 | 12 | 15 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | user 26 | 27 | 28 | 29 | 32 | ../post-add&complete=true 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/post.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | QVersionNumber 8 | user.h 9 | testmacro.h 10 | 11 | -1 12 | 13 | 14 | 15 | nullptr 16 | 17 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/simplepost.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | QtRestClient/Simple 9 | post.h 10 | 11 | -1 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/test_api.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | user.h 10 | api_posts.h 11 | testmacro.h 12 | 13 | TestSvr::getUrl() 14 | QString::number(LIMIT) 15 |
APP_SECRET
16 | 17 | 18 | 19 | 24 | 25 | User::GenderType::Female 26 |
babaric
27 |
28 |
29 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/testmacro.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTMACRO_H 2 | #define TESTMACRO_H 3 | 4 | #include 5 | #include 6 | 7 | #define TEST_EXPORT 8 | #define LIMIT 100 9 | #define APP_SECRET "baum42" 10 | 11 | namespace TestSvr { 12 | QUrl getUrl(); 13 | } 14 | 15 | #endif // TESTMACRO_H 16 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/tst_qmlrestclient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "testlib.h" 6 | 7 | namespace { 8 | 9 | QPointer _server; 10 | 11 | } 12 | 13 | namespace TestSvr { 14 | 15 | QUrl getUrl() { 16 | return _server->url(); 17 | } 18 | 19 | } 20 | 21 | class Setup : public QObject 22 | { 23 | Q_OBJECT 24 | 25 | public slots: 26 | void qmlEngineAvailable(QQmlEngine *engine) 27 | { 28 | engine->rootContext()->setContextProperty("testPort", _server->port()); 29 | } 30 | }; 31 | 32 | static void initImportPath() 33 | { 34 | //start the http server 35 | _server = new HttpServer(qApp); 36 | QVERIFY(_server->setupRoutes()); 37 | _server->setAdvancedData(); 38 | 39 | QCborMap posts; 40 | for (auto i = 0; i < 100; i++) { 41 | posts[i] = QCborMap { 42 | {QStringLiteral("id"), i}, 43 | {QStringLiteral("user"), QCborMap { 44 | {QStringLiteral("id"), qCeil(i/2.0)}, 45 | {QStringLiteral("name"), QStringLiteral("user%1").arg(qCeil(i/2.0))}, 46 | }}, 47 | {QStringLiteral("title"), QStringLiteral("Title%1").arg(i)}, 48 | {QStringLiteral("body"), QStringLiteral("Body%1").arg(i)} 49 | }; 50 | } 51 | _server->setSubData(QStringLiteral("posts"), posts); 52 | } 53 | Q_COREAPP_STARTUP_FUNCTION(initImportPath) 54 | 55 | QUICK_TEST_MAIN_WITH_SETUP(qmlrestclient, Setup) 56 | 57 | #include "tst_qmlrestclient.moc" 58 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/tst_qmlrestclient.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | import de.skycoder42.RestClient 3.0 3 | import QtTest 1.1 4 | import de.skycoder42.qtrestclient.test 3.0 5 | 6 | Item { 7 | id: root 8 | 9 | TestCase { 10 | id: testCase 11 | name: "RestClient" 12 | 13 | RestClient { 14 | id: api 15 | baseUrl: "http://localhost:%1".arg(testPort) 16 | 17 | Component.onCompleted: QtRestClient.addGlobalApi("testapi", api) 18 | 19 | RestClass { 20 | id: postClass 21 | path: "posts" 22 | } 23 | 24 | RestClass { 25 | id: pagesClass 26 | path: "pages" 27 | } 28 | 29 | RestClass { 30 | id: userClass 31 | path: "users" 32 | 33 | RestClass { 34 | id: userNameClass 35 | path: "name" 36 | } 37 | } 38 | } 39 | 40 | QtObject { 41 | id: waiter 42 | property bool ok: false 43 | property bool done: false 44 | property int counter: 0 45 | 46 | function jcomp(actual, expected) { 47 | compare(actual, expected, JSON.stringify(actual) + " != " + JSON.stringify(expected)); 48 | } 49 | 50 | function repConnect(reply) { 51 | reply.addFailedHandler(function(code, data) { 52 | done = true; 53 | testCase.fail(code + ": " + JSON.stringify(data)); 54 | }); 55 | reply.addErrorHandler(function(error, code, type) { 56 | done = true; 57 | testCase.fail(error + " (" + code + ", " + type + ")"); 58 | }); 59 | } 60 | 61 | function ewait(reply) { 62 | repConnect(reply); 63 | var i = 0; 64 | while (!done && i < 50){ 65 | i++; 66 | testCase.wait(100); 67 | } 68 | done = false; 69 | } 70 | 71 | function cwait(reply, counter) { 72 | repConnect(reply); 73 | var i = 0; 74 | while (!done && waiter.counter < counter && i < 50){ 75 | i++; 76 | testCase.wait(100); 77 | } 78 | done = false; 79 | ok = waiter.counter === counter; 80 | waiter.counter = 0; 81 | } 82 | } 83 | 84 | function initTestCase() { 85 | // check if all restclasses have been initialized 86 | verify(postClass.restClass); 87 | verify(userClass.restClass); 88 | verify(userNameClass.restClass); 89 | 90 | verify(QtRestClient.apiClient("testapi")); 91 | verify(QtRestClient.createApiClass("testapi", "some/sub/path", root)); 92 | 93 | verify(postClass.get("1")); 94 | verify(userClass.put(["hello", "world"])); 95 | verify(userNameClass.post({ 96 | id: 42, 97 | name: "user" 98 | })); 99 | } 100 | 101 | function test_list() { 102 | waiter.ok = false; 103 | var reply = postClass.get(); 104 | reply.addSucceededHandler(function(code, data){ 105 | waiter.done = true; 106 | compare(code, 200); 107 | compare(data.length, 100); 108 | waiter.ok = true; 109 | }); 110 | waiter.ewait(reply); 111 | verify(waiter.ok); 112 | } 113 | 114 | function test_post() { 115 | waiter.ok = false; 116 | var params = { 117 | body: "baum", 118 | id: 100, 119 | title: "baum", 120 | userId: "42" 121 | }; 122 | var reply = postClass.post(params); 123 | reply.addSucceededHandler(function(code, data){ 124 | waiter.done = true; 125 | compare(code, 200); 126 | compare(data, params, JSON.stringify(data) + " != " + JSON.stringify(params)); 127 | waiter.ok = true; 128 | }); 129 | waiter.ewait(reply); 130 | verify(waiter.ok); 131 | } 132 | 133 | function test_put() { 134 | waiter.ok = false; 135 | var obj = { 136 | id: 1, 137 | userId: 42, 138 | title: "baum", 139 | body: "baum" 140 | }; 141 | var reply = postClass.put("1", obj); 142 | reply.addSucceededHandler(function(code, data){ 143 | waiter.done = true; 144 | compare(code, 200); 145 | compare(data, obj, JSON.stringify(data) + " != " + JSON.stringify(obj)); 146 | waiter.ok = true; 147 | }); 148 | waiter.ewait(reply); 149 | verify(waiter.ok); 150 | } 151 | 152 | function test_paging() { 153 | waiter.ok = false; 154 | var reply = pagesClass.get("0"); 155 | reply.addSucceededHandler(function(code, data) { 156 | compare(code, 200); 157 | verify(data); 158 | var paging = QtRestClient.createPaging(api, data); 159 | verify(paging); 160 | verify(paging.valid); 161 | compare(paging.total, 100); 162 | compare(paging.offset, waiter.counter); 163 | compare(paging.items.length, 10); 164 | paging.iterate(function(item, index){ 165 | compare(item.id, waiter.counter); 166 | compare(index, waiter.counter++); 167 | return true; 168 | }); 169 | }); 170 | waiter.cwait(reply, 100); 171 | verify(waiter.ok); 172 | } 173 | } 174 | 175 | TestCase { 176 | id: builderTest 177 | name: "qrestbuilder" 178 | 179 | TestApi { 180 | id: buildApi 181 | } 182 | 183 | QtObject { 184 | id: buildWaiter 185 | property bool ok: false 186 | property bool done: false 187 | property int counter: 0 188 | 189 | function jcomp(actual, expected) { 190 | compare(actual, expected, JSON.stringify(actual) + " != " + JSON.stringify(expected)); 191 | } 192 | 193 | function repConnect(reply) { 194 | reply.addFailedHandler(function(code, data) { 195 | done = true; 196 | testCase.fail(code + ": " + JSON.stringify(data)); 197 | }); 198 | reply.addErrorHandler(function(error, code, type) { 199 | done = true; 200 | testCase.fail(error + " (" + code + ", " + type + ")"); 201 | }); 202 | } 203 | 204 | function ewait(reply) { 205 | repConnect(reply); 206 | var i = 0; 207 | while(!done && i < 50){ 208 | i++; 209 | testCase.wait(100); 210 | } 211 | done = false; 212 | } 213 | 214 | function cwait(reply, counter) { 215 | repConnect(reply); 216 | var i = 0; 217 | while(!done && buildWaiter.counter < counter && i < 50){ 218 | i++; 219 | testCase.wait(100); 220 | } 221 | done = false; 222 | ok = buildWaiter.counter === counter; 223 | buildWaiter.counter = 0; 224 | } 225 | } 226 | 227 | function initTestCase() { 228 | verify(buildApi) 229 | verify(buildApi.posts) 230 | } 231 | 232 | function test_list() { 233 | buildWaiter.ok = false; 234 | var reply = buildApi.posts.listPosts(); 235 | verify(reply); 236 | reply.addSucceededHandler(function(code, data){ 237 | buildWaiter.done = true; 238 | compare(code, 200); 239 | verify(data); 240 | buildWaiter.ok = true; 241 | }); 242 | buildWaiter.ewait(reply); 243 | verify(buildWaiter.ok); 244 | } 245 | 246 | function test_post() { 247 | buildWaiter.ok = false; 248 | var reply = buildApi.posts.post(42); 249 | verify(reply); 250 | reply.addSucceededHandler(function(code, data){ 251 | buildWaiter.done = true; 252 | compare(code, 200); 253 | compare(data.id, 42); 254 | buildWaiter.ok = true; 255 | }); 256 | buildWaiter.ewait(reply); 257 | verify(buildWaiter.ok); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /tests/auto/qml/TestQmlRestClient/user.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 0x00 14 | 0x01 15 | 0x02 16 | 0x04 17 | (Person|Sir|Doctor|Professor) 18 | 19 | 20 | -1 21 | Anonymous 22 | User::GenderType::Male 23 | User::UserType::Person 24 | 25 | -------------------------------------------------------------------------------- /tests/auto/qml/qml.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | TestQmlRestClient 5 | 6 | prepareRecursiveTarget(run-tests) 7 | QMAKE_EXTRA_TARGETS += run-tests 8 | -------------------------------------------------------------------------------- /tests/auto/restclient/.gitignore: -------------------------------------------------------------------------------- 1 | #ignore the test database 2 | test-rest-db.json -------------------------------------------------------------------------------- /tests/auto/restclient/IntegrationTest/IntegrationTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += testlib 4 | QT -= gui 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = tst_integration 9 | 10 | include(../tests.pri) 11 | 12 | HEADERS += \ 13 | jphuser.h 14 | 15 | SOURCES += tst_integration.cpp \ 16 | jphuser.cpp 17 | 18 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 19 | -------------------------------------------------------------------------------- /tests/auto/restclient/IntegrationTest/jphuser.cpp: -------------------------------------------------------------------------------- 1 | #include "jphuser.h" 2 | 3 | QUrl JphUserSimple::extensionHref() const 4 | { 5 | return href; 6 | } 7 | -------------------------------------------------------------------------------- /tests/auto/restclient/IntegrationTest/jphuser.h: -------------------------------------------------------------------------------- 1 | #ifndef JPHUSER_H 2 | #define JPHUSER_H 3 | 4 | #include 5 | #include 6 | 7 | struct JphUser 8 | { 9 | Q_GADGET 10 | 11 | Q_PROPERTY(int id MEMBER id) 12 | Q_PROPERTY(QString name MEMBER name) 13 | 14 | public: 15 | int id = -1; 16 | QString name; 17 | }; 18 | 19 | struct JphUserSimple : public QtRestClient::Simple 20 | { 21 | Q_GADGET 22 | 23 | Q_PROPERTY(int id MEMBER id) 24 | Q_PROPERTY(QUrl href MEMBER href) 25 | 26 | public: 27 | QUrl extensionHref() const override; 28 | 29 | int id = -1; 30 | QUrl href; 31 | }; 32 | 33 | #endif // JPHUSER_H 34 | -------------------------------------------------------------------------------- /tests/auto/restclient/PagingModelTest/PagingModelTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += testlib gui widgets 4 | 5 | TARGET = tst_pagingmodel 6 | 7 | include(../tests.pri) 8 | 9 | SOURCES += \ 10 | tst_pagingmodel.cpp 11 | 12 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 13 | -------------------------------------------------------------------------------- /tests/auto/restclient/PagingModelTest/tst_pagingmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "testlib.h" 2 | 3 | #include 4 | using namespace QtRestClient; 5 | using namespace QtJsonSerializer; 6 | 7 | class PagingModelTest : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | private Q_SLOTS: 12 | void initTestCase(); 13 | void cleanupTestCase(); 14 | 15 | void testInitModel(); 16 | void testReplyInitModel(); 17 | void testJsonInitModel(); 18 | 19 | private: 20 | HttpServer *server; 21 | RestClient *client; 22 | PagingModel *model; 23 | 24 | QAbstractItemModelTester *modelTester; 25 | }; 26 | 27 | void PagingModelTest::initTestCase() 28 | { 29 | JsonSerializer::registerListConverters(); 30 | server = new HttpServer(this); 31 | QVERIFY(server->setupRoutes()); 32 | server->setAdvancedData(); 33 | client = Testlib::createClient(this); 34 | QVERIFY(client->serializer()); 35 | client->setBaseUrl(server->url()); 36 | model = new PagingModel{this}; 37 | 38 | // create tester 39 | modelTester = new QAbstractItemModelTester{ 40 | model, 41 | QAbstractItemModelTester::FailureReportingMode::QtTest, 42 | this 43 | }; 44 | } 45 | 46 | void PagingModelTest::cleanupTestCase() 47 | { 48 | model->deleteLater(); 49 | model = nullptr; 50 | client->deleteLater(); 51 | client = nullptr; 52 | server->deleteLater(); 53 | server = nullptr; 54 | } 55 | 56 | void PagingModelTest::testInitModel() 57 | { 58 | // create mappings 59 | const auto c1 = model->addColumn(QStringLiteral("title"), "title"); 60 | model->addRole(c1, Qt::ToolTipRole, "id"); 61 | const auto c2 = model->addColumn(QStringLiteral("body"), "body"); 62 | model->addRole(c2, Qt::ToolTipRole, "userId"); 63 | 64 | QUrl url {QStringLiteral("pages/0")}; 65 | QVERIFY(url.isValid()); 66 | model->initialize(url, client->rootClass()); 67 | 68 | // let the model populate itself 69 | QTRY_COMPARE(model->rowCount(), 100); 70 | QVERIFY(!model->canFetchMore({})); 71 | QCOMPARE(model->headerData(0).toString(), QStringLiteral("title")); 72 | QCOMPARE(model->headerData(1).toString(), QStringLiteral("body")); 73 | 74 | // test contained data 75 | QCOMPARE(model->columnCount(), 2); 76 | for (auto i = 0; i < 100; ++i) { 77 | const auto mIndex = model->index(i, 0); 78 | QVERIFY(mIndex.isValid()); 79 | // test returned object 80 | const auto post = model->object(mIndex); 81 | QVERIFY(post); 82 | QCOMPARE(post->id, i); 83 | QCOMPARE(post->userId, qCeil(i/2.0)); 84 | QCOMPARE(post->title, QStringLiteral("Title%1").arg(i)); 85 | QCOMPARE(post->body, QStringLiteral("Body%1").arg(i)); 86 | // test direct model data 87 | QCOMPARE(model->data(mIndex, model->roleNames().key("id")), QVariant{post->id}); 88 | QCOMPARE(model->data(mIndex, model->roleNames().key("userId")), QVariant{post->userId}); 89 | QCOMPARE(model->data(mIndex, model->roleNames().key("title")), QVariant{post->title}); 90 | QCOMPARE(model->data(mIndex, model->roleNames().key("body")), QVariant{post->body}); 91 | // test mapped model data 92 | QCOMPARE(model->data(mIndex, Qt::ToolTipRole), QVariant{post->id}); 93 | QCOMPARE(model->data(mIndex.sibling(i, 1), Qt::ToolTipRole), QVariant{post->userId}); 94 | QCOMPARE(model->data(mIndex, Qt::DisplayRole), QVariant{post->title}); 95 | QCOMPARE(model->data(mIndex.sibling(i, 1), Qt::DisplayRole), QVariant{post->body}); 96 | } 97 | } 98 | 99 | void PagingModelTest::testReplyInitModel() 100 | { 101 | QUrl url {QStringLiteral("pages/0")}; 102 | QVERIFY(url.isValid()); 103 | model->initialize(client->rootClass()->get(url), client->rootClass()); 104 | 105 | // let the model populate itself 106 | QTRY_COMPARE(model->rowCount(), 100); 107 | QVERIFY(!model->canFetchMore({})); 108 | } 109 | 110 | void PagingModelTest::testJsonInitModel() 111 | { 112 | model->clearColumns(); 113 | QUrl url {QStringLiteral("pages/0")}; 114 | QVERIFY(url.isValid()); 115 | model->initialize(url, client->rootClass()); 116 | 117 | // let the model populate itself 118 | QTRY_COMPARE(model->rowCount(), 100); 119 | QVERIFY(!model->canFetchMore({})); 120 | 121 | // test contained data 122 | QCOMPARE(model->columnCount(), 1); 123 | for (auto i = 0; i < 100; ++i) { 124 | const auto mIndex = model->index(i, 0); 125 | QVERIFY(mIndex.isValid()); 126 | // test returned object 127 | const auto post = model->object(mIndex).toObject(); 128 | QCOMPARE(post[QStringLiteral("id")].toInt(), i); 129 | QCOMPARE(post[QStringLiteral("userId")].toInt(), qCeil(i/2.0)); 130 | QCOMPARE(post[QStringLiteral("title")].toString(), QStringLiteral("Title%1").arg(i)); 131 | QCOMPARE(post[QStringLiteral("body")].toString(), QStringLiteral("Body%1").arg(i)); 132 | // test direct model data 133 | QCOMPARE(model->data(mIndex, PagingModel::ModelDataRole).toJsonObject(), post); 134 | } 135 | } 136 | 137 | QTEST_MAIN(PagingModelTest) 138 | 139 | #include "tst_pagingmodel.moc" 140 | -------------------------------------------------------------------------------- /tests/auto/restclient/RequestBuilderTest/RequestBuilderTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += testlib 4 | QT -= gui 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = tst_requestbuilder 9 | 10 | include(../tests.pri) 11 | 12 | SOURCES += tst_requestbuilder.cpp 13 | 14 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 15 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestAwaitablesTest/RestAwaitablesTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += testlib 4 | QT -= gui 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = tst_restawaitables 9 | 10 | include(../tests.pri) 11 | 12 | SOURCES += \ 13 | tst_restawaitables.cpp 14 | 15 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 16 | 17 | QDEP_DEPENDS += Skycoder42/QtCoroutines@1.1.2 18 | 19 | !load(qdep):warning("Failed to load qdep feature! Run 'qdep prfgen --qmake $$QMAKE_QMAKE' to create it.") 20 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/RestBuilderTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += testlib 4 | QT -= gui 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = tst_restbuilder 9 | 10 | include(../tests.pri) 11 | 12 | HEADERS += \ 13 | testmacro.h 14 | 15 | SOURCES += tst_restbuilder.cpp 16 | 17 | REST_API_FILES += \ 18 | user.xml \ 19 | post.xml \ 20 | api_posts.xml \ 21 | test_api.xml \ 22 | simplepost.xml \ 23 | user_props.xml 24 | 25 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 26 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/api_posts.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | post.h 8 | 9 | posts 10 | 11 | 14 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | user 25 | 26 | 27 | 28 | 30 | users 31 | 32 | 33 | 36 | ../post-add&complete=true 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/post.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | QVersionNumber 8 | user.h 9 | testmacro.h 10 | 11 | -1 12 | 13 | 14 | 15 | nullptr 16 | 17 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/simplepost.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | QtRestClient/Simple 8 | post.h 9 | 10 | -1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/test_api.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | user.h 11 | api_posts.h 12 | testmacro.h 13 | 14 | serverUrl() 15 | QString::number(LIMIT) 16 |
no
17 |
APP_SECRET
18 | 19 | 20 | 21 | 26 | 27 | User::GenderType::Female 28 |
babaric
29 |
30 |
31 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/testmacro.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTMACRO_H 2 | #define TESTMACRO_H 3 | 4 | #include 5 | 6 | #define TEST_EXPORT 7 | #define LIMIT 100 8 | #define APP_SECRET "baum42" 9 | 10 | extern QUrl serverUrl(); 11 | 12 | #endif // TESTMACRO_H 13 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/tst_restbuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "testlib.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace TestSpace; 8 | 9 | namespace { 10 | QUrl _serverUrl; 11 | } 12 | 13 | QUrl serverUrl() { 14 | return _serverUrl; 15 | } 16 | 17 | class RestBuilderTest : public QObject 18 | { 19 | Q_OBJECT 20 | 21 | private Q_SLOTS: 22 | void initTestCase(); 23 | void cleanupTestCase(); 24 | void testCustomCompiledObject(); 25 | void testCustomCompiledGadget(); 26 | void testCustomCompiledApi(); 27 | void testCustomCompiledApiPosts(); 28 | 29 | private: 30 | HttpServer *server; 31 | }; 32 | 33 | void RestBuilderTest::initTestCase() 34 | { 35 | server = new HttpServer(45715, this); 36 | QVERIFY(server->setupRoutes()); 37 | _serverUrl = server->url(); 38 | 39 | QCborMap posts; 40 | for(auto i = 0; i < 100; i++) { 41 | posts[i] = QCborMap { 42 | {QStringLiteral("id"), i}, 43 | {QStringLiteral("user"), QCborMap { 44 | {QStringLiteral("id"), qCeil(i/2.0)}, 45 | {QStringLiteral("name"), QStringLiteral("user%1").arg(qCeil(i/2.0))}, 46 | }}, 47 | {QStringLiteral("title"), QStringLiteral("Title%1").arg(i)}, 48 | {QStringLiteral("body"), QStringLiteral("Body%1").arg(i)} 49 | }; 50 | } 51 | server->setSubData(QStringLiteral("posts"), posts); 52 | 53 | qRegisterMetaType(); 54 | } 55 | 56 | void RestBuilderTest::cleanupTestCase() 57 | { 58 | server->deleteLater(); 59 | server = nullptr; 60 | } 61 | 62 | void RestBuilderTest::testCustomCompiledObject() 63 | { 64 | User user; 65 | 66 | QSignalSpy idSpy(&user, &User::idChanged); 67 | QSignalSpy nameSpy(&user, &User::nameChanged); 68 | 69 | QCOMPARE(idSpy.count(), 0); 70 | user.setId(42); 71 | QCOMPARE(idSpy.count(), 1); 72 | QCOMPARE(nameSpy.count(), 0); 73 | user.setName("baum"); 74 | QCOMPARE(nameSpy.count(), 1); 75 | user.setGender(User::GenderType::Male); 76 | user.setTitle(User::Doctor | User::Professor); 77 | 78 | QCOMPARE(user.property("id").toInt(), 42); 79 | QCOMPARE(user.property("name").toString(), QStringLiteral("baum")); 80 | QCOMPARE(user.property("gender").value(), User::GenderType::Male); 81 | QCOMPARE(user.property("title").toInt(), static_cast(User::Doctor | User::Professor)); 82 | 83 | User user2 {42, QStringLiteral("baum"), User::GenderType::Male, User::Doctor | User::Professor}; 84 | QVERIFY(user.equals(&user2)); 85 | } 86 | 87 | void RestBuilderTest::testCustomCompiledGadget() 88 | { 89 | Post post; 90 | 91 | post.setId(42); 92 | post.setTitle(QStringLiteral("baum")); 93 | post.setVersion(QVersionNumber{1, 1, 0}); 94 | post.setBody(QStringLiteral("baum == 42")); 95 | QCOMPARE(post.user(), nullptr); 96 | 97 | Post post2 {42, QStringLiteral("baum"), QVersionNumber{1, 1, 0}, QStringLiteral("baum == 42")}; 98 | QCOMPARE(post2, post); 99 | } 100 | 101 | void RestBuilderTest::testCustomCompiledApi() 102 | { 103 | // test eveything is there 104 | auto api = new TestApi(this); 105 | QVERIFY(api->restClient()); 106 | QVERIFY(api->restClass()); 107 | QVERIFY(api->posts()); 108 | api->deleteLater(); 109 | 110 | //same for factory creation 111 | auto t1 = TestApi::factory().instance(this); 112 | QVERIFY(t1); 113 | t1->deleteLater(); 114 | auto t2 = TestApi::factory().posts().instance(this); 115 | QVERIFY(t2); 116 | t2->deleteLater(); 117 | } 118 | 119 | void RestBuilderTest::testCustomCompiledApiPosts() 120 | { 121 | auto api = new TestApi(this); 122 | 123 | bool called = false; 124 | auto reply = api->posts()->listPosts(); 125 | reply->onSucceeded([&](int code, const QList &posts){ 126 | called = true; 127 | QCOMPARE(code, 200); 128 | QCOMPARE(posts.size(), 100); 129 | }); 130 | reply->onAllErrors([&](const QString &error, int, QtRestClient::RestReply::Error){ 131 | called = true; 132 | QFAIL(qUtf8Printable(error)); 133 | }); 134 | QTRY_VERIFY(called); 135 | 136 | called = false; 137 | auto reply2 = api->posts()->post(42); 138 | reply2->onSucceeded([&](int code, const Post &post){ 139 | called = true; 140 | QCOMPARE(code, 200); 141 | QCOMPARE(post.id(), 42); 142 | QVERIFY(post.user()); 143 | QCOMPARE(post.user()->id(), 42/2); 144 | post.user()->deleteLater(); 145 | }); 146 | reply2->onAllErrors([&](const QString &error, int, QtRestClient::RestReply::Error){ 147 | called = true; 148 | QFAIL(qUtf8Printable(error)); 149 | }); 150 | QTRY_VERIFY(called); 151 | 152 | QCoreApplication::processEvents(); 153 | called = false; 154 | QSignalSpy errorSpy(api->posts(), &PostClass::apiError); 155 | api->posts()->setErrorTranslator([&](const QString &error, int){ 156 | called = true; 157 | return error; 158 | }); 159 | auto reply3 = api->posts()->post(4242); 160 | reply3->onSucceeded([&](int, const Post &){ 161 | called = true; 162 | QFAIL("Expected to fail!"); 163 | }); 164 | QTRY_VERIFY(called); 165 | 166 | if (errorSpy.isEmpty()) 167 | QVERIFY(errorSpy.wait()); 168 | QCOMPARE(errorSpy.size(), 1); 169 | QCOMPARE(errorSpy[0][2].toInt(), static_cast(QtRestClient::RestReply::Error::Failure)); 170 | QCOMPARE(errorSpy[0][1].toInt(), 404); 171 | } 172 | 173 | QTEST_MAIN(RestBuilderTest) 174 | 175 | #include "tst_restbuilder.moc" 176 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/user.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 0x00 13 | 0x01 14 | 0x02 15 | 0x04 16 | (Person|Sir|Doctor|Professor) 17 | 18 | 19 | -1 20 | Anonymous 21 | User::GenderType::Male 22 | User::Person 23 | 24 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestBuilderTest/user_props.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | testmacro.h 6 | 7 | 8 | 9 | return 42; 10 | 11 | 12 | 13 | 14 | 15 | return d->_test2; 16 | 17 | 18 | d->_test2 = x; 19 | emit not2(d->_test2); 20 | 21 | 22 | d->_test2 = false; 23 | emit not2(false); 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestClientTest/RestClientTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += testlib 4 | QT -= gui 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = tst_restclient 9 | 10 | include(../tests.pri) 11 | 12 | SOURCES += tst_restclient.cpp 13 | 14 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 15 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestClientTest/tst_restclient.cpp: -------------------------------------------------------------------------------- 1 | #include "testlib.h" 2 | 3 | class RestClientTest : public QObject 4 | { 5 | Q_OBJECT 6 | 7 | private Q_SLOTS: 8 | void testBaseUrl_data(); 9 | void testBaseUrl(); 10 | }; 11 | 12 | void RestClientTest::testBaseUrl_data() 13 | { 14 | QTest::addColumn("base"); 15 | QTest::addColumn("version"); 16 | QTest::addColumn("headers"); 17 | QTest::addColumn("params"); 18 | QTest::addColumn>("attributes"); 19 | QTest::addColumn("sslConfig"); 20 | QTest::addColumn("resultUrl"); 21 | 22 | QUrlQuery query; 23 | query.addQueryItem("p1", "baum"); 24 | query.addQueryItem("p2", "42"); 25 | QHash attribs; 26 | attribs.insert(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); 27 | auto config = QSslConfiguration::defaultConfiguration(); 28 | config.setProtocol(QSsl::TlsV1_2); 29 | config.setPeerVerifyMode(QSslSocket::VerifyPeer); 30 | QTest::newRow("general") << QUrl("https://api.example.com/basic/") 31 | << QVersionNumber(4,2,0) 32 | << QtRestClient::HeaderHash({{"Bearer", "Secret"}}) 33 | << query 34 | << attribs 35 | << config 36 | << QUrl("https://api.example.com/basic/v4.2?p1=baum&p2=42"); 37 | } 38 | 39 | void RestClientTest::testBaseUrl() 40 | { 41 | QFETCH(QUrl, base); 42 | QFETCH(QVersionNumber, version); 43 | QFETCH(QtRestClient::HeaderHash, headers); 44 | QFETCH(QUrlQuery, params); 45 | auto attributes = *static_cast *>(QTest::qData("attributes", ::qMetaTypeId >::type>())); 46 | QFETCH(QSslConfiguration, sslConfig); 47 | QFETCH(QUrl, resultUrl); 48 | 49 | QtRestClient::RestClient client; 50 | client.setBaseUrl(base); 51 | client.setApiVersion(version); 52 | client.setGlobalHeaders(headers); 53 | client.setGlobalParameters(params); 54 | client.setRequestAttributes(attributes); 55 | client.setSslConfiguration(sslConfig); 56 | 57 | auto request = client.builder().build(); 58 | 59 | QCOMPARE(request.url(), resultUrl); 60 | for(auto it = headers.constBegin(); it != headers.constEnd(); it++) 61 | QCOMPARE(request.rawHeader(it.key()), it.value()); 62 | for(auto it = attributes.constBegin(); it != attributes.constEnd(); it++) 63 | QCOMPARE(request.attribute(it.key()), it.value()); 64 | QCOMPARE(request.sslConfiguration(), sslConfig); 65 | } 66 | 67 | QTEST_MAIN(RestClientTest) 68 | 69 | #include "tst_restclient.moc" 70 | -------------------------------------------------------------------------------- /tests/auto/restclient/RestReplyTest/RestReplyTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += testlib restclient-private 4 | QT -= gui 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = tst_restreply 9 | 10 | include(../tests.pri) 11 | 12 | SOURCES += tst_restreply.cpp 13 | 14 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 15 | -------------------------------------------------------------------------------- /tests/auto/restclient/restclient.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | android: CONFIG += no_coroutine_tests 4 | contains(QMAKE_COMPILER_DEFINES, _MSC_VER=1900): CONFIG += no_coroutine_tests 5 | 6 | SUBDIRS += \ 7 | testlib \ 8 | PagingModelTest \ 9 | RequestBuilderTest \ 10 | RestClientTest \ 11 | RestReplyTest \ 12 | RestBuilderTest \ 13 | IntegrationTest 14 | 15 | !no_coroutine_tests: SUBDIRS += RestAwaitablesTest 16 | 17 | RequestBuilderTest.depends += testlib 18 | RestClientTest.depends += testlib 19 | RestReplyTest.depends += testlib 20 | IntegrationTest.depends += testlib 21 | RestBuilderTest.depends += testlib 22 | RestAwaitablesTest.depends += testlib 23 | PagingModelTest.depends += testlib 24 | 25 | prepareRecursiveTarget(run-tests) 26 | QMAKE_EXTRA_TARGETS += run-tests 27 | -------------------------------------------------------------------------------- /tests/auto/restclient/testlib/httpserver.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTPSERVER_H 2 | #define HTTPSERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | class HttpServer : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit HttpServer(QObject *parent = nullptr); 16 | explicit HttpServer(quint16 port, QObject *parent = nullptr); 17 | 18 | quint16 port() const; 19 | QUrl url() const; 20 | QUrl url(const QString &subPath) const; 21 | QString generateToken(); 22 | 23 | bool setupRoutes(); 24 | 25 | QCborMap data() const; 26 | void setData(QCborMap data); 27 | void setSubData(const QString &key, QCborMap data); 28 | void setDefaultData(); 29 | void setAdvancedData(); 30 | 31 | private: 32 | QHttpServer *_server; 33 | int _port = -1; 34 | QByteArray _token; 35 | QCborMap _data; 36 | 37 | bool checkAccept(const QHttpServerRequest &request); 38 | QCborMap extract(const QHttpServerRequest &request, bool allowPost); 39 | QHttpServerResponse reply(bool asJson, const QCborValue &value); 40 | }; 41 | 42 | #endif // HTTPSERVER_H 43 | -------------------------------------------------------------------------------- /tests/auto/restclient/testlib/jphpost.cpp: -------------------------------------------------------------------------------- 1 | #include "jphpost.h" 2 | 3 | JphPost::JphPost(QObject *parent) : 4 | QObject{parent} 5 | {} 6 | 7 | JphPost::JphPost(int id, int userId, QString title, QString body, QObject *parent) : 8 | QObject{parent}, 9 | id{id}, 10 | userId{userId}, 11 | title{std::move(title)}, 12 | body{std::move(body)} 13 | {} 14 | 15 | bool JphPost::equals(const JphPost *left, const QObject *right) 16 | { 17 | if (left) { 18 | if (!left->equals(right)) 19 | return false; 20 | } else if (left != right) 21 | return false; 22 | return true; 23 | } 24 | 25 | bool JphPost::equals(const QObject *other) const 26 | { 27 | if (this == other) 28 | return true; 29 | else if (!other) 30 | return false; 31 | else if (metaObject()->className() != other->metaObject()->className()) 32 | return false; 33 | else { 34 | for (auto i = staticMetaObject.propertyOffset(); i < metaObject()->propertyCount(); i++) { 35 | auto property = metaObject()->property(i); 36 | QMetaType t(property.userType()); 37 | if (t.flags().testFlag(QMetaType::PointerToQObject) && 38 | t.metaObject()->inherits(&staticMetaObject)) { 39 | auto c1 = property.read(this).value(); 40 | auto c2 = property.read(other).value(); 41 | if (!equals(c1, c2)) 42 | return false; 43 | } else if (property.read(this) != property.read(other)) 44 | return false; 45 | } 46 | return true; 47 | } 48 | } 49 | 50 | JphPost *JphPost::create(int index, QObject *parent) 51 | { 52 | return new JphPost{ 53 | index, 54 | qCeil(index/2.0), 55 | QStringLiteral("Title%1").arg(index), 56 | QStringLiteral("Body%1").arg(index), 57 | parent 58 | }; 59 | } 60 | 61 | JphPost *JphPost::createDefault(QObject *parent) 62 | { 63 | return create(1, parent); 64 | } 65 | 66 | JphPost *JphPost::createFirst(QObject *parent) 67 | { 68 | return create(0, parent); 69 | } 70 | 71 | 72 | 73 | JphPostSimple::JphPostSimple(QObject *parent) : 74 | Simple{parent} 75 | {} 76 | 77 | JphPostSimple::JphPostSimple(int id, QString title, QUrl href, QObject *parent) : 78 | Simple{parent}, 79 | id{id}, 80 | title{std::move(title)}, 81 | href{std::move(href)} 82 | {} 83 | -------------------------------------------------------------------------------- /tests/auto/restclient/testlib/jphpost.h: -------------------------------------------------------------------------------- 1 | #ifndef JPHPOST_H 2 | #define JPHPOST_H 3 | 4 | #include 5 | 6 | class JphPost : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | Q_PROPERTY(int id MEMBER id) 11 | Q_PROPERTY(int userId MEMBER userId) 12 | Q_PROPERTY(QString title MEMBER title) 13 | Q_PROPERTY(QString body MEMBER body) 14 | 15 | public: 16 | Q_INVOKABLE JphPost(QObject *parent = nullptr); 17 | JphPost(int id, int userId, QString title, QString body, QObject *parent = nullptr); 18 | 19 | static bool equals(const JphPost *left, const QObject *right); 20 | virtual bool equals(const QObject *other) const; 21 | 22 | template 23 | static bool listEquals(const QList &left, const QList &right); 24 | 25 | static JphPost *create(int index, QObject *parent); 26 | static JphPost *createDefault(QObject *parent); 27 | static JphPost *createFirst(QObject *parent); 28 | 29 | int id = 0; 30 | int userId = 0; 31 | QString title; 32 | QString body; 33 | }; 34 | 35 | class JphPostSimple : public QtRestClient::Simple 36 | { 37 | Q_OBJECT 38 | 39 | Q_PROPERTY(int id MEMBER id) 40 | Q_PROPERTY(QString title MEMBER title) 41 | Q_PROPERTY(QUrl href MEMBER href) 42 | 43 | public: 44 | Q_INVOKABLE JphPostSimple(QObject *parent = nullptr); 45 | JphPostSimple(int id, QString title, QUrl href, QObject *parent = nullptr); 46 | 47 | QTRESTCLIENT_EXT_HREF_PROP(href) 48 | 49 | int id = 0; 50 | QString title; 51 | QUrl href; 52 | }; 53 | 54 | template 55 | bool JphPost::listEquals(const QList &left, const QList &right) 56 | { 57 | static_assert(std::is_base_of::value, "T must inherit QObject!"); 58 | if (left.size() != right.size()) 59 | return false; 60 | else { 61 | for (auto i = 0; i < left.size(); i++) { 62 | if (!equals(qobject_cast(left[i]), qobject_cast(right[i]))) 63 | return false; 64 | } 65 | return true; 66 | } 67 | } 68 | 69 | 70 | #endif // JPHPOST_H 71 | -------------------------------------------------------------------------------- /tests/auto/restclient/testlib/testlib.cpp: -------------------------------------------------------------------------------- 1 | #include "testlib.h" 2 | using namespace QtRestClient; 3 | 4 | QtRestClient::RestClient *Testlib::createClient(QObject *parent) 5 | { 6 | auto client = new QtRestClient::RestClient(parent); 7 | client->setModernAttributes(); 8 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 9 | client->addRequestAttribute(QNetworkRequest::HTTP2AllowedAttribute, false); 10 | #else 11 | client->addRequestAttribute(QNetworkRequest::Http2AllowedAttribute, false); 12 | #endif 13 | return client; 14 | } 15 | 16 | void Testlib::setAccept(QNetworkRequest &request, RestClient *client) 17 | { 18 | switch (client->dataMode()) { 19 | case RestClient::DataMode::Cbor: 20 | request.setRawHeader("Accept", "application/cbor"); 21 | break; 22 | case RestClient::DataMode::Json: 23 | request.setRawHeader("Accept", "application/json"); 24 | break; 25 | } 26 | } 27 | 28 | BodyType::BodyType(QCborValue data) : 29 | variant{std::move(data)} 30 | {} 31 | 32 | BodyType::BodyType(QCborMap data) : 33 | variant{std::move(data)} 34 | {} 35 | 36 | BodyType::BodyType(QCborArray data) : 37 | variant{std::move(data)} 38 | {} 39 | 40 | BodyType::BodyType(QJsonValue data) : 41 | variant{std::move(data)} 42 | {} 43 | 44 | BodyType::BodyType(QJsonObject data) : 45 | variant{std::move(data)} 46 | {} 47 | 48 | BodyType::BodyType(QJsonArray data) : 49 | variant{std::move(data)} 50 | {} 51 | 52 | BodyType::BodyType(const QtRestClient::RestReply::DataType &data) : 53 | variant{std::visit(QtRestClient::__private::overload { 54 | [](std::nullopt_t) -> variant { 55 | return QCborValue{}; 56 | }, 57 | [](const auto &vData) -> variant { 58 | return vData; 59 | } 60 | }, data)} 61 | {} 62 | 63 | BodyType BodyType::parse(QNetworkReply *reply) 64 | { 65 | const auto contentType = reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(); 66 | if (contentType == "application/cbor") { 67 | QCborStreamReader reader{reply}; 68 | auto repData = QCborValue::fromCbor(reader); 69 | if (reader.lastError() != QCborError::NoError) 70 | return Testlib::CBody(); 71 | else 72 | return repData; 73 | } else if (contentType == "application/json") { 74 | QJsonParseError error; 75 | auto repData = QJsonDocument::fromJson(reply->readAll(), &error); 76 | if (error.error != QJsonParseError::NoError) 77 | return Testlib::JBody(); 78 | else if (repData.isObject()) 79 | return repData.object(); 80 | else if (repData.isArray()) 81 | return repData.array(); 82 | else 83 | return QJsonValue{QJsonValue::Null}; 84 | } else 85 | return {}; 86 | } 87 | 88 | bool BodyType::isValid() const 89 | { 90 | return visit([](const auto &body) { 91 | return !body.isUndefined(); 92 | }); 93 | } 94 | 95 | QByteArray BodyType::accept() const 96 | { 97 | if (std::holds_alternative(*this)) 98 | return "application/cbor"; 99 | if (std::holds_alternative(*this)) 100 | return "application/json"; 101 | else 102 | Q_UNREACHABLE(); 103 | } 104 | 105 | void BodyType::setAccept(QNetworkRequest &request) const 106 | { 107 | request.setRawHeader("Accept", accept()); 108 | } 109 | 110 | QVariant BodyType::toVariant() const 111 | { 112 | return visit([](const auto &data){ 113 | return data.toVariant(); 114 | }); 115 | } 116 | 117 | bool BodyType::operator==(const BodyType &other) const 118 | { 119 | return static_cast>(*this) == 120 | static_cast>(other); 121 | } 122 | 123 | bool BodyType::operator!=(const BodyType &other) const 124 | { 125 | return static_cast>(*this) != 126 | static_cast>(other); 127 | } 128 | 129 | QDebug operator<<(QDebug debug, const BodyType &data) 130 | { 131 | return data.visit([&](const auto &vData) { 132 | return debug << vData; 133 | }); 134 | } 135 | -------------------------------------------------------------------------------- /tests/auto/restclient/testlib/testlib.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTLIB_H 2 | #define TESTLIB_H 3 | 4 | #include 5 | #include 6 | #include "jphpost.h" 7 | #include "httpserver.h" 8 | 9 | #include 10 | 11 | class BodyType : public std::variant 12 | { 13 | public: 14 | BodyType() = default; 15 | BodyType(const BodyType &other) = default; 16 | BodyType(BodyType &&other) noexcept = default; 17 | BodyType &operator=(const BodyType &other) = default; 18 | BodyType &operator=(BodyType &&other) noexcept = default; 19 | 20 | BodyType(QCborValue data); 21 | BodyType(QCborMap data); 22 | BodyType(QCborArray data); 23 | BodyType(QJsonValue data); 24 | BodyType(QJsonObject data); 25 | BodyType(QJsonArray data); 26 | 27 | explicit BodyType(const QtRestClient::RestReply::DataType &data); 28 | 29 | static BodyType parse(QNetworkReply *reply); 30 | 31 | bool isValid() const; 32 | QByteArray accept() const; 33 | void setAccept(QNetworkRequest &request) const; 34 | QVariant toVariant() const; 35 | 36 | template 37 | inline auto visit(TFn &&fn) { 38 | return std::visit(std::forward(fn), static_cast>(*this)); 39 | } 40 | template 41 | inline auto visit(TFn &&fn) const { 42 | return std::visit(std::forward(fn), static_cast>(*this)); 43 | } 44 | 45 | bool operator==(const BodyType &other) const; 46 | bool operator!=(const BodyType &other) const; 47 | bool operator<(const BodyType &other) const = delete; 48 | }; 49 | 50 | QDebug operator<<(QDebug debug, const BodyType &data); 51 | 52 | class Testlib 53 | { 54 | public: 55 | inline static BodyType CBody(QCborValue value = QCborValue::Undefined) { 56 | return BodyType{std::move(value)}; 57 | } 58 | inline static BodyType CBody(const QJsonValue &value) { 59 | return BodyType{QCborValue::fromJsonValue(value)}; 60 | } 61 | inline static BodyType JBody(QJsonValue value = QJsonValue::Undefined) { 62 | return BodyType{std::move(value)}; 63 | } 64 | inline static BodyType JBody(const QCborValue &value) { 65 | return BodyType{value.toJsonValue()}; 66 | } 67 | 68 | static QtRestClient::RestClient *createClient(QObject *parent = nullptr); 69 | static void setAccept(QNetworkRequest &request, QtRestClient::RestClient *client); 70 | }; 71 | 72 | Q_DECLARE_METATYPE(QUrlQuery) 73 | Q_DECLARE_METATYPE(QNetworkRequest::Attribute) 74 | Q_DECLARE_METATYPE(BodyType) 75 | 76 | #endif // TESTLIB_H 77 | -------------------------------------------------------------------------------- /tests/auto/restclient/testlib/testlib.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | QT += testlib restclient httpserver restclient-private 4 | QT -= gui 5 | 6 | CONFIG += static 7 | 8 | TARGET = testlib 9 | 10 | DEFINES += TESTLIB_LIBRARY 11 | DEFINES += "TESTLIB_SRC_DIR=\\\"$$PWD\\\"" 12 | 13 | HEADERS += \ 14 | testlib.h \ 15 | jphpost.h \ 16 | httpserver.h 17 | 18 | SOURCES += \ 19 | testlib.cpp \ 20 | jphpost.cpp \ 21 | httpserver.cpp 22 | 23 | runtarget.target = run-tests 24 | !compat_test { 25 | win32: runtarget.depends += $(DESTDIR_TARGET) 26 | else: runtarget.depends += $(TARGET) 27 | } 28 | QMAKE_EXTRA_TARGETS += runtarget 29 | -------------------------------------------------------------------------------- /tests/auto/restclient/tests.pri: -------------------------------------------------------------------------------- 1 | QT += core network restclient httpserver 2 | 3 | isEmpty(LIB_PWD): LIB_PWD = $$OUT_PWD/../testlib 4 | 5 | win32:!win32-g++:CONFIG(release, debug|release): LIBS += -L$$LIB_PWD/release/ -ltestlib 6 | else:win32:!win32-g++:CONFIG(debug, debug|release): LIBS += -L$$LIB_PWD/debug/ -ltestlib 7 | else: LIBS += -L$$LIB_PWD/ -ltestlib 8 | 9 | INCLUDEPATH += $$PWD/testlib 10 | DEPENDPATH += $$PWD/testlib 11 | 12 | win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$LIB_PWD/release/testlib.lib 13 | else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$LIB_PWD/debug/testlib.lib 14 | else: PRE_TARGETDEPS += $$LIB_PWD/libtestlib.a 15 | 16 | DEFINES -= QT_ASCII_CAST_WARNINGS 17 | 18 | include($$PWD/../testrun.pri) 19 | -------------------------------------------------------------------------------- /tests/auto/restclientauth/AuthRequestBuilderTest/AuthRequestBuilderTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += testlib restclientauth 4 | QT -= gui 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = tst_authrequestbuilder 9 | 10 | LIB_PWD = $$OUT_PWD/../../restclient/testlib 11 | include(../../restclient/tests.pri) 12 | 13 | SOURCES += \ 14 | tst_authrequestbuilder.cpp 15 | 16 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 17 | -------------------------------------------------------------------------------- /tests/auto/restclientauth/AuthRequestBuilderTest/tst_authrequestbuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "testlib.h" 2 | #include 3 | using namespace QtRestClient; 4 | using namespace QtRestClient::Auth; 5 | 6 | class AuthRequestBuilderTest : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | private Q_SLOTS: 11 | void initTestCase(); 12 | void cleanupTestCase(); 13 | 14 | void testSending_data(); 15 | void testSending(); 16 | 17 | private: 18 | HttpServer *server; 19 | QOAuth2AuthorizationCodeFlow *oAuth; 20 | }; 21 | 22 | void AuthRequestBuilderTest::initTestCase() 23 | { 24 | server = new HttpServer(this); 25 | QVERIFY(server->setupRoutes()); 26 | server->setDefaultData(); 27 | oAuth = new QOAuth2AuthorizationCodeFlow{new QNetworkAccessManager{this}, this}; 28 | oAuth->setToken(server->generateToken()); 29 | } 30 | 31 | void AuthRequestBuilderTest::cleanupTestCase() 32 | { 33 | server->deleteLater(); 34 | server = nullptr; 35 | oAuth->deleteLater(); 36 | oAuth = nullptr; 37 | } 38 | 39 | void AuthRequestBuilderTest::testSending_data() 40 | { 41 | QTest::addColumn("url"); 42 | QTest::addColumn("body"); 43 | QTest::addColumn("verb"); 44 | QTest::addColumn("status"); 45 | QTest::addColumn("error"); 46 | QTest::addColumn("object"); 47 | 48 | QCborMap map; 49 | map[QStringLiteral("id")] = 1; 50 | map[QStringLiteral("userId")] = 1; 51 | map[QStringLiteral("title")] = QStringLiteral("Title1"); 52 | map[QStringLiteral("body")] = QStringLiteral("Body1"); 53 | 54 | QTest::newRow("testDefaultGet.cbor") << server->url("/posts/1") 55 | << Testlib::CBody() 56 | << QByteArray() 57 | << 200 58 | << QNetworkReply::NoError 59 | << Testlib::CBody(map); 60 | QTest::newRow("testDefaultGet.json") << server->url("/posts/1") 61 | << Testlib::JBody() 62 | << QByteArray() 63 | << 200 64 | << QNetworkReply::NoError 65 | << Testlib::JBody(map); 66 | 67 | map[QStringLiteral("title")] = "baum"; 68 | map.remove(QStringLiteral("body")); // TODO workarounb 69 | map[QStringLiteral("body")] = 42; 70 | QTest::newRow("testPut.cbor") << server->url("/posts/1") 71 | << Testlib::CBody(map) 72 | << QByteArray("PUT") 73 | << 200 74 | << QNetworkReply::NoError 75 | << Testlib::CBody(map); 76 | QTest::newRow("testPut.json") << server->url("/posts/1") 77 | << Testlib::JBody(map) 78 | << QByteArray("PUT") 79 | << 200 80 | << QNetworkReply::NoError 81 | << Testlib::JBody(map); 82 | 83 | QTest::newRow("testError.cbor") << server->url("/posts/3434") 84 | << Testlib::CBody() 85 | << QByteArray("GET") 86 | << 404 87 | << QNetworkReply::ContentNotFoundError 88 | << Testlib::CBody(); 89 | QTest::newRow("testError.json") << server->url("/posts/3434") 90 | << Testlib::JBody() 91 | << QByteArray("GET") 92 | << 404 93 | << QNetworkReply::ContentNotFoundError 94 | << Testlib::JBody(); 95 | } 96 | 97 | void AuthRequestBuilderTest::testSending() 98 | { 99 | QFETCH(QUrl, url); 100 | QFETCH(BodyType, body); 101 | QFETCH(QByteArray, verb); 102 | QFETCH(int, status); 103 | QFETCH(QNetworkReply::NetworkError, error); 104 | QFETCH(BodyType, object); 105 | 106 | AuthRequestBuilder builder(url, oAuth); 107 | builder.setAccept(body.accept()); 108 | 109 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 110 | builder.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, false); 111 | #else 112 | builder.setAttribute(QNetworkRequest::Http2AllowedAttribute, false); 113 | #endif 114 | if (!verb.isEmpty()) 115 | builder.setVerb(verb); 116 | if (body.isValid()) { 117 | body.visit([&](const auto &data) { 118 | builder.setBody(data); 119 | }); 120 | } 121 | 122 | auto reply = builder.send(); 123 | QSignalSpy replySpy(reply, &QNetworkReply::finished); 124 | 125 | QVERIFY(replySpy.wait()); 126 | QCOMPARE(reply->error(), error); 127 | QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), status); 128 | 129 | if (error == QNetworkReply::NoError) { 130 | auto repData = BodyType::parse(reply); 131 | QVERIFY(repData.isValid()); 132 | QCOMPARE(repData, object); 133 | } 134 | 135 | reply->deleteLater(); 136 | } 137 | 138 | QTEST_MAIN(AuthRequestBuilderTest) 139 | 140 | #include "tst_authrequestbuilder.moc" 141 | -------------------------------------------------------------------------------- /tests/auto/restclientauth/restclientauth.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | AuthRequestBuilderTest 5 | 6 | prepareRecursiveTarget(run-tests) 7 | QMAKE_EXTRA_TARGETS += run-tests 8 | -------------------------------------------------------------------------------- /tests/auto/testrun.pri: -------------------------------------------------------------------------------- 1 | debug_and_release:!ReleaseBuild:!DebugBuild { 2 | runtarget.target = run-tests 3 | runtarget.CONFIG = recursive 4 | runtarget.recurse_target = run-tests 5 | QMAKE_EXTRA_TARGETS += runtarget 6 | } else { 7 | oneshell.target = .ONESHELL 8 | QMAKE_EXTRA_TARGETS += oneshell 9 | 10 | win32:!win32-g++ { 11 | CONFIG(debug, debug|release): outdir_helper = debug 12 | CONFIG(release, debug|release): outdir_helper = release 13 | runtarget.target = run-tests 14 | !compat_test: runtarget.depends += $(DESTDIR_TARGET) 15 | runtarget.commands += set PATH=$$shell_path($$shadowed($$dirname(_QMAKE_CONF_))/bin);$$shell_path($$[QT_INSTALL_BINS]);$(PATH) 16 | runtarget.commands += $$escape_expand(\\n\\t)set QT_PLUGIN_PATH=$$shadowed($$dirname(_QMAKE_CONF_))/plugins;$$[QT_INSTALL_PLUGINS];$(QT_PLUGIN_PATH) 17 | runtarget.commands += $$escape_expand(\\n\\t)set QML2_IMPORT_PATH=$$shadowed($$dirname(_QMAKE_CONF_))/qml;$$[QT_INSTALL_QML];$(QML2_IMPORT_PATH) 18 | !isEmpty(LOGGING_RULES): runtarget.commands += $$escape_expand(\\n\\t)set \"QT_LOGGING_RULES=$$LOGGING_RULES\" 19 | runtarget.commands += $$escape_expand(\\n\\t)if exist $${outdir_helper}\\fail del $${outdir_helper}\\fail 20 | runtarget.commands += $$escape_expand(\\n\\t)start /w call $(DESTDIR_TARGET) ^> $${outdir_helper}\\test.log ^|^| echo FAIL ^> $${outdir_helper}\\fail ^& exit 0 21 | runtarget.commands += $$escape_expand(\\n\\t)type $${outdir_helper}\\test.log 22 | runtarget.commands += $$escape_expand(\\n\\t)if exist $${outdir_helper}\\fail exit 42 23 | QMAKE_EXTRA_TARGETS += runtarget 24 | } else { 25 | win32-g++: QMAKE_DIRLIST_SEP = ";" 26 | runtarget.commands += export PATH=\"$$shell_path($$shadowed($$dirname(_QMAKE_CONF_))/bin):$$shell_path($$[QT_INSTALL_BINS]):$${LITERAL_DOLLAR}$${LITERAL_DOLLAR}PATH\" 27 | runtarget.commands += $$escape_expand(\\n\\t)export QT_PLUGIN_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/plugins$${QMAKE_DIRLIST_SEP}$$[QT_INSTALL_PLUGINS]$${QMAKE_DIRLIST_SEP}$(QT_PLUGIN_PATH)\" 28 | runtarget.commands += $$escape_expand(\\n\\t)export QML2_IMPORT_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/qml$${QMAKE_DIRLIST_SEP}$$[QT_INSTALL_QML]$${QMAKE_DIRLIST_SEP}$(QML2_IMPORT_PATH)\" 29 | !isEmpty(LOGGING_RULES): runtarget.commands += $$escape_expand(\\n\\t)export QT_LOGGING_RULES=\"$$LOGGING_RULES\" 30 | win32-g++: QMAKE_DIRLIST_SEP = ":" 31 | 32 | linux|win32-g++ { 33 | runtarget.commands += $$escape_expand(\\n\\t)export LD_LIBRARY_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/lib$${QMAKE_DIRLIST_SEP}$$[QT_INSTALL_LIBS]$${QMAKE_DIRLIST_SEP}$(LD_LIBRARY_PATH)\" 34 | runtarget.commands += $$escape_expand(\\n\\t)export QT_QPA_PLATFORM=minimal 35 | } else:mac { 36 | runtarget.commands += $$escape_expand(\\n\\t)export DYLD_LIBRARY_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/lib:$$[QT_INSTALL_LIBS]:$(DYLD_LIBRARY_PATH)\" 37 | runtarget.commands += $$escape_expand(\\n\\t)export DYLD_FRAMEWORK_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/lib:$$[QT_INSTALL_LIBS]:$(DYLD_FRAMEWORK_PATH)\" 38 | } 39 | 40 | runtarget.target = run-tests 41 | win32-g++ { 42 | !compat_test: runtarget.depends += $(DESTDIR_TARGET) 43 | runtarget.commands += $$escape_expand(\\n\\t)./$(DESTDIR_TARGET) 44 | } else { 45 | !compat_test: runtarget.depends += $(TARGET) 46 | runtarget.commands += $$escape_expand(\\n\\t)./$(TARGET) 47 | } 48 | QMAKE_EXTRA_TARGETS += runtarget 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/global/global.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/tests.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | CONFIG += no_docs_target 4 | 5 | SUBDIRS += auto 6 | 7 | prepareRecursiveTarget(run-tests) 8 | QMAKE_EXTRA_TARGETS += run-tests 9 | -------------------------------------------------------------------------------- /tools/qrestbuilder/classbuilder.h: -------------------------------------------------------------------------------- 1 | #ifndef CLASSBUILDER_H 2 | #define CLASSBUILDER_H 3 | 4 | #include "restbuilder.h" 5 | #include 6 | 7 | class ClassBuilder : public RestBuilder 8 | { 9 | public: 10 | ClassBuilder(RestBuilderXmlReader::RestClass restClass); 11 | ClassBuilder(RestBuilderXmlReader::RestApi restApi); 12 | 13 | private: 14 | RestBuilderXmlReader::RestClass classData; 15 | RestBuilderXmlReader::RestApi apiData; 16 | RestBuilderXmlReader::RestAccess &data; 17 | RestBuilderXmlReader::RestAccessElements &elements; 18 | bool isApi; 19 | 20 | void build() override; 21 | 22 | void generateClass(); 23 | void generateApi(); 24 | 25 | void writeClassBeginDeclaration(); 26 | void writeClassMainDeclaration(); 27 | void writeClassBeginDefinition(); 28 | void writeClassMainDefinition(); 29 | 30 | QString writeMethodParams(const RestBuilderXmlReader::Method &method, bool withDefaults); 31 | 32 | void writeFactoryDeclaration(); 33 | void writePrivateDefinitions(); 34 | void writeFactoryDefinition(); 35 | void writeClassDefinitions(); 36 | void writeMethodDefinitions(); 37 | void writeMemberDefinitions(); 38 | void writeStartupCode(); 39 | 40 | void writeLocalApiGeneration(); 41 | void writeGlobalApiGeneration(); 42 | void writeApiCreation(); 43 | 44 | void writeQmlDeclaration(); 45 | void writeQmlDefinitions(); 46 | 47 | void writeMethodPath(const RestBuilderXmlReader::variant &info); 48 | }; 49 | 50 | #endif // CLASSBUILDER_H 51 | -------------------------------------------------------------------------------- /tools/qrestbuilder/main.cpp: -------------------------------------------------------------------------------- 1 | #include "classbuilder.h" 2 | #include "objectbuilder.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "xmlconverter.h" 13 | 14 | //testing 15 | #include 16 | 17 | int main(int argc, char *argv[]) 18 | { 19 | QCoreApplication a(argc, argv); 20 | QCoreApplication::setApplicationName(QStringLiteral(TARGET)); 21 | QCoreApplication::setApplicationVersion(QStringLiteral(VERSION)); 22 | QCoreApplication::setOrganizationName(QStringLiteral(COMPANY)); 23 | QCoreApplication::setOrganizationDomain(QStringLiteral(BUNDLE_PREFIX)); 24 | 25 | QCommandLineParser parser; 26 | parser.setApplicationDescription(QStringLiteral("A tool to create code for a rest API based on an API description")); 27 | parser.addVersionOption(); 28 | parser.addHelpOption(); 29 | 30 | parser.addOption({ 31 | {QStringLiteral("c"), QStringLiteral("convert")}, 32 | QStringLiteral("Convert a legacy json file of to the new XML format. " 33 | "Use --impl to specify the name of the RC-XML file to be created."), 34 | QStringLiteral("type") 35 | }); 36 | parser.addOption({ 37 | QStringLiteral("in"), 38 | QStringLiteral("The input JSON containing the API definition"), 39 | QStringLiteral("file") 40 | }); 41 | parser.addOption({ 42 | QStringLiteral("header"), 43 | QStringLiteral("The of the header file to generate"), 44 | QStringLiteral("name") 45 | }); 46 | parser.addOption({ 47 | QStringLiteral("impl"), 48 | QStringLiteral("The of the implementation file to generate"), 49 | QStringLiteral("name") 50 | }); 51 | 52 | parser.process(a); 53 | 54 | try { 55 | if (parser.isSet(QStringLiteral("convert"))) { 56 | XmlConverter converter; 57 | converter.convert(parser.value(QStringLiteral("convert")), 58 | parser.value(QStringLiteral("in")), 59 | parser.value(QStringLiteral("impl"))); 60 | return EXIT_SUCCESS; 61 | } 62 | 63 | QFile inFile(parser.value(QStringLiteral("in"))); 64 | if(!inFile.open(QIODevice::ReadOnly | QIODevice::Text)) 65 | throw RestBuilderXmlReader::FileException{inFile}; 66 | QXmlStreamReader reader(&inFile); 67 | 68 | RestBuilderXmlReader builderBase; 69 | auto data = builderBase.readDocument(parser.value(QStringLiteral("in"))); 70 | 71 | QScopedPointer builder; 72 | if (nonstd::holds_alternative(data)) 73 | builder.reset(new ObjectBuilder(nonstd::get(data))); 74 | else if (nonstd::holds_alternative(data)) 75 | builder.reset(new ObjectBuilder(nonstd::get(data))); 76 | else if (nonstd::holds_alternative(data)) 77 | builder.reset(new ClassBuilder(nonstd::get(data))); 78 | else if (nonstd::holds_alternative(data)) 79 | builder.reset(new ClassBuilder(nonstd::get(data))); 80 | else 81 | Q_UNREACHABLE(); 82 | 83 | builder->build(parser.value(QStringLiteral("in")), 84 | parser.value(QStringLiteral("header")), 85 | parser.value(QStringLiteral("impl"))); 86 | return EXIT_SUCCESS; 87 | } catch (RestBuilderXmlReader::Exception &e) { 88 | qCritical() << e.what(); 89 | return EXIT_FAILURE; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tools/qrestbuilder/objectbuilder.h: -------------------------------------------------------------------------------- 1 | #ifndef OBJECTBUILDER_H 2 | #define OBJECTBUILDER_H 3 | 4 | #include "restbuilder.h" 5 | 6 | class ObjectBuilder : public RestBuilder 7 | { 8 | public: 9 | ObjectBuilder(RestBuilderXmlReader::RestObject restObject); 10 | ObjectBuilder(RestBuilderXmlReader::RestGadget restGadget); 11 | 12 | private: 13 | struct SudoProperty { 14 | const RestBuilderXmlReader::TypedVariableAttribs &basic; 15 | const RestBuilderXmlReader::PropertyAttribs &attribs; 16 | }; 17 | 18 | bool isObject; 19 | RestBuilderXmlReader::RestContent data; 20 | 21 | void build() override; 22 | 23 | void generateApiObject(); 24 | void generateApiGadget(); 25 | 26 | QString setter(const QString &name); 27 | const RestBuilderXmlReader::TypedVariableAttribs &propertyBasics(const nonstd::variant &prop) const; 28 | const RestBuilderXmlReader::PropertyAttribs &propertyAttribs(const nonstd::variant &prop) const; 29 | SudoProperty sudoProperty(const nonstd::variant &prop) const; 30 | 31 | void writeEnums(); 32 | void writeFlagOperators(); 33 | void writeProperties(); 34 | void writeAggregateConstructorDeclaration(); 35 | void writeReadDeclarations(); 36 | void writeWriteDeclarations(); 37 | void writeResetDeclarations(); 38 | void writeEqualsDeclaration(); 39 | void writeQHashDeclaration(bool asFriend); 40 | void writeSourceIncludes(); 41 | void writeAggregateConstructorDefinition(); 42 | void writeReadDefinitions(); 43 | void writeWriteDefinitions(); 44 | void writeResetDefinitions(); 45 | void writeEqualsDefinition(); 46 | void writeQHashDefinition(); 47 | void writePrivateClass(); 48 | void writeDataClass(); 49 | void writeMemberDefinitions(); 50 | void writeSetupHooks(); 51 | }; 52 | 53 | #endif // OBJECTBUILDER_H 54 | -------------------------------------------------------------------------------- /tools/qrestbuilder/qrestbuilder.pro: -------------------------------------------------------------------------------- 1 | option(host_build) 2 | 3 | QT = core 4 | !force_bootstrap:qtHaveModule(xmlpatterns): QT += xmlpatterns 5 | 6 | TARGET = qrestbuilder 7 | VERSION = $$MODULE_VERSION 8 | COMPANY = Skycoder42 9 | BUNDLE_PREFIX = de.skycoder42 10 | 11 | DEFINES += BUILD_QRESTBUILDER 12 | DEFINES += "TARGET=\\\"$$TARGET\\\"" 13 | DEFINES += "VERSION=\\\"$$VERSION\\\"" 14 | DEFINES += "COMPANY=\\\"$$COMPANY\\\"" 15 | DEFINES += "BUNDLE_PREFIX=\\\"$$BUNDLE_PREFIX\\\"" 16 | 17 | HEADERS += \ 18 | restbuilder.h \ 19 | objectbuilder.h \ 20 | classbuilder.h \ 21 | xmlconverter.h \ 22 | ../../src/3rdparty/optional-lite/optional.hpp \ 23 | ../../src/3rdparty/variant-lite/variant.hpp 24 | 25 | SOURCES += \ 26 | main.cpp \ 27 | restbuilder.cpp \ 28 | objectbuilder.cpp \ 29 | classbuilder.cpp \ 30 | xmlconverter.cpp 31 | 32 | XML_SCHEMA_DEFINITIONS += \ 33 | qrestbuilder.xsd 34 | 35 | contains(QT, xmlpatterns): RESOURCES += qrestbuilder.qrc 36 | 37 | INCLUDEPATH += \ 38 | ../../src/3rdparty/optional-lite \ 39 | ../../src/3rdparty/variant-lite 40 | 41 | load(qt_tool) 42 | 43 | QDEP_DEPENDS += Skycoder42/QXmlCodeGen@1.4.4 44 | 45 | win32 { 46 | QMAKE_TARGET_PRODUCT = "Qt Rest API Builder" 47 | QMAKE_TARGET_COMPANY = $$COMPANY 48 | QMAKE_TARGET_COPYRIGHT = "Felix Barz" 49 | } else:mac { 50 | QMAKE_TARGET_BUNDLE_PREFIX = $${BUNDLE_PREFIX}. 51 | } 52 | 53 | !load(qdep):error("Failed to load qdep feature! Run 'qdep prfgen --qmake $$QMAKE_QMAKE' to create it.") 54 | -------------------------------------------------------------------------------- /tools/qrestbuilder/qrestbuilder.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qrestbuilder.xsd 4 | 5 | 6 | -------------------------------------------------------------------------------- /tools/qrestbuilder/restbuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "restbuilder.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | RestBuilder::~RestBuilder() = default; 12 | 13 | void RestBuilder::build(const QString &in, const QString &hOut, const QString &cppOut) 14 | { 15 | fileName = QFileInfo{in}.baseName(); 16 | 17 | QSaveFile headerFile(hOut); 18 | if(!headerFile.open(QIODevice::WriteOnly | QIODevice::Text)) 19 | throw RestBuilderXmlReader::FileException(headerFile); 20 | header.setDevice(&headerFile); 21 | 22 | QSaveFile sourceFile(cppOut); 23 | if(!sourceFile.open(QIODevice::WriteOnly | QIODevice::Text)) 24 | throw RestBuilderXmlReader::FileException(sourceFile); 25 | source.setDevice(&sourceFile); 26 | 27 | try { 28 | writeIncGuardBegin(); 29 | build(); 30 | writeIncGuardEnd(); 31 | 32 | header.flush(); 33 | if(!headerFile.commit()) 34 | throw RestBuilderXmlReader::FileException(headerFile); 35 | source.flush(); 36 | if(!sourceFile.commit()) 37 | throw RestBuilderXmlReader::FileException(sourceFile); 38 | } catch(...) { 39 | headerFile.cancelWriting(); 40 | sourceFile.cancelWriting(); 41 | throw; 42 | } 43 | } 44 | 45 | QString RestBuilder::exportedName(const QString &name, const RestBuilderXmlReader::optional &exportKey) const 46 | { 47 | if(exportKey) 48 | return exportKey.value() + QLatin1Char(' ') + name; 49 | else 50 | return name; 51 | } 52 | 53 | QString RestBuilder::nsName(const QString &name, const RestBuilderXmlReader::optional &nspace) const 54 | { 55 | if(nspace) 56 | return nspace.value() + QStringLiteral("::") + name; 57 | else 58 | return name; 59 | } 60 | 61 | QString RestBuilder::nsInject(const QString &name, const QString &prefix) const 62 | { 63 | auto nList = name.split(QStringLiteral("::")); 64 | nList.last().prepend(prefix); 65 | return nList.join(QStringLiteral("::")); 66 | } 67 | 68 | QString RestBuilder::boolValue(bool value) const 69 | { 70 | return value ? QStringLiteral("true") : QStringLiteral("false"); 71 | } 72 | 73 | void RestBuilder::writeIncludes(const QList &includes) 74 | { 75 | for(const auto &inc : includes) { 76 | if(inc.local) 77 | header << "#include \"" << inc.include << "\"\n"; 78 | else 79 | header << "#include <" << inc.include << ">\n"; 80 | } 81 | header << "\n"; 82 | } 83 | 84 | QString RestBuilder::writeParamDefault(const RestBuilderXmlReader::BaseParam ¶m) 85 | { 86 | if(param.asStr) { 87 | if(param.type == QStringLiteral("QString")) 88 | return QStringLiteral("QStringLiteral(\"") + param.defaultValue + QStringLiteral("\")"); 89 | else if(param.type == QStringLiteral("QByteArray")) 90 | return QStringLiteral("QByteArrayLiteral(\"") + param.defaultValue + QStringLiteral("\")"); 91 | else 92 | return QStringLiteral("QVariant(QStringLiteral(\"") + param.defaultValue + QStringLiteral("\")).value<") + param.type + QStringLiteral(">()"); 93 | } else 94 | return param.defaultValue; 95 | } 96 | 97 | QString RestBuilder::writeParamArg(const RestBuilderXmlReader::BaseParam ¶m, bool withDefault) 98 | { 99 | QString res = QStringLiteral("const ") + param.type + QStringLiteral(" &") + param.key; 100 | if(withDefault && !param.defaultValue.isEmpty()) 101 | res += QStringLiteral(" = ") + writeParamDefault(param); 102 | return res; 103 | } 104 | 105 | QString RestBuilder::writeExpression(const RestBuilderXmlReader::Expression &expression, bool asString) 106 | { 107 | if (expression.expr) 108 | return expression.value; 109 | else if (asString) 110 | return QStringLiteral("QStringLiteral(\"") + expression.value + QStringLiteral("\")"); 111 | else 112 | return QLatin1Char('"') + expression.value + QLatin1Char('"'); 113 | } 114 | 115 | void RestBuilder::writeIncGuardBegin() 116 | { 117 | QString guard = fileName.toUpper() + QStringLiteral("_H"); 118 | header << "#ifndef " << guard << '\n' 119 | << "#define " << guard << "\n\n"; 120 | } 121 | 122 | void RestBuilder::writeIncGuardEnd() 123 | { 124 | QString guard = fileName.toUpper() + QStringLiteral("_H"); 125 | header << "#endif //" << guard << '\n'; 126 | } 127 | 128 | 129 | 130 | RestBuilder::GeneralException::GeneralException(QString message) : 131 | _msg{std::move(message)} 132 | {} 133 | 134 | RestBuilder::GeneralException::GeneralException(const QByteArray &message) : 135 | GeneralException{QString::fromUtf8(message)} 136 | {} 137 | 138 | RestBuilder::GeneralException::GeneralException(const char *message) : 139 | GeneralException{QString::fromUtf8(message)} 140 | {} 141 | 142 | QString RestBuilder::GeneralException::createQWhat() const 143 | { 144 | return _msg; 145 | } 146 | -------------------------------------------------------------------------------- /tools/qrestbuilder/restbuilder.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTBUILDER_H 2 | #define RESTBUILDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class RestBuilder 13 | { 14 | public: 15 | class GeneralException : public RestBuilderXmlReader::Exception 16 | { 17 | public: 18 | GeneralException(QString message); 19 | GeneralException(const QByteArray &message); 20 | GeneralException(const char *message); 21 | 22 | protected: 23 | QString createQWhat() const override; 24 | 25 | private: 26 | QString _msg; 27 | }; 28 | 29 | virtual ~RestBuilder(); 30 | 31 | void build(const QString &in, const QString &hOut, const QString &cppOut); 32 | 33 | protected: 34 | virtual void build() = 0; 35 | 36 | QString exportedName(const QString &name, const RestBuilderXmlReader::optional &exportKey) const; 37 | QString nsName(const QString &name, const RestBuilderXmlReader::optional &nspace) const; 38 | QString nsInject(const QString &name, const QString &prefix) const; 39 | QString boolValue(bool value) const; 40 | 41 | void writeIncludes(const QList &includes = {}); 42 | QString writeParamDefault(const RestBuilderXmlReader::BaseParam ¶m); 43 | QString writeParamArg(const RestBuilderXmlReader::BaseParam ¶m, bool withDefault); 44 | QString writeExpression(const RestBuilderXmlReader::Expression &expression, bool asString); 45 | 46 | QString fileName; 47 | QTextStream header; 48 | QTextStream source; 49 | 50 | private: 51 | void writeIncGuardBegin(); 52 | void writeIncGuardEnd(); 53 | }; 54 | 55 | #endif // RESTBUILDER_H 56 | -------------------------------------------------------------------------------- /tools/qrestbuilder/xmlconverter.h: -------------------------------------------------------------------------------- 1 | #ifndef XMLCONVERTER_H 2 | #define XMLCONVERTER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class XmlConverter 9 | { 10 | public: 11 | void convert(const QString &type, const QString &in, const QString &out); 12 | 13 | private: 14 | void writeObjectXml(const QJsonObject &data, QXmlStreamWriter &writer); 15 | void writeClassXml(const QJsonObject &data, QXmlStreamWriter &writer); 16 | 17 | void writeIncludes(const QJsonArray &includes, QXmlStreamWriter &writer); 18 | 19 | void writeAttrIf(const QJsonObject &data, QXmlStreamWriter &writer, const QString &baseKey, QString newKey = {}); 20 | void writeAttrIfAny(const QJsonObject &data, QXmlStreamWriter &writer, const QString &baseKey); 21 | 22 | void writeExpr(const QJsonObject &data, QXmlStreamWriter &writer, const QString &baseKey, QString newKey = {}, const std::function &attrFn = {}); 23 | void writeFixParams(const QJsonObject &data, QXmlStreamWriter &writer, const QString &baseKey, const QString &newKey); 24 | void writeParamList(const QJsonArray ¶ms, QXmlStreamWriter &writer, const QString &newKey); 25 | }; 26 | 27 | #endif // XMLCONVERTER_H 28 | -------------------------------------------------------------------------------- /tools/tools.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | !no_json_serializer: SUBDIRS += qrestbuilder 4 | 5 | QMAKE_EXTRA_TARGETS += run-tests 6 | --------------------------------------------------------------------------------