Split the lib in smaller libs to ease unit tests

This commit is contained in:
emeric
2020-02-13 18:04:35 +01:00
parent 1e2c1caeed
commit 15e53caa2d
131 changed files with 382 additions and 138 deletions
+89
View File
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2019 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SubsonicId.hpp"
#include "SubsonicResponse.hpp"
#include "utils/Logger.hpp"
#include "utils/String.hpp"
namespace API::Subsonic
{
std::optional<Id>
IdFromString(const std::string& id)
{
if (id == "root")
return Id {Id::Type::Root};
std::vector<std::string> values {StringUtils::splitString(id, "-")};
if (values.size() != 2)
return std::nullopt;
Id res;
const std::string type {std::move(values[0])};
if (type == "ar")
res.type = Id::Type::Artist;
else if (type == "al")
res.type = Id::Type::Release;
else if (type == "tr")
res.type = Id::Type::Track;
else if (type == "pl")
res.type = Id::Type::Playlist;
else
return std::nullopt;
auto optId {StringUtils::readAs<Database::IdType>(values[1])};
if (!optId)
return std::nullopt;
res.value = *optId;
return res;
}
std::string
IdToString(const Id& id)
{
std::string res;
switch (id.type)
{
case Id::Type::Root:
return "root";
case Id::Type::Artist:
res = "ar-";
break;
case Id::Type::Release:
res = "al-";
break;
case Id::Type::Track:
res = "tr-";
break;
case Id::Type::Playlist:
res = "pl-";
break;
}
return res + std::to_string(id.value);
}
} // namespace API::Subsonic
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2019 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <optional>
#include "database/Types.hpp"
namespace API::Subsonic
{
struct Id
{
enum class Type
{
Root, // Where all artists artistless albums reside
Track,
Release,
Artist,
Playlist,
};
Type type;
Database::IdType value {};
};
std::optional<Id> IdFromString(const std::string& id);
std::string IdToString(const Id& id);
} // namespace API::Subsonic
File diff suppressed because it is too large Load Diff
+237
View File
@@ -0,0 +1,237 @@
/*
* Copyright (C) 2019 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SubsonicResponse.hpp"
#include <Wt/Json/Array.h>
#include <Wt/Json/Object.h>
#include <Wt/Json/Value.h>
#include <Wt/Json/Serializer.h>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include "utils/Exception.hpp"
namespace API::Subsonic
{
std::string
ResponseFormatToMimeType(ResponseFormat format)
{
switch (format)
{
case ResponseFormat::xml: return "text/xml";
case ResponseFormat::json: return "application/json";
}
return "";
}
void
Response::Node::setValue(std::string_view value)
{
if (!_children.empty() || !_childrenArrays.empty())
throw LmsException {"Node already has children"};
_value = value;
}
void
Response::Node::setAttribute(std::string_view key, std::string_view value)
{
_attributes[std::string {key}] = value;
}
void
Response::Node::addChild(const std::string& key, Node node)
{
if (!_value.empty())
throw LmsException {"Node already has a value"};
_children[key].emplace_back(std::move(node));
}
void
Response::Node::addArrayChild(const std::string& key, Node node)
{
if (!_value.empty())
throw LmsException {"Node already has a value"};
_childrenArrays[key].emplace_back(std::move(node));
}
Response::Node&
Response::Node::createChild(const std::string& key)
{
_children[key].emplace_back();
return _children[key].back();
}
Response::Node&
Response::Node::createArrayChild(const std::string& key)
{
_childrenArrays[key].emplace_back();
return _childrenArrays[key].back();
}
Response
Response::createOkResponse()
{
Response response;
Node& responseNode {response._root.createChild("subsonic-response")};
responseNode.setAttribute("status", "ok");
responseNode.setAttribute("version", API_VERSION_STR);
return response;
}
Response
Response::createFailedResponse(const Error& error)
{
Response response;
Node& responseNode {response._root.createChild("subsonic-response")};
responseNode.setAttribute("status", "failed");
responseNode.setAttribute("version", API_VERSION_STR);
Node& errorNode {responseNode.createChild("error")};
errorNode.setAttribute("code", std::to_string(static_cast<int>(error.getCode())));
errorNode.setAttribute("message", error.getMessage());
return response;
}
void
Response::addNode(const std::string& key, Node node)
{
return _root._children["subsonic-response"].front().addChild(key, std::move(node));
}
Response::Node&
Response::createNode(const std::string& key)
{
return _root._children["subsonic-response"].front().createChild(key);
}
Response::Node&
Response::createArrayNode(const std::string& key)
{
return _root._children["subsonic-response"].front().createArrayChild(key);
}
void
Response::write(std::ostream& os, ResponseFormat format)
{
switch (format)
{
case ResponseFormat::xml:
writeXML(os);
break;
case ResponseFormat::json:
writeJSON(os);
break;
}
}
void
Response::writeXML(std::ostream& os)
{
std::function<boost::property_tree::ptree(const Response::Node&)> nodeToPropertyTree = [&] (const Response::Node& node)
{
boost::property_tree::ptree res;
for (auto itAttribute : node._attributes)
res.put("<xmlattr>." + itAttribute.first, itAttribute.second);
if (!node._value.empty())
{
res.put_value(node._value);
}
else
{
for (auto itChildNode : node._children)
{
for (const Response::Node& childNode : itChildNode.second)
res.add_child(itChildNode.first, nodeToPropertyTree(childNode));
}
for (auto itChildArrayNode : node._childrenArrays)
{
const std::vector<Response::Node>& childArrayNodes {itChildArrayNode .second};
for (const Response::Node& childNode : childArrayNodes )
res.add_child(itChildArrayNode.first, nodeToPropertyTree(childNode));
}
}
return res;
};
boost::property_tree::ptree root {nodeToPropertyTree(_root)};
boost::property_tree::write_xml(os, root);
}
void
Response::writeJSON(std::ostream& os)
{
namespace Json = Wt::Json;
std::function<Json::Object(const Response::Node&)> nodeToJsonObject = [&] (const Response::Node& node)
{
Json::Object res;
for (auto itAttribute : node._attributes)
res[itAttribute.first] = Json::Value {itAttribute.second};
if (!node._value.empty())
{
res["value"] = Json::Value {node._value};
}
else
{
for (auto itChildNode : node._children)
{
for (const Response::Node& childNode : itChildNode.second)
res[itChildNode.first] = nodeToJsonObject(childNode);
}
for (auto itChildArrayNode : node._childrenArrays)
{
const std::vector<Response::Node>& childArrayNodes {itChildArrayNode .second};
Json::Array array;
for (const Response::Node& childNode : childArrayNodes )
array.emplace_back(nodeToJsonObject(childNode));
res[itChildArrayNode.first] = std::move(array);
}
}
return res;
};
Json::Object root {nodeToJsonObject(_root)};
os << Json::serialize(root);
}
} // namespace
+224
View File
@@ -0,0 +1,224 @@
/*
* Copyright (C) 2019 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <map>
#include <string>
#include <string_view>
#include <vector>
namespace API::Subsonic
{
#define API_VERSION_MAJOR 1
#define API_VERSION_MINOR 12
#define API_VERSION_PATCH 0
#define API_VERSION_STR "1.12.0"
enum class ResponseFormat
{
xml,
json,
};
std::string ResponseFormatToMimeType(ResponseFormat format);
class Error
{
public:
enum class Code
{
Generic = 0,
RequiredParameterMissing = 10,
ClientMustUpgrade = 20,
ServerMustUpgrade = 30,
WrongUsernameOrPassword = 40,
UserNotAuthorized = 50,
RequestedDataNotFound = 70,
};
Error(Code code) : _code {code} {}
virtual std::string getMessage() const = 0;
Code getCode() const { return _code; }
private:
const Code _code;
};
class GenericError : public Error
{
public:
GenericError() : Error {Code::Generic} {}
};
class RequiredParameterMissingError : public Error
{
public:
RequiredParameterMissingError() : Error {Code::RequiredParameterMissing} {}
private:
std::string getMessage() const override { return "Required parameter is missing."; }
};
class ClientMustUpgradeError : public Error
{
public:
ClientMustUpgradeError() : Error {Code::ClientMustUpgrade} {}
private:
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; }
};
class ServerMustUpgradeError : public Error
{
public:
ServerMustUpgradeError() : Error {Code::ServerMustUpgrade} {}
private:
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; }
};
class WrongUsernameOrPasswordError : public Error
{
public:
WrongUsernameOrPasswordError() : Error {Code::WrongUsernameOrPassword} {}
private:
std::string getMessage() const override { return "Wrong username or password."; }
};
class UserNotAuthorizedError : public Error
{
public:
UserNotAuthorizedError () : Error {Code::UserNotAuthorized} {}
private:
std::string getMessage() const override { return "User is not authorized for the given operation."; }
};
class RequestedDataNotFoundError : public Error
{
public:
RequestedDataNotFoundError() : Error {Code::RequestedDataNotFound} {}
private:
std::string getMessage() const override { return "The requested data was not found."; }
};
class InternalErrorGenericError : public GenericError
{
public:
InternalErrorGenericError(const std::string& message) : _message {message} {}
private:
std::string getMessage() const override { return "Internal error: " + _message; }
const std::string _message;
};
class LoginThrottledGenericError : public GenericError
{
std::string getMessage() const override { return "Login throttled, too many attempts"; }
};
class NotImplementedGenericError : public GenericError
{
std::string getMessage() const override { return "Not implemented"; }
};
class UnknownEntryPointGenericError : public GenericError
{
std::string getMessage() const override { return "Unknown API method"; }
};
class PasswordTooWeakGenericError : public GenericError
{
std::string getMessage() const override { return "Password too weak"; }
};
class UserAlreadyExistsGenericError : public GenericError
{
std::string getMessage() const override { return "User already exists"; }
};
class BadParameterGenericError : public GenericError
{
public:
BadParameterGenericError(const std::string& parameterName) : _parameterName {parameterName} {}
private:
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; }
const std::string _parameterName;
};
class BadParameterFormatGenericError : public GenericError
{
public:
BadParameterFormatGenericError(const std::string& parameterName) : _parameterName {parameterName} {}
private:
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad format"; }
const std::string _parameterName;
};
class Response
{
public:
class Node
{
public:
void setAttribute(std::string_view key, std::string_view value);
// A Node has either a value or some children
void setValue(std::string_view value);
Node& createChild(const std::string& key);
Node& createArrayChild(const std::string& key);
void addChild(const std::string& key, Node node);
void addArrayChild(const std::string& key, Node node);
private:
friend class Response;
std::map<std::string, std::string> _attributes;
std::string _value;
std::map<std::string, std::vector<Node>> _children;
std::map<std::string, std::vector<Node>> _childrenArrays;
};
static Response createOkResponse();
static Response createFailedResponse(const Error& error);
virtual ~Response() {}
Response(const Response&) = delete;
Response& operator=(const Response&) = delete;
Response(Response&&) = default;
Response& operator=(Response&&) = default;
void addNode(const std::string& key, Node node);
Node& createNode(const std::string& key);
Node& createArrayNode(const std::string& key);
void write(std::ostream& os, ResponseFormat format);
private:
void writeJSON(std::ostream& os);
void writeXML(std::ostream& os);
Response() = default;
Node _root;
};
} // namespace