From 178c4000676795bb92a7925a4233f5418b2ac917 Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 13 Aug 2019 13:58:59 +0200 Subject: [PATCH] Subsonic API: better error reporting --- src/api/subsonic/SubsonicId.cpp | 21 ++-- src/api/subsonic/SubsonicResource.cpp | 173 +++++++++++++------------- src/api/subsonic/SubsonicResponse.cpp | 62 --------- src/api/subsonic/SubsonicResponse.hpp | 123 +++++++++++++++--- 4 files changed, 202 insertions(+), 177 deletions(-) diff --git a/src/api/subsonic/SubsonicId.cpp b/src/api/subsonic/SubsonicId.cpp index 46253749..44e8a696 100644 --- a/src/api/subsonic/SubsonicId.cpp +++ b/src/api/subsonic/SubsonicId.cpp @@ -19,6 +19,8 @@ #include "SubsonicId.hpp" +#include "SubsonicResponse.hpp" + #include "utils/Logger.hpp" #include "utils/Utils.hpp" @@ -29,18 +31,15 @@ boost::optional IdFromString(const std::string& id) { if (id == "root") - return Id{Id::Type::Root}; + return Id {Id::Type::Root}; std::vector values {splitString(id, "-")}; if (values.size() != 2) - { - LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format"; - return {}; - } + return boost::none; Id res; - std::string type {std::move(values[0])}; + const std::string type {std::move(values[0])}; if (type == "ar") res.type = Id::Type::Artist; else if (type == "al") @@ -50,17 +49,11 @@ IdFromString(const std::string& id) else if (type == "pl") res.type = Id::Type::Playlist; else - { - LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format"; - return {}; - } + return boost::none; auto optId {readAs(values[1])}; if (!optId) - { - LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format"; - return {}; - } + return boost::none; res.value = *optId; diff --git a/src/api/subsonic/SubsonicResource.cpp b/src/api/subsonic/SubsonicResource.cpp index aab24c7f..a9437cac 100644 --- a/src/api/subsonic/SubsonicResource.cpp +++ b/src/api/subsonic/SubsonicResource.cpp @@ -187,14 +187,11 @@ getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::stri for (const std::string& param : it->second) { - auto val {readAs(param)}; - if (!val) - { - res.clear(); - return res; - } + auto value {readAs(param)}; + if (!value) + throw BadParameterFormatGenericError {paramName}; - res.emplace_back(std::move(*val)); + res.emplace_back(std::move(*value)); } return res; @@ -206,7 +203,7 @@ getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const { std::vector res {getMultiParametersAs(parameterMap, param)}; if (res.empty()) - throw Error {Error::Code::RequiredParameterMissing}; + throw RequiredParameterMissingError {}; return res; } @@ -229,7 +226,7 @@ getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::s { auto res {getParameterAs(parameterMap, param)}; if (!res) - throw Error {Error::Code::RequiredParameterMissing}; + throw RequiredParameterMissingError {}; return *res; } @@ -242,7 +239,7 @@ decodePasswordIfNeeded(const std::string& password) { auto decodedPassword {stringFromHex(password.substr(4))}; if (!decodedPassword) - throw Error {Error::CustomType::BadPasswordFormat}; + return password; // fallback on plain password return *decodedPassword; } @@ -259,11 +256,11 @@ getClientInfo(const Wt::Http::ParameterMap& parameters) // Mandatory parameters res.version = getMandatoryParameterAs(parameters, "v"); if (res.version.major > API_VERSION_MAJOR) - throw Error {Error::Code::ServerMustUpgrade}; + throw ServerMustUpgradeError {}; if (res.version.major < API_VERSION_MAJOR) - throw Error {Error::Code::ClientMustUpgrade}; + throw ClientMustUpgradeError {}; if (res.version.minor > API_VERSION_MINOR) - throw Error {Error::Code::ServerMustUpgrade}; + throw ServerMustUpgradeError {}; res.name = getMandatoryParameterAs(parameters, "c"); res.user = getMandatoryParameterAs(parameters, "u"); @@ -332,10 +329,10 @@ checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username) { User::pointer currentUser {User::getByLoginName(context.dbSession, context.userName)}; if (!currentUser) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; if (!currentUser->isAdmin()) - throw Error {Error::Code::UserNotAuthorized}; + throw UserNotAuthorizedError {}; } } @@ -587,7 +584,7 @@ handleChangePassword(RequestContext& context) std::string password {decodePasswordIfNeeded(getMandatoryParameterAs(context.parameters, "password"))}; if (!getService()->evaluatePasswordStrength(username, password)) - throw Error {Error::CustomType::PasswordTooWeak}; + throw PasswordTooWeakGenericError {}; const User::PasswordHash hash {getService()->hashPassword(password)}; @@ -597,7 +594,7 @@ handleChangePassword(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, username)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; user.modify()->setPasswordHash(hash); user.modify()->clearAuthTokens(); @@ -612,22 +609,22 @@ handleCreatePlaylistRequest(RequestContext& context) // Optional params auto id {getParameterAs(context.parameters, "playlistId")}; if (id && id->type != Id::Type::Playlist) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"playlistId"}; auto name {getParameterAs(context.parameters, "name")}; std::vector trackIds {getMultiParametersAs(context.parameters, "songId")}; if (!std::all_of(std::cbegin(trackIds), std::cend(trackIds ), [](const Id& id) { return id.type == Id::Type::Track; })) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"songId"}; if (!name && !id) - throw Error {Error::Code::RequiredParameterMissing}; + throw RequiredParameterMissingError {}; auto transaction {context.dbSession.createUniqueTransaction()}; User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; TrackList::pointer tracklist; if (id) @@ -637,7 +634,7 @@ handleCreatePlaylistRequest(RequestContext& context) || tracklist->getUser() != user || tracklist->getType() != TrackList::Type::Playlist) { - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; } if (name) @@ -669,14 +666,14 @@ handleCreateUserRequest(RequestContext& context) // Just ignore all the other fields as we don't handle them if (!getService()->evaluatePasswordStrength(username, password)) - throw Error {Error::CustomType::PasswordTooWeak}; + throw PasswordTooWeakGenericError {}; const User::PasswordHash hash {getService()->hashPassword(password)}; auto transaction {context.dbSession.createUniqueTransaction()}; if (User::getByLoginName(context.dbSession, username) != User::pointer{}) - throw Error {Error::CustomType::UserAlreadyExists}; + throw UserAlreadyExistsGenericError {}; User::pointer user {User::create(context.dbSession, username, hash)}; user.modify()->setMaxAudioTranscodeBitrate(128000); @@ -690,20 +687,20 @@ handleDeletePlaylistRequest(RequestContext& context) { Id id {getMandatoryParameterAs(context.parameters, "id")}; if (id.type != Id::Type::Playlist) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; auto transaction {context.dbSession.createUniqueTransaction()}; User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)}; if (!tracklist || tracklist->getUser() != user || tracklist->getType() != TrackList::Type::Playlist) { - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; } tracklist.remove(); @@ -717,14 +714,15 @@ handleDeleteUserRequest(RequestContext& context) { std::string username {getMandatoryParameterAs(context.parameters, "username")}; + // cannot delete ourself if (username == context.userName) - throw Error {Error::Code::UserNotAuthorized}; + throw UserNotAuthorizedError {}; auto transaction {context.dbSession.createUniqueTransaction()}; User::pointer user {User::getByLoginName(context.dbSession, username)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; user.remove(); @@ -757,7 +755,7 @@ handleGetRandomSongsRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; auto tracks {Track::getAllRandom(context.dbSession, size)}; @@ -787,7 +785,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; if (type == "random") { @@ -835,7 +833,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3) } } else - throw Error {Error::CustomType::NotImplemented}; + throw NotImplementedGenericError {}; Response response {Response::createOkResponse()}; Response::Node& albumListNode {response.createNode(id3 ? "albumList2" : "albumList")}; @@ -868,17 +866,17 @@ handleGetAlbumRequest(RequestContext& context) Id id {getMandatoryParameterAs(context.parameters, "id")}; if (id.type != Id::Type::Release) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; auto transaction {context.dbSession.createSharedTransaction()}; Release::pointer release {Release::getById(context.dbSession, id.value)}; if (!release) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; Response response {Response::createOkResponse()}; Response::Node releaseNode {releaseToResponseNode(release, context.dbSession, user, true /* id3 */)}; @@ -900,15 +898,17 @@ handleGetArtistRequest(RequestContext& context) Id id {getMandatoryParameterAs(context.parameters, "id")}; if (id.type != Id::Type::Artist) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; auto transaction {context.dbSession.createSharedTransaction()}; Artist::pointer artist {Artist::getById(context.dbSession, id.value)}; - User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; + if (!artist) + throw RequestedDataNotFoundError {}; - if (!artist || !user) - throw Error {Error::Code::RequestedDataNotFound}; + User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; + if (!user) + throw UserNotAuthorizedError {}; Response response {Response::createOkResponse()}; Response::Node artistNode {artistToResponseNode(user, artist, true /* id3 */)}; @@ -929,7 +929,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3) // Mandatory params Id id {getMandatoryParameterAs(context.parameters, "id")}; if (id.type != Id::Type::Artist) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; // Optional params std::size_t count {getParameterAs(context.parameters, "count").get_value_or(20)}; @@ -942,7 +942,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3) Artist::pointer artist {Artist::getById(context.dbSession, id.value)}; if (!artist) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; if (!artist->getMBID().empty()) artistInfoNode.createChild("musicBrainzId").setValue(artist->getMBID()); @@ -955,7 +955,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; for ( const auto& similarArtistId : similarArtistsId ) { @@ -996,7 +996,7 @@ handleGetArtistsRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; auto artists {Artist::getAll(context.dbSession)}; for (const Artist::pointer& artist : artists) @@ -1021,7 +1021,7 @@ handleGetMusicDirectoryRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; switch (id.type) { @@ -1040,7 +1040,7 @@ handleGetMusicDirectoryRequest(RequestContext& context) { auto artist {Artist::getById(context.dbSession, id.value)}; if (!artist) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; directoryNode.setAttribute("name", makeNameFilesystemCompatible(artist->getName())); @@ -1055,7 +1055,7 @@ handleGetMusicDirectoryRequest(RequestContext& context) { auto release {Release::getById(context.dbSession, id.value)}; if (!release) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; directoryNode.setAttribute("name", makeNameFilesystemCompatible(release->getName())); @@ -1067,7 +1067,7 @@ handleGetMusicDirectoryRequest(RequestContext& context) } default: - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; } return response; @@ -1123,7 +1123,7 @@ handleGetIndexesRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; auto artists {Artist::getAll(context.dbSession)}; for (const Artist::pointer& artist : artists) @@ -1139,7 +1139,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3) // Mandatory params Id id {getMandatoryParameterAs(context.parameters, "id")}; if (id.type != Id::Type::Artist) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; // Optional params std::size_t count {getParameterAs(context.parameters, "count").get_value_or(50)}; @@ -1149,10 +1149,12 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3) auto transaction {context.dbSession.createSharedTransaction()}; Artist::pointer artist {Artist::getById(context.dbSession, id.value)}; - User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; + if (!artist) + throw RequestedDataNotFoundError {}; - if (!user || !artist) - throw Error {Error::Code::RequestedDataNotFound}; + User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; + if (!user) + throw UserNotAuthorizedError {}; // "Returns a random collection of songs from the given artist and similar artists" auto tracks {artist->getRandomTracks(count / 2)}; @@ -1203,7 +1205,7 @@ handleGetStarredRequestCommon(RequestContext& context, bool id3) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; Response response {Response::createOkResponse()}; Response::Node& starredNode {response.createNode(id3 ? "starred2" : "starred")}; @@ -1268,14 +1270,17 @@ handleGetPlaylistRequest(RequestContext& context) // Mandatory params Id id {getMandatoryParameterAs(context.parameters, "id")}; if (id.type != Id::Type::Playlist) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; auto transaction {context.dbSession.createSharedTransaction()}; User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; + if (!user) + throw UserNotAuthorizedError {}; + TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)}; - if (!user || !tracklist) - throw Error {Error::Code::RequestedDataNotFound}; + if (!tracklist) + throw RequestedDataNotFoundError {}; Response response {Response::createOkResponse()}; Response::Node playlistNode {tracklistToResponseNode(tracklist, context.dbSession)}; @@ -1297,7 +1302,7 @@ handleGetPlaylistsRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; Response response {Response::createOkResponse()}; Response::Node& playlistsNode {response.createNode("playlists")}; @@ -1326,15 +1331,15 @@ handleGetSongsByGenreRequest(RequestContext& context) auto clusterType {ClusterType::getByName(context.dbSession, genreClusterName)}; if (!clusterType) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; auto cluster {clusterType->getCluster(genre)}; if (!cluster) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; Response response {Response::createOkResponse()}; Response::Node& songsByGenreNode {response.createNode("songsByGenre")}; @@ -1359,7 +1364,7 @@ handleGetUserRequest(RequestContext& context) const User::pointer user {User::getByLoginName(context.dbSession, username)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; Response response {Response::createOkResponse()}; response.addNode("user", userToResponseNode(user)); @@ -1404,7 +1409,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; Response response {Response::createOkResponse()}; Response::Node& searchResult2Node {response.createNode(id3 ? "searchResult3" : "searchResult2")}; @@ -1449,10 +1454,10 @@ getStarParameters(const Wt::Http::ParameterMap& parameters) res.releaseIds = getMultiParametersAs(parameters, "albumId"); if (!std::all_of(std::cbegin(res.releaseIds ), std::cend(res.releaseIds ), [](const Id& id) { return id.type == Id::Type::Release; })) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"albumId"}; if (!std::all_of(std::cbegin(res.artistIds ), std::cend(res.artistIds ), [](const Id& id) { return id.type == Id::Type::Artist; })) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"artistId"}; // Redispatch the old "id" parameter in new lists for (const Id& id : ids) @@ -1469,7 +1474,7 @@ getStarParameters(const Wt::Http::ParameterMap& parameters) res.trackIds.emplace_back(id); break; default: - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; } } @@ -1486,7 +1491,7 @@ handleStarRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; for (const Id& id : params.artistIds) { @@ -1542,7 +1547,7 @@ handleUnstarRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; for (const Id& id : params.artistIds) { @@ -1588,7 +1593,7 @@ handleUpdateUserRequest(RequestContext& context) { *password = decodePasswordIfNeeded(*password); if (!getService()->evaluatePasswordStrength(username, *password)) - throw Error {Error::CustomType::PasswordTooWeak}; + throw PasswordTooWeakGenericError {}; hash = getService()->hashPassword(*password); } @@ -1597,7 +1602,7 @@ handleUpdateUserRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, username)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; if (maxBitRate) { @@ -1623,7 +1628,7 @@ handleUpdatePlaylistRequest(RequestContext& context) // Mandatory params Id id {getMandatoryParameterAs(context.parameters, "playlistId")}; if (id.type != Id::Type::Playlist) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"playlistId"}; // Optional parameters auto name {getParameterAs(context.parameters, "name")}; @@ -1631,7 +1636,7 @@ handleUpdatePlaylistRequest(RequestContext& context) std::vector trackIdsToAdd {getMultiParametersAs(context.parameters, "songIdToAdd")}; if (!std::all_of(std::cbegin(trackIdsToAdd), std::cend(trackIdsToAdd), [](const Id& id) { return id.type == Id::Type::Track; })) - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"songIdToAdd"}; std::vector trackPositionsToRemove {getMultiParametersAs(context.parameters, "songIndexToRemove")}; @@ -1639,14 +1644,14 @@ handleUpdatePlaylistRequest(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)}; if (!tracklist || tracklist->getUser() != user || tracklist->getType() != TrackList::Type::Playlist) { - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; } if (name) @@ -1696,7 +1701,7 @@ createTranscoder(RequestContext& context) User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; if (!user) - throw Error {Error::Code::RequestedDataNotFound}; + throw UserNotAuthorizedError {}; // "If set to zero, no limit is imposed" if (!maxBitRate || *maxBitRate == 0) @@ -1706,7 +1711,7 @@ createTranscoder(RequestContext& context) auto track {Track::getById(context.dbSession, id.value)}; if (!track) - throw Error {Error::Code::RequestedDataNotFound}; + throw RequestedDataNotFoundError {}; trackPath = track->getPath(); } @@ -1743,7 +1748,7 @@ handleStream(RequestContext& context, Wt::Http::ResponseContinuation* continuati } if (!transcoder) - throw Error {Error::CustomType::InternalError}; + throw InternalErrorGenericError {"Cannot create transcoder"}; if (!transcoder->isComplete()) { @@ -1751,7 +1756,6 @@ handleStream(RequestContext& context, Wt::Http::ResponseContinuation* continuati res.data.reserve(chunkSize); transcoder->process(res.data, chunkSize); - } if (!transcoder->isComplete()) @@ -1781,7 +1785,7 @@ handleGetCoverArt(RequestContext& context, Wt::Http::ResponseContinuation*) res.data = getService()->getFromRelease(context.dbSession, id.value, Image::Format::JPEG, size); break; default: - throw Error {Error::CustomType::BadIdFormat}; + throw BadParameterGenericError {"id"}; } res.mimeType = Image::format_to_mimeType(Image::Format::JPEG); @@ -1874,9 +1878,9 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp case Auth::PasswordService::PasswordCheckResult::Match: break; case Auth::PasswordService::PasswordCheckResult::Mismatch: - throw Error {Error::Code::WrongUsernameOrPassword}; + throw WrongUsernameOrPasswordError {}; case Auth::PasswordService::PasswordCheckResult::Throttled: - throw Error {Error::CustomType::LoginThrottled}; + throw LoginThrottledGenericError {}; } RequestContext requestContext {.parameters = parameters, .dbSession = dbSession, .userName = clientInfo.user}; @@ -1889,11 +1893,8 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp auto transaction {dbSession.createSharedTransaction()}; User::pointer user {User::getByLoginName(dbSession, clientInfo.user)}; - if (!user) - throw Error {Error::Code::RequestedDataNotFound}; - - if (!user->isAdmin()) - throw Error {Error::Code::UserNotAuthorized}; + if (!user || !user->isAdmin()) + throw UserNotAuthorizedError {}; } Response resp {(itEntryPoint->second.func)(requestContext)}; @@ -1933,7 +1934,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp } LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'"; - throw Error {Error::CustomType::NotImplemented}; + throw NotImplementedGenericError {}; } catch (const Error& e) { diff --git a/src/api/subsonic/SubsonicResponse.cpp b/src/api/subsonic/SubsonicResponse.cpp index 0f5715f4..6bac404c 100644 --- a/src/api/subsonic/SubsonicResponse.cpp +++ b/src/api/subsonic/SubsonicResponse.cpp @@ -44,68 +44,6 @@ ResponseFormatToMimeType(ResponseFormat format) return ""; } -static -const char* -ErrorCodeToString(Error::Code error) -{ - switch (error) - { - case Error::Code::Generic: - return "Generic Error."; - case Error::Code::RequiredParameterMissing: - return "Required parameter is missing."; - case Error::Code::ClientMustUpgrade: - return "Incompatible Subsonic REST protocol version. Client must upgrade."; - case Error::Code::ServerMustUpgrade: - return "Incompatible Subsonic REST protocol version. Server must upgrade."; - case Error::Code::WrongUsernameOrPassword: - return "Wrong username or password."; - case Error::Code::UserNotAuthorized: - return "User is not authorized for the given operation."; - case Error::Code::RequestedDataNotFound: - return "The requested data was not found."; - } - - return "Unknown error"; -} - -static -const char* -CustomTypeToString(Error::CustomType type) -{ - switch (type) - { - case Error::CustomType::BadIdFormat: - return "Bad id format"; - case Error::CustomType::BadPasswordFormat: - return "Bad password format"; - case Error::CustomType::InternalError: - return "Internal error"; - case Error::CustomType::LoginThrottled: - return "Login throttled, too many attempts"; - case Error::CustomType::NotImplemented: - return "Not implemented"; - case Error::CustomType::PasswordTooWeak: - return "Password too weak"; - case Error::CustomType::UserAlreadyExists: - return "User already exists"; - } - - return "Unknown custom error"; -} - -Error::Error(Code code) -: _code {code}, -_message {ErrorCodeToString(code)} -{ -} - -Error::Error(Error::CustomType type) -: _code {Code::Generic}, -_message {CustomTypeToString(type)} -{ -} - void Response::Node::setValue(const std::string& value) { diff --git a/src/api/subsonic/SubsonicResponse.hpp b/src/api/subsonic/SubsonicResponse.hpp index 0a97b98e..ca38d9c0 100644 --- a/src/api/subsonic/SubsonicResponse.hpp +++ b/src/api/subsonic/SubsonicResponse.hpp @@ -52,26 +52,119 @@ class Error RequestedDataNotFound = 70, }; - enum class CustomType - { - BadIdFormat, - BadPasswordFormat, - InternalError, - LoginThrottled, - NotImplemented, - PasswordTooWeak, - UserAlreadyExists, - }; + Error(Code code) : _code {code} {} - Error(Code code); - Error(CustomType customType); + virtual std::string getMessage() const = 0; Code getCode() const { return _code; } - const std::string& getMessage() const { return _message; } private: - Code _code; - std::string _message; + 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 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