summaryrefslogtreecommitdiffstats
path: root/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/interfaces/json-rpc/JSONServiceDescription.cpp')
-rw-r--r--xbmc/interfaces/json-rpc/JSONServiceDescription.cpp2153
1 files changed, 2153 insertions, 0 deletions
diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp
new file mode 100644
index 0000000..da100b2
--- /dev/null
+++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp
@@ -0,0 +1,2153 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "JSONServiceDescription.h"
+
+#include "AddonsOperations.h"
+#include "ApplicationOperations.h"
+#include "AudioLibrary.h"
+#include "FavouritesOperations.h"
+#include "FileOperations.h"
+#include "GUIOperations.h"
+#include "InputOperations.h"
+#include "JSONRPC.h"
+#include "PVROperations.h"
+#include "PlayerOperations.h"
+#include "PlaylistOperations.h"
+#include "ProfilesOperations.h"
+#include "ServiceDescription.h"
+#include "SettingsOperations.h"
+#include "SystemOperations.h"
+#include "TextureOperations.h"
+#include "VideoLibrary.h"
+#include "XBMCOperations.h"
+#include "utils/JSONVariantParser.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+using namespace JSONRPC;
+
+std::map<std::string, CVariant> CJSONServiceDescription::m_notifications = std::map<std::string, CVariant>();
+CJSONServiceDescription::CJsonRpcMethodMap CJSONServiceDescription::m_actionMap;
+std::map<std::string, JSONSchemaTypeDefinitionPtr> CJSONServiceDescription::m_types = std::map<std::string, JSONSchemaTypeDefinitionPtr>();
+CJSONServiceDescription::IncompleteSchemaDefinitionMap CJSONServiceDescription::m_incompleteDefinitions = CJSONServiceDescription::IncompleteSchemaDefinitionMap();
+
+// clang-format off
+
+JsonRpcMethodMap CJSONServiceDescription::m_methodMaps[] = {
+// JSON-RPC
+ { "JSONRPC.Introspect", CJSONRPC::Introspect },
+ { "JSONRPC.Version", CJSONRPC::Version },
+ { "JSONRPC.Permission", CJSONRPC::Permission },
+ { "JSONRPC.Ping", CJSONRPC::Ping },
+ { "JSONRPC.GetConfiguration", CJSONRPC::GetConfiguration },
+ { "JSONRPC.SetConfiguration", CJSONRPC::SetConfiguration },
+ { "JSONRPC.NotifyAll", CJSONRPC::NotifyAll },
+
+// Player
+ { "Player.GetActivePlayers", CPlayerOperations::GetActivePlayers },
+ { "Player.GetPlayers", CPlayerOperations::GetPlayers },
+ { "Player.GetProperties", CPlayerOperations::GetProperties },
+ { "Player.GetItem", CPlayerOperations::GetItem },
+
+ { "Player.PlayPause", CPlayerOperations::PlayPause },
+ { "Player.Stop", CPlayerOperations::Stop },
+ { "Player.GetAudioDelay", CPlayerOperations::GetAudioDelay },
+ { "Player.SetAudioDelay", CPlayerOperations::SetAudioDelay },
+ { "Player.SetSpeed", CPlayerOperations::SetSpeed },
+ { "Player.Seek", CPlayerOperations::Seek },
+ { "Player.Move", CPlayerOperations::Move },
+ { "Player.Zoom", CPlayerOperations::Zoom },
+ { "Player.SetViewMode", CPlayerOperations::SetViewMode },
+ { "Player.GetViewMode", CPlayerOperations::GetViewMode },
+ { "Player.Rotate", CPlayerOperations::Rotate },
+
+ { "Player.Open", CPlayerOperations::Open },
+ { "Player.GoTo", CPlayerOperations::GoTo },
+ { "Player.SetShuffle", CPlayerOperations::SetShuffle },
+ { "Player.SetRepeat", CPlayerOperations::SetRepeat },
+ { "Player.SetPartymode", CPlayerOperations::SetPartymode },
+
+ { "Player.SetAudioStream", CPlayerOperations::SetAudioStream },
+ { "Player.AddSubtitle", CPlayerOperations::AddSubtitle },
+ { "Player.SetSubtitle", CPlayerOperations::SetSubtitle },
+ { "Player.SetVideoStream", CPlayerOperations::SetVideoStream },
+
+// Playlist
+ { "Playlist.GetPlaylists", CPlaylistOperations::GetPlaylists },
+ { "Playlist.GetProperties", CPlaylistOperations::GetProperties },
+ { "Playlist.GetItems", CPlaylistOperations::GetItems },
+ { "Playlist.Add", CPlaylistOperations::Add },
+ { "Playlist.Insert", CPlaylistOperations::Insert },
+ { "Playlist.Clear", CPlaylistOperations::Clear },
+ { "Playlist.Remove", CPlaylistOperations::Remove },
+ { "Playlist.Swap", CPlaylistOperations::Swap },
+
+// Files
+ { "Files.GetSources", CFileOperations::GetRootDirectory },
+ { "Files.GetDirectory", CFileOperations::GetDirectory },
+ { "Files.GetFileDetails", CFileOperations::GetFileDetails },
+ { "Files.SetFileDetails", CFileOperations::SetFileDetails },
+ { "Files.PrepareDownload", CFileOperations::PrepareDownload },
+ { "Files.Download", CFileOperations::Download },
+
+// Music Library
+ { "AudioLibrary.GetProperties", CAudioLibrary::GetProperties },
+ { "AudioLibrary.GetArtists", CAudioLibrary::GetArtists },
+ { "AudioLibrary.GetArtistDetails", CAudioLibrary::GetArtistDetails },
+ { "AudioLibrary.GetAlbums", CAudioLibrary::GetAlbums },
+ { "AudioLibrary.GetAlbumDetails", CAudioLibrary::GetAlbumDetails },
+ { "AudioLibrary.GetSongs", CAudioLibrary::GetSongs },
+ { "AudioLibrary.GetSongDetails", CAudioLibrary::GetSongDetails },
+ { "AudioLibrary.GetRecentlyAddedAlbums", CAudioLibrary::GetRecentlyAddedAlbums },
+ { "AudioLibrary.GetRecentlyAddedSongs", CAudioLibrary::GetRecentlyAddedSongs },
+ { "AudioLibrary.GetRecentlyPlayedAlbums", CAudioLibrary::GetRecentlyPlayedAlbums },
+ { "AudioLibrary.GetRecentlyPlayedSongs", CAudioLibrary::GetRecentlyPlayedSongs },
+ { "AudioLibrary.GetGenres", CAudioLibrary::GetGenres },
+ { "AudioLibrary.GetRoles", CAudioLibrary::GetRoles },
+ { "AudioLibrary.GetSources", CAudioLibrary::GetSources },
+ { "AudioLibrary.GetAvailableArtTypes", CAudioLibrary::GetAvailableArtTypes },
+ { "AudioLibrary.GetAvailableArt", CAudioLibrary::GetAvailableArt },
+ { "AudioLibrary.SetArtistDetails", CAudioLibrary::SetArtistDetails },
+ { "AudioLibrary.SetAlbumDetails", CAudioLibrary::SetAlbumDetails },
+ { "AudioLibrary.SetSongDetails", CAudioLibrary::SetSongDetails },
+ { "AudioLibrary.Scan", CAudioLibrary::Scan },
+ { "AudioLibrary.Export", CAudioLibrary::Export },
+ { "AudioLibrary.Clean", CAudioLibrary::Clean },
+
+// Video Library
+ { "VideoLibrary.GetGenres", CVideoLibrary::GetGenres },
+ { "VideoLibrary.GetTags", CVideoLibrary::GetTags },
+ { "VideoLibrary.GetAvailableArtTypes", CVideoLibrary::GetAvailableArtTypes },
+ { "VideoLibrary.GetAvailableArt", CVideoLibrary::GetAvailableArt },
+ { "VideoLibrary.GetMovies", CVideoLibrary::GetMovies },
+ { "VideoLibrary.GetMovieDetails", CVideoLibrary::GetMovieDetails },
+ { "VideoLibrary.GetMovieSets", CVideoLibrary::GetMovieSets },
+ { "VideoLibrary.GetMovieSetDetails", CVideoLibrary::GetMovieSetDetails },
+ { "VideoLibrary.GetTVShows", CVideoLibrary::GetTVShows },
+ { "VideoLibrary.GetTVShowDetails", CVideoLibrary::GetTVShowDetails },
+ { "VideoLibrary.GetSeasons", CVideoLibrary::GetSeasons },
+ { "VideoLibrary.GetSeasonDetails", CVideoLibrary::GetSeasonDetails },
+ { "VideoLibrary.GetEpisodes", CVideoLibrary::GetEpisodes },
+ { "VideoLibrary.GetEpisodeDetails", CVideoLibrary::GetEpisodeDetails },
+ { "VideoLibrary.GetMusicVideos", CVideoLibrary::GetMusicVideos },
+ { "VideoLibrary.GetMusicVideoDetails", CVideoLibrary::GetMusicVideoDetails },
+ { "VideoLibrary.GetRecentlyAddedMovies", CVideoLibrary::GetRecentlyAddedMovies },
+ { "VideoLibrary.GetRecentlyAddedEpisodes", CVideoLibrary::GetRecentlyAddedEpisodes },
+ { "VideoLibrary.GetRecentlyAddedMusicVideos", CVideoLibrary::GetRecentlyAddedMusicVideos },
+ { "VideoLibrary.GetInProgressTVShows", CVideoLibrary::GetInProgressTVShows },
+ { "VideoLibrary.SetMovieDetails", CVideoLibrary::SetMovieDetails },
+ { "VideoLibrary.SetMovieSetDetails", CVideoLibrary::SetMovieSetDetails },
+ { "VideoLibrary.SetTVShowDetails", CVideoLibrary::SetTVShowDetails },
+ { "VideoLibrary.SetSeasonDetails", CVideoLibrary::SetSeasonDetails },
+ { "VideoLibrary.SetEpisodeDetails", CVideoLibrary::SetEpisodeDetails },
+ { "VideoLibrary.SetMusicVideoDetails", CVideoLibrary::SetMusicVideoDetails },
+ { "VideoLibrary.RefreshMovie", CVideoLibrary::RefreshMovie },
+ { "VideoLibrary.RefreshTVShow", CVideoLibrary::RefreshTVShow },
+ { "VideoLibrary.RefreshEpisode", CVideoLibrary::RefreshEpisode },
+ { "VideoLibrary.RefreshMusicVideo", CVideoLibrary::RefreshMusicVideo },
+ { "VideoLibrary.RemoveMovie", CVideoLibrary::RemoveMovie },
+ { "VideoLibrary.RemoveTVShow", CVideoLibrary::RemoveTVShow },
+ { "VideoLibrary.RemoveEpisode", CVideoLibrary::RemoveEpisode },
+ { "VideoLibrary.RemoveMusicVideo", CVideoLibrary::RemoveMusicVideo },
+ { "VideoLibrary.Scan", CVideoLibrary::Scan },
+ { "VideoLibrary.Export", CVideoLibrary::Export },
+ { "VideoLibrary.Clean", CVideoLibrary::Clean },
+
+// Addon operations
+ { "Addons.GetAddons", CAddonsOperations::GetAddons },
+ { "Addons.GetAddonDetails", CAddonsOperations::GetAddonDetails },
+ { "Addons.SetAddonEnabled", CAddonsOperations::SetAddonEnabled },
+ { "Addons.ExecuteAddon", CAddonsOperations::ExecuteAddon },
+
+// GUI operations
+ { "GUI.GetProperties", CGUIOperations::GetProperties },
+ { "GUI.ActivateWindow", CGUIOperations::ActivateWindow },
+ { "GUI.ShowNotification", CGUIOperations::ShowNotification },
+ { "GUI.SetFullscreen", CGUIOperations::SetFullscreen },
+ { "GUI.SetStereoscopicMode", CGUIOperations::SetStereoscopicMode },
+ { "GUI.GetStereoscopicModes", CGUIOperations::GetStereoscopicModes },
+
+// PVR operations
+ { "PVR.GetProperties", CPVROperations::GetProperties },
+ { "PVR.GetChannelGroups", CPVROperations::GetChannelGroups },
+ { "PVR.GetChannelGroupDetails", CPVROperations::GetChannelGroupDetails },
+ { "PVR.GetChannels", CPVROperations::GetChannels },
+ { "PVR.GetChannelDetails", CPVROperations::GetChannelDetails },
+ { "PVR.GetClients", CPVROperations::GetClients },
+ { "PVR.GetBroadcasts", CPVROperations::GetBroadcasts },
+ { "PVR.GetBroadcastDetails", CPVROperations::GetBroadcastDetails },
+ { "PVR.GetBroadcastIsPlayable", CPVROperations::GetBroadcastIsPlayable },
+ { "PVR.GetTimers", CPVROperations::GetTimers },
+ { "PVR.GetTimerDetails", CPVROperations::GetTimerDetails },
+ { "PVR.GetRecordings", CPVROperations::GetRecordings },
+ { "PVR.GetRecordingDetails", CPVROperations::GetRecordingDetails },
+ { "PVR.AddTimer", CPVROperations::AddTimer },
+ { "PVR.DeleteTimer", CPVROperations::DeleteTimer },
+ { "PVR.ToggleTimer", CPVROperations::ToggleTimer },
+ { "PVR.Record", CPVROperations::Record },
+ { "PVR.Scan", CPVROperations::Scan },
+
+// Profiles operations
+ { "Profiles.GetProfiles", CProfilesOperations::GetProfiles},
+ { "Profiles.GetCurrentProfile", CProfilesOperations::GetCurrentProfile},
+ { "Profiles.LoadProfile", CProfilesOperations::LoadProfile},
+
+// System operations
+ { "System.GetProperties", CSystemOperations::GetProperties },
+ { "System.EjectOpticalDrive", CSystemOperations::EjectOpticalDrive },
+ { "System.Shutdown", CSystemOperations::Shutdown },
+ { "System.Suspend", CSystemOperations::Suspend },
+ { "System.Hibernate", CSystemOperations::Hibernate },
+ { "System.Reboot", CSystemOperations::Reboot },
+
+// Input operations
+ { "Input.SendText", CInputOperations::SendText },
+ { "Input.ExecuteAction", CInputOperations::ExecuteAction },
+ { "Input.ButtonEvent", CInputOperations::ButtonEvent },
+ { "Input.Left", CInputOperations::Left },
+ { "Input.Right", CInputOperations::Right },
+ { "Input.Down", CInputOperations::Down },
+ { "Input.Up", CInputOperations::Up },
+ { "Input.Select", CInputOperations::Select },
+ { "Input.Back", CInputOperations::Back },
+ { "Input.ContextMenu", CInputOperations::ContextMenu },
+ { "Input.Info", CInputOperations::Info },
+ { "Input.Home", CInputOperations::Home },
+ { "Input.ShowCodec", CInputOperations::ShowCodec },
+ { "Input.ShowOSD", CInputOperations::ShowOSD },
+ { "Input.ShowPlayerProcessInfo", CInputOperations::ShowPlayerProcessInfo },
+
+// Application operations
+ { "Application.GetProperties", CApplicationOperations::GetProperties },
+ { "Application.SetVolume", CApplicationOperations::SetVolume },
+ { "Application.SetMute", CApplicationOperations::SetMute },
+ { "Application.Quit", CApplicationOperations::Quit },
+
+// Favourites operations
+ { "Favourites.GetFavourites", CFavouritesOperations::GetFavourites },
+ { "Favourites.AddFavourite", CFavouritesOperations::AddFavourite },
+
+// Textures operations
+ { "Textures.GetTextures", CTextureOperations::GetTextures },
+ { "Textures.RemoveTexture", CTextureOperations::RemoveTexture },
+
+// Settings operations
+ { "Settings.GetSections", CSettingsOperations::GetSections },
+ { "Settings.GetCategories", CSettingsOperations::GetCategories },
+ { "Settings.GetSettings", CSettingsOperations::GetSettings },
+ { "Settings.GetSettingValue", CSettingsOperations::GetSettingValue },
+ { "Settings.SetSettingValue", CSettingsOperations::SetSettingValue },
+ { "Settings.ResetSettingValue", CSettingsOperations::ResetSettingValue },
+ { "Settings.GetSkinSettings", CSettingsOperations::GetSkinSettings },
+ { "Settings.GetSkinSettingValue", CSettingsOperations::GetSkinSettingValue },
+ { "Settings.SetSkinSettingValue", CSettingsOperations::SetSkinSettingValue },
+
+// XBMC operations
+ { "XBMC.GetInfoLabels", CXBMCOperations::GetInfoLabels },
+ { "XBMC.GetInfoBooleans", CXBMCOperations::GetInfoBooleans }
+};
+
+// clang-format on
+
+JSONSchemaTypeDefinition::JSONSchemaTypeDefinition()
+ : missingReference(),
+ name(),
+ ID(),
+ referencedType(nullptr),
+ extends(),
+ description(),
+ unionTypes(),
+ defaultValue(),
+ minimum(-std::numeric_limits<double>::max()),
+ maximum(std::numeric_limits<double>::max()),
+ enums(),
+ items(),
+ additionalItems(),
+ properties(),
+ additionalProperties(nullptr)
+{ }
+
+bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* = false */)
+{
+ bool hasReference = false;
+
+ // Check if the type of the parameter defines a json reference
+ // to a type defined somewhere else
+ if (value.isMember("$ref") && value["$ref"].isString())
+ {
+ // Get the name of the referenced type
+ std::string refType = value["$ref"].asString();
+ // Check if the referenced type exists
+ JSONSchemaTypeDefinitionPtr referencedTypeDef = CJSONServiceDescription::GetType(refType);
+ if (refType.length() <= 0 || referencedTypeDef.get() == NULL)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} references an unknown type {}", name,
+ refType);
+ missingReference = refType;
+ return false;
+ }
+
+ std::string typeName = name;
+ *this = *referencedTypeDef;
+ if (!typeName.empty())
+ name = typeName;
+ referencedType = referencedTypeDef;
+ hasReference = true;
+ }
+ else if (value.isMember("id") && value["id"].isString())
+ ID = GetString(value["id"], "");
+
+ // Check if the "required" field has been defined
+ optional = value.isMember("required") && value["required"].isBoolean() ? !value["required"].asBoolean() : true;
+
+ // Get the "description"
+ if (!hasReference || (value.isMember("description") && value["description"].isString()))
+ description = GetString(value["description"], "");
+
+ if (hasReference)
+ {
+ // If there is a specific default value, read it
+ if (value.isMember("default") && IsType(value["default"], type))
+ {
+ bool ok = false;
+ if (enums.size() <= 0)
+ ok = true;
+ // If the type has an enum definition we must make
+ // sure that the default value is a valid enum value
+ else
+ {
+ for (const auto& itr : enums)
+ {
+ if (value["default"] == itr)
+ {
+ ok = true;
+ break;
+ }
+ }
+ }
+
+ if (ok)
+ defaultValue = value["default"];
+ }
+
+ return true;
+ }
+
+ // Check whether this type extends an existing type
+ if (value.isMember("extends"))
+ {
+ if (value["extends"].isString())
+ {
+ std::string extendsName = GetString(value["extends"], "");
+ if (!extendsName.empty())
+ {
+ JSONSchemaTypeDefinitionPtr extendedTypeDef = CJSONServiceDescription::GetType(extendsName);
+ if (extendedTypeDef.get() == NULL)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends an unknown type {}", name,
+ extendsName);
+ missingReference = extendsName;
+ return false;
+ }
+
+ type = extendedTypeDef->type;
+ extends.push_back(extendedTypeDef);
+ }
+ }
+ else if (value["extends"].isArray())
+ {
+ JSONSchemaType extendedType = AnyValue;
+ for (unsigned int extendsIndex = 0; extendsIndex < value["extends"].size(); extendsIndex++)
+ {
+ std::string extendsName = GetString(value["extends"][extendsIndex], "");
+ if (!extendsName.empty())
+ {
+ JSONSchemaTypeDefinitionPtr extendedTypeDef = CJSONServiceDescription::GetType(extendsName);
+ if (extendedTypeDef.get() == NULL)
+ {
+ extends.clear();
+ CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends an unknown type {}", name,
+ extendsName);
+ missingReference = extendsName;
+ return false;
+ }
+
+ if (extendsIndex == 0)
+ extendedType = extendedTypeDef->type;
+ else if (extendedType != extendedTypeDef->type)
+ {
+ extends.clear();
+ CLog::Log(LOGDEBUG,
+ "JSONRPC: JSON schema type {} extends multiple JSON schema types of "
+ "mismatching types",
+ name);
+ return false;
+ }
+
+ extends.push_back(extendedTypeDef);
+ }
+ }
+
+ type = extendedType;
+ }
+ }
+
+ // Only read the "type" attribute if it's
+ // not an extending type
+ if (extends.size() <= 0)
+ {
+ // Get the defined type of the parameter
+ if (!CJSONServiceDescription::parseJSONSchemaType(value["type"], unionTypes, type, missingReference))
+ return false;
+ }
+
+ if (HasType(type, ObjectValue))
+ {
+ // If the type definition is of type "object"
+ // and has a "properties" definition we need
+ // to handle these as well
+ if (value.isMember("properties") && value["properties"].isObject())
+ {
+ // Get all child elements of the "properties"
+ // object and loop through them
+ for (CVariant::const_iterator_map itr = value["properties"].begin_map(); itr != value["properties"].end_map(); ++itr)
+ {
+ // Create a new type definition, store the name
+ // of the current property into it, parse it
+ // recursively and add its default value
+ // to the current type's default value
+ JSONSchemaTypeDefinitionPtr propertyType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ propertyType->name = itr->first;
+ if (!propertyType->Parse(itr->second))
+ {
+ missingReference = propertyType->missingReference;
+ return false;
+ }
+ defaultValue[itr->first] = propertyType->defaultValue;
+ properties.add(propertyType);
+ }
+ }
+
+ hasAdditionalProperties = true;
+ additionalProperties = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ if (value.isMember("additionalProperties"))
+ {
+ if (value["additionalProperties"].isBoolean())
+ {
+ hasAdditionalProperties = value["additionalProperties"].asBoolean();
+ if (!hasAdditionalProperties)
+ {
+ additionalProperties.reset();
+ }
+ }
+ else if (value["additionalProperties"].isObject() && !value["additionalProperties"].isNull())
+ {
+ if (!additionalProperties->Parse(value["additionalProperties"]))
+ {
+ missingReference = additionalProperties->missingReference;
+ hasAdditionalProperties = false;
+ additionalProperties.reset();
+
+ CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties schema definition in type {}",
+ name);
+ return false;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties definition in type {}", name);
+ return false;
+ }
+ }
+ }
+
+ // If the defined parameter is an array
+ // we need to check for detailed definitions
+ // of the array items
+ if (HasType(type, ArrayValue))
+ {
+ // Check for "uniqueItems" field
+ if (value.isMember("uniqueItems") && value["uniqueItems"].isBoolean())
+ uniqueItems = value["uniqueItems"].asBoolean();
+ else
+ uniqueItems = false;
+
+ // Check for "additionalItems" field
+ if (value.isMember("additionalItems"))
+ {
+ // If it is an object, there is only one schema for it
+ if (value["additionalItems"].isObject())
+ {
+ JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ if (additionalItem->Parse(value["additionalItems"]))
+ additionalItems.push_back(additionalItem);
+ else
+ {
+ CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value for type {}", name);
+ missingReference = additionalItem->missingReference;
+ return false;
+ }
+ }
+ // If it is an array there may be multiple schema definitions
+ else if (value["additionalItems"].isArray())
+ {
+ for (unsigned int itemIndex = 0; itemIndex < value["additionalItems"].size(); itemIndex++)
+ {
+ JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+
+ if (additionalItem->Parse(value["additionalItems"][itemIndex]))
+ additionalItems.push_back(additionalItem);
+ else
+ {
+ CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value (item {}) for type {}",
+ itemIndex, name);
+ missingReference = additionalItem->missingReference;
+ return false;
+ }
+ }
+ }
+ // If it is not a (array of) schema and not a bool (default value is false)
+ // it has an invalid value
+ else if (!value["additionalItems"].isBoolean())
+ {
+ CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" definition for type {}", name);
+ return false;
+ }
+ }
+
+ // If the "items" field is a single object
+ // we can parse that directly
+ if (value.isMember("items"))
+ {
+ if (value["items"].isObject())
+ {
+ JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ if (!item->Parse(value["items"]))
+ {
+ CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" for type {}", name);
+ missingReference = item->missingReference;
+ return false;
+ }
+ items.push_back(item);
+ }
+ // Otherwise if it is an array we need to
+ // parse all elements and store them
+ else if (value["items"].isArray())
+ {
+ for (CVariant::const_iterator_array itemItr = value["items"].begin_array(); itemItr != value["items"].end_array(); ++itemItr)
+ {
+ JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ if (!item->Parse(*itemItr))
+ {
+ CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" array for type {}", name);
+ missingReference = item->missingReference;
+ return false;
+ }
+ items.push_back(item);
+ }
+ }
+ }
+
+ minItems = (unsigned int)value["minItems"].asUnsignedInteger(0);
+ maxItems = (unsigned int)value["maxItems"].asUnsignedInteger(0);
+ }
+
+ if (HasType(type, NumberValue) || HasType(type, IntegerValue))
+ {
+ if ((type & NumberValue) == NumberValue)
+ {
+ minimum = value["minimum"].asDouble(-std::numeric_limits<double>::max());
+ maximum = value["maximum"].asDouble(std::numeric_limits<double>::max());
+ }
+ else if ((type & IntegerValue) == IntegerValue)
+ {
+ minimum = (double)value["minimum"].asInteger(std::numeric_limits<int>::min());
+ maximum = (double)value["maximum"].asInteger(std::numeric_limits<int>::max());
+ }
+
+ exclusiveMinimum = value["exclusiveMinimum"].asBoolean(false);
+ exclusiveMaximum = value["exclusiveMaximum"].asBoolean(false);
+ divisibleBy = (unsigned int)value["divisibleBy"].asUnsignedInteger(0);
+ }
+
+ if (HasType(type, StringValue))
+ {
+ minLength = (int)value["minLength"].asInteger(-1);
+ maxLength = (int)value["maxLength"].asInteger(-1);
+ }
+
+ // If the type definition is neither an
+ // "object" nor an "array" we can check
+ // for an "enum" definition
+ if (value.isMember("enum") && value["enum"].isArray())
+ {
+ // Loop through all elements in the "enum" array
+ for (CVariant::const_iterator_array enumItr = value["enum"].begin_array(); enumItr != value["enum"].end_array(); ++enumItr)
+ {
+ // Check for duplicates and eliminate them
+ bool approved = true;
+ for (unsigned int approvedIndex = 0; approvedIndex < enums.size(); approvedIndex++)
+ {
+ if (*enumItr == enums.at(approvedIndex))
+ {
+ approved = false;
+ break;
+ }
+ }
+
+ // Only add the current item to the enum value
+ // list if it is not duplicate
+ if (approved)
+ enums.push_back(*enumItr);
+ }
+ }
+
+ if (type != ObjectValue)
+ {
+ // If there is a definition for a default value and its type
+ // matches the type of the parameter we can parse it
+ bool ok = false;
+ if (value.isMember("default") && IsType(value["default"], type))
+ {
+ if (enums.size() <= 0)
+ ok = true;
+ // If the type has an enum definition we must make
+ // sure that the default value is a valid enum value
+ else
+ {
+ for (std::vector<CVariant>::const_iterator itr = enums.begin(); itr != enums.end(); ++itr)
+ {
+ if (value["default"] == *itr)
+ {
+ ok = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (ok)
+ defaultValue = value["default"];
+ else
+ {
+ // If the type of the default value definition does not
+ // match the type of the parameter we have to log this
+ if (value.isMember("default") && !IsType(value["default"], type))
+ CLog::Log(LOGDEBUG, "JSONRPC: Parameter {} has an invalid default value", name);
+
+ // If the type contains an "enum" we need to get the
+ // default value from the first enum value
+ if (enums.size() > 0)
+ defaultValue = enums.at(0);
+ // otherwise set a default value instead
+ else
+ SetDefaultValue(defaultValue, type);
+ }
+ }
+
+ return true;
+}
+
+JSONRPC_STATUS JSONSchemaTypeDefinition::Check(const CVariant& value,
+ CVariant& outputValue,
+ CVariant& errorData) const
+{
+ if (!name.empty())
+ errorData["name"] = name;
+ SchemaValueTypeToJson(type, errorData["type"]);
+ std::string errorMessage;
+
+ // Let's check the type of the provided parameter
+ if (!IsType(value, type))
+ {
+ errorMessage = StringUtils::Format("Invalid type {} received", ValueTypeToString(value.type()));
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ else if (value.isNull() && !HasType(type, NullValue))
+ {
+ errorData["message"] = "Received value is null";
+ return InvalidParams;
+ }
+
+ // Let's check if we have to handle a union type
+ if (unionTypes.size() > 0)
+ {
+ bool ok = false;
+ for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++)
+ {
+ CVariant dummyError;
+ CVariant testOutput = outputValue;
+ if (unionTypes.at(unionIndex)->Check(value, testOutput, dummyError) == OK)
+ {
+ ok = true;
+ outputValue = testOutput;
+ break;
+ }
+ }
+
+ if (!ok)
+ {
+ errorData["message"] = "Received value does not match any of the union type definitions";
+ return InvalidParams;
+ }
+ }
+
+ // First we need to check if this type extends another
+ // type and if so we need to check against the extended
+ // type first
+ if (extends.size() > 0)
+ {
+ for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++)
+ {
+ JSONRPC_STATUS status = extends.at(extendsIndex)->Check(value, outputValue, errorData);
+
+ if (status != OK)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Value does not match extended type {} of type {}",
+ extends.at(extendsIndex)->ID, name);
+ errorMessage = StringUtils::Format("value does not match extended type {}",
+ extends.at(extendsIndex)->ID);
+ errorData["message"] = errorMessage.c_str();
+ return status;
+ }
+ }
+ }
+
+ // If it is an array we need to
+ // - check the type of every element ("items")
+ // - check if they need to be unique ("uniqueItems")
+ if (HasType(type, ArrayValue) && value.isArray())
+ {
+ outputValue = CVariant(CVariant::VariantTypeArray);
+ // Check the number of items against minItems and maxItems
+ if ((minItems > 0 && value.size() < minItems) || (maxItems > 0 && value.size() > maxItems))
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "JSONRPC: Number of array elements does not match minItems and/or maxItems in type {}",
+ name);
+ if (minItems > 0 && maxItems > 0)
+ errorMessage = StringUtils::Format("Between {} and {} array items expected but {} received",
+ minItems, maxItems, value.size());
+ else if (minItems > 0)
+ errorMessage = StringUtils::Format("At least {} array items expected but only {} received",
+ minItems, value.size());
+ else
+ errorMessage = StringUtils::Format("Only {} array items expected but {} received", maxItems,
+ value.size());
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+
+ if (items.size() == 0)
+ outputValue = value;
+ else if (items.size() == 1)
+ {
+ JSONSchemaTypeDefinitionPtr itemType = items.at(0);
+
+ // Loop through all array elements
+ for (unsigned int arrayIndex = 0; arrayIndex < value.size(); arrayIndex++)
+ {
+ CVariant temp;
+ JSONRPC_STATUS status = itemType->Check(value[arrayIndex], temp, errorData["property"]);
+ outputValue.push_back(temp);
+ if (status != OK)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Array element at index {} does not match in type {}",
+ arrayIndex, name);
+ errorMessage =
+ StringUtils::Format("array element at index {} does not match", arrayIndex);
+ errorData["message"] = errorMessage.c_str();
+ return status;
+ }
+ }
+ }
+ // We have more than one element in "items"
+ // so we have tuple typing, which means that
+ // every element in the value array must match
+ // with the type at the same position in the
+ // "items" array
+ else
+ {
+ // If the number of elements in the value array
+ // does not match the number of elements in the
+ // "items" array and additional items are not
+ // allowed there is no need to check every element
+ if (value.size() < items.size() || (value.size() != items.size() && additionalItems.size() == 0))
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: One of the array elements does not match in type {}", name);
+ errorMessage = StringUtils::Format("{0} array elements expected but {1} received", items.size(), value.size());
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+
+ // Loop through all array elements until there
+ // are either no more schemas in the "items"
+ // array or no more elements in the value's array
+ unsigned int arrayIndex;
+ for (arrayIndex = 0; arrayIndex < std::min(items.size(), (size_t)value.size()); arrayIndex++)
+ {
+ JSONRPC_STATUS status = items.at(arrayIndex)->Check(value[arrayIndex], outputValue[arrayIndex], errorData["property"]);
+ if (status != OK)
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "JSONRPC: Array element at index {} does not match with items schema in type {}",
+ arrayIndex, name);
+ return status;
+ }
+ }
+
+ if (additionalItems.size() > 0)
+ {
+ // Loop through the rest of the elements
+ // in the array and check them against the
+ // "additionalItems"
+ for (; arrayIndex < value.size(); arrayIndex++)
+ {
+ bool ok = false;
+ for (unsigned int additionalIndex = 0; additionalIndex < additionalItems.size(); additionalIndex++)
+ {
+ CVariant dummyError;
+ if (additionalItems.at(additionalIndex)->Check(value[arrayIndex], outputValue[arrayIndex], dummyError) == OK)
+ {
+ ok = true;
+ break;
+ }
+ }
+
+ if (!ok)
+ {
+ CLog::Log(LOGDEBUG,
+ "JSONRPC: Array contains non-conforming additional items in type {}", name);
+ errorMessage = StringUtils::Format(
+ "Array element at index {} does not match the \"additionalItems\" schema",
+ arrayIndex);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ }
+ }
+ }
+
+ // If every array element is unique we need to check each one
+ if (uniqueItems)
+ {
+ for (unsigned int checkingIndex = 0; checkingIndex < outputValue.size(); checkingIndex++)
+ {
+ for (unsigned int checkedIndex = checkingIndex + 1; checkedIndex < outputValue.size(); checkedIndex++)
+ {
+ // If two elements are the same they are not unique
+ if (outputValue[checkingIndex] == outputValue[checkedIndex])
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Not unique array element at index {} and {} in type {}",
+ checkingIndex, checkedIndex, name);
+ errorMessage = StringUtils::Format(
+ "Array element at index {} is not unique (same as array element at index {})",
+ checkingIndex, checkedIndex);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ }
+ }
+ }
+
+ return OK;
+ }
+
+ // If it is an object we need to check every element
+ // against the defined "properties"
+ if (HasType(type, ObjectValue) && value.isObject())
+ {
+ unsigned int handled = 0;
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end();
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator;
+ for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; ++propertiesIterator)
+ {
+ if (value.isMember(propertiesIterator->second->name))
+ {
+ JSONRPC_STATUS status = propertiesIterator->second->Check(value[propertiesIterator->second->name], outputValue[propertiesIterator->second->name], errorData["property"]);
+ if (status != OK)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Invalid property \"{}\" in type {}",
+ propertiesIterator->second->name, name);
+ return status;
+ }
+ handled++;
+ }
+ else if (propertiesIterator->second->optional)
+ outputValue[propertiesIterator->second->name] = propertiesIterator->second->defaultValue;
+ else
+ {
+ errorData["property"]["name"] = propertiesIterator->second->name.c_str();
+ errorData["property"]["type"] = SchemaValueTypeToString(propertiesIterator->second->type);
+ errorData["message"] = "Missing property";
+ return InvalidParams;
+ }
+ }
+
+ // Additional properties are not allowed
+ if (handled < value.size())
+ {
+ // If additional properties are allowed we need to check if
+ // they match the defined schema
+ if (hasAdditionalProperties && additionalProperties != NULL)
+ {
+ CVariant::const_iterator_map iter;
+ CVariant::const_iterator_map iterEnd = value.end_map();
+ for (iter = value.begin_map(); iter != iterEnd; ++iter)
+ {
+ if (properties.find(iter->first) != properties.end())
+ continue;
+
+ // If the additional property is of type "any"
+ // we can simply copy its value to the output
+ // object
+ if (additionalProperties->type == AnyValue)
+ {
+ outputValue[iter->first] = value[iter->first];
+ continue;
+ }
+
+ JSONRPC_STATUS status = additionalProperties->Check(value[iter->first], outputValue[iter->first], errorData["property"]);
+ if (status != OK)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Invalid additional property \"{}\" in type {}",
+ iter->first, name);
+ return status;
+ }
+ }
+ }
+ // If we still have unchecked properties but additional
+ // properties are not allowed, we have invalid parameters
+ else if (!hasAdditionalProperties || additionalProperties == NULL)
+ {
+ errorData["message"] = "Unexpected additional properties received";
+ errorData.erase("property");
+ return InvalidParams;
+ }
+ }
+
+ return OK;
+ }
+
+ // It's neither an array nor an object
+
+ // If it can only take certain values ("enum")
+ // we need to check against those
+ if (enums.size() > 0)
+ {
+ bool valid = false;
+ for (const auto& enumItr : enums)
+ {
+ if (enumItr == value)
+ {
+ valid = true;
+ break;
+ }
+ }
+
+ if (!valid)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Value does not match any of the enum values in type {}", name);
+ errorData["message"] = "Received value does not match any of the defined enum values";
+ return InvalidParams;
+ }
+ }
+
+ // If we have a number or an integer type, we need
+ // to check the minimum and maximum values
+ if ((HasType(type, NumberValue) && value.isDouble()) || (HasType(type, IntegerValue) && value.isInteger()))
+ {
+ double numberValue;
+ if (value.isDouble())
+ numberValue = value.asDouble();
+ else
+ numberValue = (double)value.asInteger();
+ // Check minimum
+ if ((exclusiveMinimum && numberValue <= minimum) || (!exclusiveMinimum && numberValue < minimum) ||
+ // Check maximum
+ (exclusiveMaximum && numberValue >= maximum) || (!exclusiveMaximum && numberValue > maximum))
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Value does not lay between minimum and maximum in type {}",
+ name);
+ if (value.isDouble())
+ errorMessage =
+ StringUtils::Format("Value between {:f} ({}) and {:f} ({}) expected but {:f} received",
+ minimum, exclusiveMinimum ? "exclusive" : "inclusive", maximum,
+ exclusiveMaximum ? "exclusive" : "inclusive", numberValue);
+ else
+ errorMessage = StringUtils::Format(
+ "Value between {} ({}) and {} ({}) expected but {} received", (int)minimum,
+ exclusiveMinimum ? "exclusive" : "inclusive", (int)maximum,
+ exclusiveMaximum ? "exclusive" : "inclusive", (int)numberValue);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ // Check divisibleBy
+ if ((HasType(type, IntegerValue) && divisibleBy > 0 && ((int)numberValue % divisibleBy) != 0))
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet divisibleBy requirements in type {}", name);
+ errorMessage = StringUtils::Format("Value should be divisible by {} but {} received",
+ divisibleBy, (int)numberValue);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ }
+
+ // If we have a string, we need to check the length
+ if (HasType(type, StringValue) && value.isString())
+ {
+ int size = static_cast<int>(value.asString().size());
+ if (size < minLength)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet minLength requirements in type {}", name);
+ errorMessage = StringUtils::Format(
+ "Value should have a minimum length of {} but has a length of {}", minLength, size);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+
+ if (maxLength >= 0 && size > maxLength)
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet maxLength requirements in type {}", name);
+ errorMessage = StringUtils::Format(
+ "Value should have a maximum length of {} but has a length of {}", maxLength, size);
+ errorData["message"] = errorMessage.c_str();
+ return InvalidParams;
+ }
+ }
+
+ // Otherwise it can have any value
+ outputValue = value;
+ return OK;
+}
+
+void JSONSchemaTypeDefinition::Print(bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, CVariant &output) const
+{
+ bool typeReference = false;
+
+ // Printing general fields
+ if (isParameter)
+ output["name"] = name;
+
+ if (isGlobal)
+ output["id"] = ID;
+ else if (!ID.empty())
+ {
+ output["$ref"] = ID;
+ typeReference = true;
+ }
+
+ if (printDescriptions && !description.empty())
+ output["description"] = description;
+
+ if (isParameter || printDefault)
+ {
+ if (!optional)
+ output["required"] = true;
+ if (optional && type != ObjectValue && type != ArrayValue)
+ output["default"] = defaultValue;
+ }
+
+ if (!typeReference)
+ {
+ if (extends.size() == 1)
+ {
+ output["extends"] = extends.at(0)->ID;
+ }
+ else if (extends.size() > 1)
+ {
+ output["extends"] = CVariant(CVariant::VariantTypeArray);
+ for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++)
+ output["extends"].append(extends.at(extendsIndex)->ID);
+ }
+ else if (unionTypes.size() > 0)
+ {
+ output["type"] = CVariant(CVariant::VariantTypeArray);
+ for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++)
+ {
+ CVariant unionOutput = CVariant(CVariant::VariantTypeObject);
+ unionTypes.at(unionIndex)->Print(false, false, false, printDescriptions, unionOutput);
+ output["type"].append(unionOutput);
+ }
+ }
+ else
+ CJSONUtils::SchemaValueTypeToJson(type, output["type"]);
+
+ // Printing enum field
+ if (enums.size() > 0)
+ {
+ output["enums"] = CVariant(CVariant::VariantTypeArray);
+ for (unsigned int enumIndex = 0; enumIndex < enums.size(); enumIndex++)
+ output["enums"].append(enums.at(enumIndex));
+ }
+
+ // Printing integer/number fields
+ if (CJSONUtils::HasType(type, IntegerValue) || CJSONUtils::HasType(type, NumberValue))
+ {
+ if (CJSONUtils::HasType(type, NumberValue))
+ {
+ if (minimum > -std::numeric_limits<double>::max())
+ output["minimum"] = minimum;
+ if (maximum < std::numeric_limits<double>::max())
+ output["maximum"] = maximum;
+ }
+ else
+ {
+ if (minimum > std::numeric_limits<int>::min())
+ output["minimum"] = (int)minimum;
+ if (maximum < std::numeric_limits<int>::max())
+ output["maximum"] = (int)maximum;
+ }
+
+ if (exclusiveMinimum)
+ output["exclusiveMinimum"] = true;
+ if (exclusiveMaximum)
+ output["exclusiveMaximum"] = true;
+ if (divisibleBy > 0)
+ output["divisibleBy"] = divisibleBy;
+ }
+ if (CJSONUtils::HasType(type, StringValue))
+ {
+ if (minLength >= 0)
+ output["minLength"] = minLength;
+ if (maxLength >= 0)
+ output["maxLength"] = maxLength;
+ }
+
+ // Print array fields
+ if (CJSONUtils::HasType(type, ArrayValue))
+ {
+ if (items.size() == 1)
+ {
+ items.at(0)->Print(false, false, false, printDescriptions, output["items"]);
+ }
+ else if (items.size() > 1)
+ {
+ output["items"] = CVariant(CVariant::VariantTypeArray);
+ for (unsigned int itemIndex = 0; itemIndex < items.size(); itemIndex++)
+ {
+ CVariant item = CVariant(CVariant::VariantTypeObject);
+ items.at(itemIndex)->Print(false, false, false, printDescriptions, item);
+ output["items"].append(item);
+ }
+ }
+
+ if (minItems > 0)
+ output["minItems"] = minItems;
+ if (maxItems > 0)
+ output["maxItems"] = maxItems;
+
+ if (additionalItems.size() == 1)
+ {
+ additionalItems.at(0)->Print(false, false, false, printDescriptions, output["additionalItems"]);
+ }
+ else if (additionalItems.size() > 1)
+ {
+ output["additionalItems"] = CVariant(CVariant::VariantTypeArray);
+ for (unsigned int addItemIndex = 0; addItemIndex < additionalItems.size(); addItemIndex++)
+ {
+ CVariant item = CVariant(CVariant::VariantTypeObject);
+ additionalItems.at(addItemIndex)->Print(false, false, false, printDescriptions, item);
+ output["additionalItems"].append(item);
+ }
+ }
+
+ if (uniqueItems)
+ output["uniqueItems"] = true;
+ }
+
+ // Print object fields
+ if (CJSONUtils::HasType(type, ObjectValue))
+ {
+ if (properties.size() > 0)
+ {
+ output["properties"] = CVariant(CVariant::VariantTypeObject);
+
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end();
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator;
+ for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; ++propertiesIterator)
+ {
+ propertiesIterator->second->Print(false, false, true, printDescriptions, output["properties"][propertiesIterator->first]);
+ }
+ }
+
+ if (!hasAdditionalProperties)
+ output["additionalProperties"] = false;
+ else if (additionalProperties != NULL && additionalProperties->type != AnyValue)
+ additionalProperties->Print(false, false, true, printDescriptions, output["additionalProperties"]);
+ }
+ }
+}
+
+void JSONSchemaTypeDefinition::ResolveReference()
+{
+ // Check and set the reference type before recursing
+ // to guard against cycles
+ if (referencedTypeSet)
+ return;
+
+ referencedTypeSet = true;
+
+ // Take care of all nested types
+ for (const auto& it : extends)
+ it->ResolveReference();
+ for (const auto& it : unionTypes)
+ it->ResolveReference();
+ for (const auto& it : items)
+ it->ResolveReference();
+ for (const auto& it : additionalItems)
+ it->ResolveReference();
+ for (const auto& it : properties)
+ it.second->ResolveReference();
+
+ if (additionalProperties)
+ additionalProperties->ResolveReference();
+
+ if (referencedType == nullptr)
+ return;
+
+ std::string origName = name;
+ std::string origDescription = description;
+ bool origOptional = optional;
+ CVariant origDefaultValue = defaultValue;
+ JSONSchemaTypeDefinitionPtr referencedTypeDef = referencedType;
+
+ // set all the values from the given type definition
+ *this = *referencedType;
+
+ // restore the original values
+ if (!origName.empty())
+ name = origName;
+
+ if (!origDescription.empty())
+ description = origDescription;
+
+ if (!origOptional)
+ optional = origOptional;
+
+ if (!origDefaultValue.isNull())
+ defaultValue = origDefaultValue;
+
+ if (referencedTypeDef.get() != NULL)
+ referencedType = referencedTypeDef;
+
+ // This will have been overwritten by the copy of the reference
+ // type so we need to set it again
+ referencedTypeSet = true;
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::CJsonSchemaPropertiesMap() :
+ m_propertiesmap(std::map<std::string, JSONSchemaTypeDefinitionPtr>())
+{
+}
+
+void JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::add(
+ const JSONSchemaTypeDefinitionPtr& property)
+{
+ std::string name = property->name;
+ StringUtils::ToLower(name);
+ m_propertiesmap[name] = property;
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::begin() const
+{
+ return m_propertiesmap.begin();
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::find(const std::string& key) const
+{
+ return m_propertiesmap.find(key);
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::end() const
+{
+ return m_propertiesmap.end();
+}
+
+unsigned int JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::size() const
+{
+ return static_cast<unsigned int>(m_propertiesmap.size());
+}
+
+JsonRpcMethod::JsonRpcMethod()
+ : missingReference(),
+ name(),
+ method(NULL),
+ description(),
+ parameters(),
+ returns(new JSONSchemaTypeDefinition())
+{ }
+
+bool JsonRpcMethod::Parse(const CVariant &value)
+{
+ // Parse XBMC specific information about the method
+ if (value.isMember("transport") && value["transport"].isArray())
+ {
+ int transport = 0;
+ for (unsigned int index = 0; index < value["transport"].size(); index++)
+ transport |= StringToTransportLayer(value["transport"][index].asString());
+
+ transportneed = (TransportLayerCapability)transport;
+ }
+ else
+ transportneed = StringToTransportLayer(value.isMember("transport") ? value["transport"].asString() : "");
+
+ if (value.isMember("permission") && value["permission"].isArray())
+ {
+ int permissions = 0;
+ for (unsigned int index = 0; index < value["permission"].size(); index++)
+ permissions |= StringToPermission(value["permission"][index].asString());
+
+ permission = (OperationPermission)permissions;
+ }
+ else
+ permission = StringToPermission(value.isMember("permission") ? value["permission"].asString() : "");
+
+ description = GetString(value["description"], "");
+
+ // Check whether there are parameters defined
+ if (value.isMember("params") && value["params"].isArray())
+ {
+ // Loop through all defined parameters
+ for (unsigned int paramIndex = 0; paramIndex < value["params"].size(); paramIndex++)
+ {
+ CVariant parameter = value["params"][paramIndex];
+ // If the parameter definition does not contain a valid "name" or
+ // "type" element we will ignore it
+ if (!parameter.isMember("name") || !parameter["name"].isString() ||
+ (!parameter.isMember("type") && !parameter.isMember("$ref") && !parameter.isMember("extends")) ||
+ (parameter.isMember("type") && !parameter["type"].isString() && !parameter["type"].isArray()) ||
+ (parameter.isMember("$ref") && !parameter["$ref"].isString()) ||
+ (parameter.isMember("extends") && !parameter["extends"].isString() && !parameter["extends"].isArray()))
+ {
+ CLog::Log(LOGDEBUG, "JSONRPC: Method {} has a badly defined parameter", name);
+ return false;
+ }
+
+ // Parse the parameter and add it to the list
+ // of defined parameters
+ JSONSchemaTypeDefinitionPtr param = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ if (!parseParameter(parameter, param))
+ {
+ missingReference = param->missingReference;
+ return false;
+ }
+ parameters.push_back(param);
+ }
+ }
+
+ // Parse the return value of the method
+ if (!parseReturn(value))
+ {
+ missingReference = returns->missingReference;
+ return false;
+ }
+
+ return true;
+}
+
+JSONRPC_STATUS JsonRpcMethod::Check(const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) const
+{
+ if (transport != NULL && (transport->GetCapabilities() & transportneed) == transportneed)
+ {
+ if (client != NULL && (client->GetPermissionFlags() & permission) == permission && (!notification || (permission & OPERATION_PERMISSION_NOTIFICATION) == permission))
+ {
+ methodCall = method;
+
+ // Count the number of actually handled (present)
+ // parameters
+ unsigned int handled = 0;
+ CVariant errorData = CVariant(CVariant::VariantTypeObject);
+ errorData["method"] = name;
+
+ // Loop through all the parameters to check
+ for (unsigned int i = 0; i < parameters.size(); i++)
+ {
+ // Evaluate the current parameter
+ JSONRPC_STATUS status = checkParameter(requestParameters, parameters.at(i), i, outputParameters, handled, errorData);
+ if (status != OK)
+ {
+ // Return the error data object in the outputParameters reference
+ outputParameters = errorData;
+ return status;
+ }
+ }
+
+ // Check if there were unnecessary parameters
+ if (handled < requestParameters.size())
+ {
+ errorData["message"] = "Too many parameters";
+ outputParameters = errorData;
+ return InvalidParams;
+ }
+
+ return OK;
+ }
+ else
+ return BadPermission;
+ }
+
+ return MethodNotFound;
+}
+
+bool JsonRpcMethod::parseParameter(const CVariant& value,
+ const JSONSchemaTypeDefinitionPtr& parameter)
+{
+ parameter->name = GetString(value["name"], "");
+
+ // Parse the type and default value of the parameter
+ return parameter->Parse(value, true);
+}
+
+bool JsonRpcMethod::parseReturn(const CVariant &value)
+{
+ // Only parse the "returns" definition if there is one
+ if (!value.isMember("returns"))
+ {
+ returns->type = NullValue;
+ return true;
+ }
+
+ // If the type of the return value is defined as a simple string we can parse it directly
+ if (value["returns"].isString())
+ return CJSONServiceDescription::parseJSONSchemaType(value["returns"], returns->unionTypes, returns->type, missingReference);
+
+ // otherwise we have to parse the whole type definition
+ if (!returns->Parse(value["returns"]))
+ {
+ missingReference = returns->missingReference;
+ return false;
+ }
+
+ return true;
+}
+
+JSONRPC_STATUS JsonRpcMethod::checkParameter(const CVariant& requestParameters,
+ const JSONSchemaTypeDefinitionPtr& type,
+ unsigned int position,
+ CVariant& outputParameters,
+ unsigned int& handled,
+ CVariant& errorData)
+{
+ // Let's check if the parameter has been provided
+ if (ParameterExists(requestParameters, type->name, position))
+ {
+ // Get the parameter
+ CVariant parameterValue = GetParameter(requestParameters, type->name, position);
+
+ // Evaluate the type of the parameter
+ JSONRPC_STATUS status = type->Check(parameterValue, outputParameters[type->name], errorData["stack"]);
+ if (status != OK)
+ return status;
+
+ // The parameter was present and valid
+ handled++;
+ }
+ // If the parameter has not been provided but is optional
+ // we can use its default value
+ else if (type->optional)
+ outputParameters[type->name] = type->defaultValue;
+ // The parameter is required but has not been provided => invalid
+ else
+ {
+ errorData["stack"]["name"] = type->name;
+ SchemaValueTypeToJson(type->type, errorData["stack"]["type"]);
+ errorData["stack"]["message"] = "Missing parameter";
+ return InvalidParams;
+ }
+
+ return OK;
+}
+
+void CJSONServiceDescription::ResolveReferences()
+{
+ for (const auto& it : m_types)
+ it.second->ResolveReference();
+}
+
+void CJSONServiceDescription::Cleanup()
+{
+ // reset all of the static data
+ m_notifications.clear();
+ m_actionMap.clear();
+ m_types.clear();
+ m_incompleteDefinitions.clear();
+}
+
+bool CJSONServiceDescription::prepareDescription(std::string &description, CVariant &descriptionObject, std::string &name)
+{
+ if (description.empty())
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Missing JSON Schema definition for \"{}\"", name);
+ return false;
+ }
+
+ if (description.at(0) != '{')
+ description = StringUtils::Format("{{{:s}}}", description);
+
+ // Make sure the method description actually exists and represents an object
+ if (!CJSONVariantParser::Parse(description, descriptionObject) || !descriptionObject.isObject())
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Unable to parse JSON Schema definition for \"{}\"", name);
+ return false;
+ }
+
+ CVariant::const_iterator_map member = descriptionObject.begin_map();
+ if (member != descriptionObject.end_map())
+ name = member->first;
+
+ if (name.empty() ||
+ (!descriptionObject[name].isMember("type") && !descriptionObject[name].isMember("$ref") && !descriptionObject[name].isMember("extends")))
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for \"{}\"", name);
+ return false;
+ }
+
+ return true;
+}
+
+bool CJSONServiceDescription::addMethod(const std::string &jsonMethod, MethodCall method)
+{
+ CVariant descriptionObject;
+ std::string methodName;
+
+ std::string modJsonMethod = jsonMethod;
+ // Make sure the method description actually exists and represents an object
+ if (!prepareDescription(modJsonMethod, descriptionObject, methodName))
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for method \"{}\"", methodName);
+ return false;
+ }
+
+ if (m_actionMap.find(methodName) != m_actionMap.end())
+ {
+ CLog::Log(LOGERROR, "JSONRPC: There already is a method with the name \"{}\"", methodName);
+ return false;
+ }
+
+ std::string type = GetString(descriptionObject[methodName]["type"], "");
+ if (type.compare("method") != 0)
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for method \"{}\"", methodName);
+ return false;
+ }
+
+ if (method == NULL)
+ {
+ unsigned int size = sizeof(m_methodMaps) / sizeof(JsonRpcMethodMap);
+ for (unsigned int index = 0; index < size; index++)
+ {
+ if (methodName.compare(m_methodMaps[index].name) == 0)
+ {
+ method = m_methodMaps[index].method;
+ break;
+ }
+ }
+
+ if (method == NULL)
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Missing implementation for method \"{}\"", methodName);
+ return false;
+ }
+ }
+
+ // Parse the details of the method
+ JsonRpcMethod newMethod;
+ newMethod.name = methodName;
+ newMethod.method = method;
+
+ if (!newMethod.Parse(descriptionObject[newMethod.name]))
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Could not parse method \"{}\"", methodName);
+ if (!newMethod.missingReference.empty())
+ {
+ IncompleteSchemaDefinition incomplete;
+ incomplete.Schema = modJsonMethod;
+ incomplete.Type = SchemaDefinitionMethod;
+ incomplete.Method = method;
+
+ IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(newMethod.missingReference);
+ if (iter == m_incompleteDefinitions.end())
+ m_incompleteDefinitions[newMethod.missingReference] = std::vector<IncompleteSchemaDefinition>();
+
+ CLog::Log(
+ LOGINFO,
+ "JSONRPC: Adding method \"{}\" to list of incomplete definitions (waiting for \"{}\")",
+ methodName, newMethod.missingReference);
+ m_incompleteDefinitions[newMethod.missingReference].push_back(incomplete);
+ }
+
+ return false;
+ }
+
+ m_actionMap.add(newMethod);
+
+ return true;
+}
+
+bool CJSONServiceDescription::AddType(const std::string &jsonType)
+{
+ CVariant descriptionObject;
+ std::string typeName;
+
+ std::string modJsonType = jsonType;
+ if (!prepareDescription(modJsonType, descriptionObject, typeName))
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for type \"{}\"", typeName);
+ return false;
+ }
+
+ if (m_types.find(typeName) != m_types.end())
+ {
+ CLog::Log(LOGERROR, "JSONRPC: There already is a type with the name \"{}\"", typeName);
+ return false;
+ }
+
+ // Make sure the "id" attribute is correctly populated
+ descriptionObject[typeName]["id"] = typeName;
+
+ JSONSchemaTypeDefinitionPtr globalType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ globalType->name = typeName;
+ globalType->ID = typeName;
+ CJSONServiceDescription::addReferenceTypeDefinition(globalType);
+
+ if (!globalType->Parse(descriptionObject[typeName]))
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Could not parse type \"{}\"", typeName);
+ CJSONServiceDescription::removeReferenceTypeDefinition(typeName);
+ if (!globalType->missingReference.empty())
+ {
+ IncompleteSchemaDefinition incomplete;
+ incomplete.Schema = modJsonType;
+ incomplete.Type = SchemaDefinitionType;
+
+ IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(globalType->missingReference);
+ if (iter == m_incompleteDefinitions.end())
+ m_incompleteDefinitions[globalType->missingReference] = std::vector<IncompleteSchemaDefinition>();
+
+ CLog::Log(
+ LOGINFO,
+ "JSONRPC: Adding type \"{}\" to list of incomplete definitions (waiting for \"{}\")",
+ typeName, globalType->missingReference);
+ m_incompleteDefinitions[globalType->missingReference].push_back(incomplete);
+ }
+
+ globalType.reset();
+
+ return false;
+ }
+
+ return true;
+}
+
+bool CJSONServiceDescription::AddMethod(const std::string &jsonMethod, MethodCall method)
+{
+ if (method == NULL)
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Invalid JSONRPC method implementation");
+ return false;
+ }
+
+ return addMethod(jsonMethod, method);
+}
+
+bool CJSONServiceDescription::AddBuiltinMethod(const std::string &jsonMethod)
+{
+ return addMethod(jsonMethod, NULL);
+}
+
+bool CJSONServiceDescription::AddNotification(const std::string &jsonNotification)
+{
+ CVariant descriptionObject;
+ std::string notificationName;
+
+ std::string modJsonNotification = jsonNotification;
+ // Make sure the notification description actually exists and represents an object
+ if (!prepareDescription(modJsonNotification, descriptionObject, notificationName))
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for notification \"{}\"",
+ notificationName);
+ return false;
+ }
+
+ if (m_notifications.find(notificationName) != m_notifications.end())
+ {
+ CLog::Log(LOGERROR, "JSONRPC: There already is a notification with the name \"{}\"",
+ notificationName);
+ return false;
+ }
+
+ std::string type = GetString(descriptionObject[notificationName]["type"], "");
+ if (type.compare("notification") != 0)
+ {
+ CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for notification \"{}\"", notificationName);
+ return false;
+ }
+
+ m_notifications[notificationName] = descriptionObject;
+
+ return true;
+}
+
+bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<CVariant> &values, CVariant::VariantType type /* = CVariant::VariantTypeNull */, const CVariant &defaultValue /* = CVariant::ConstNullVariant */)
+{
+ if (name.empty() || m_types.find(name) != m_types.end() ||
+ values.size() == 0)
+ return false;
+
+ JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ definition->ID = name;
+
+ std::vector<CVariant::VariantType> types;
+ bool autoType = false;
+ if (type == CVariant::VariantTypeNull)
+ autoType = true;
+ else
+ types.push_back(type);
+
+ for (unsigned int index = 0; index < values.size(); index++)
+ {
+ if (autoType)
+ types.push_back(values[index].type());
+ else if (type != CVariant::VariantTypeConstNull && type != values[index].type())
+ return false;
+ }
+ definition->enums.insert(definition->enums.begin(), values.begin(), values.end());
+
+ int schemaType = (int)AnyValue;
+ for (unsigned int index = 0; index < types.size(); index++)
+ {
+ JSONSchemaType currentType;
+ switch (type)
+ {
+ case CVariant::VariantTypeString:
+ currentType = StringValue;
+ break;
+ case CVariant::VariantTypeDouble:
+ currentType = NumberValue;
+ break;
+ case CVariant::VariantTypeInteger:
+ case CVariant::VariantTypeUnsignedInteger:
+ currentType = IntegerValue;
+ break;
+ case CVariant::VariantTypeBoolean:
+ currentType = BooleanValue;
+ break;
+ case CVariant::VariantTypeArray:
+ currentType = ArrayValue;
+ break;
+ case CVariant::VariantTypeObject:
+ currentType = ObjectValue;
+ break;
+ case CVariant::VariantTypeConstNull:
+ currentType = AnyValue;
+ break;
+ default:
+ case CVariant::VariantTypeNull:
+ return false;
+ }
+
+ if (index == 0)
+ schemaType = currentType;
+ else
+ schemaType |= (int)currentType;
+ }
+ definition->type = (JSONSchemaType)schemaType;
+
+ if (defaultValue.type() == CVariant::VariantTypeConstNull)
+ definition->defaultValue = definition->enums.at(0);
+ else
+ definition->defaultValue = defaultValue;
+
+ addReferenceTypeDefinition(definition);
+
+ return true;
+}
+
+bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<std::string> &values)
+{
+ std::vector<CVariant> enums;
+ enums.reserve(values.size());
+ for (const auto& it : values)
+ enums.emplace_back(it);
+
+ return AddEnum(name, enums, CVariant::VariantTypeString);
+}
+
+bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<int> &values)
+{
+ std::vector<CVariant> enums;
+ enums.reserve(values.size());
+ for (const auto& it : values)
+ enums.emplace_back(it);
+
+ return AddEnum(name, enums, CVariant::VariantTypeInteger);
+}
+
+const char* CJSONServiceDescription::GetVersion()
+{
+ return JSONRPC_SERVICE_VERSION;
+}
+
+JSONRPC_STATUS CJSONServiceDescription::Print(CVariant &result, ITransportLayer *transport, IClient *client,
+ bool printDescriptions /* = true */, bool printMetadata /* = false */, bool filterByTransport /* = true */,
+ const std::string &filterByName /* = "" */, const std::string &filterByType /* = "" */, bool printReferences /* = true */)
+{
+ std::map<std::string, JSONSchemaTypeDefinitionPtr> types;
+ CJsonRpcMethodMap methods;
+ std::map<std::string, CVariant> notifications;
+
+ int clientPermissions = client->GetPermissionFlags();
+ int transportCapabilities = transport->GetCapabilities();
+
+ if (filterByName.size() > 0)
+ {
+ std::string name = filterByName;
+
+ if (filterByType == "method")
+ {
+ StringUtils::ToLower(name);
+
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator = m_actionMap.find(name);
+ if (methodIterator != m_actionMap.end() &&
+ (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport))
+ methods.add(methodIterator->second);
+ else
+ return InvalidParams;
+ }
+ else if (filterByType == "namespace")
+ {
+ // append a . delimiter to make sure we check for a namespace
+ StringUtils::ToLower(name);
+ name.append(".");
+
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator;
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = m_actionMap.end();
+ for (methodIterator = m_actionMap.begin(); methodIterator != methodIteratorEnd; methodIterator++)
+ {
+ // Check if the given name is at the very beginning of the method name
+ if (methodIterator->first.find(name) == 0 &&
+ (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport))
+ methods.add(methodIterator->second);
+ }
+
+ if (methods.begin() == methods.end())
+ return InvalidParams;
+ }
+ else if (filterByType == "type")
+ {
+ std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator = m_types.find(name);
+ if (typeIterator != m_types.end())
+ types[typeIterator->first] = typeIterator->second;
+ else
+ return InvalidParams;
+ }
+ else if (filterByType == "notification")
+ {
+ std::map<std::string, CVariant>::const_iterator notificationIterator = m_notifications.find(name);
+ if (notificationIterator != m_notifications.end())
+ notifications[notificationIterator->first] = notificationIterator->second;
+ else
+ return InvalidParams;
+ }
+ else
+ return InvalidParams;
+
+ // If we need to print all referenced types we have to go through all parameters etc
+ if (printReferences)
+ {
+ std::vector<std::string> referencedTypes;
+
+ // Loop through all printed types to get all referenced types
+ std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator;
+ std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIteratorEnd = types.end();
+ for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; ++typeIterator)
+ getReferencedTypes(typeIterator->second, referencedTypes);
+
+ // Loop through all printed method's parameters and return value to get all referenced types
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator;
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end();
+ for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++)
+ {
+ for (unsigned int index = 0; index < methodIterator->second.parameters.size(); index++)
+ getReferencedTypes(methodIterator->second.parameters.at(index), referencedTypes);
+
+ getReferencedTypes(methodIterator->second.returns, referencedTypes);
+ }
+
+ for (unsigned int index = 0; index < referencedTypes.size(); index++)
+ {
+ std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator = m_types.find(referencedTypes.at(index));
+ if (typeIterator != m_types.end())
+ types[typeIterator->first] = typeIterator->second;
+ }
+ }
+ }
+ else
+ {
+ types = m_types;
+ methods = m_actionMap;
+ notifications = m_notifications;
+ }
+
+ // Print the header
+ result["id"] = JSONRPC_SERVICE_ID;
+ result["version"] = JSONRPC_SERVICE_VERSION;
+ result["description"] = JSONRPC_SERVICE_DESCRIPTION;
+
+ std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator;
+ std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIteratorEnd = types.end();
+ for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; ++typeIterator)
+ {
+ CVariant currentType = CVariant(CVariant::VariantTypeObject);
+ typeIterator->second->Print(false, true, true, printDescriptions, currentType);
+
+ result["types"][typeIterator->first] = currentType;
+ }
+
+ // Iterate through all json rpc methods
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator;
+ CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end();
+ for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++)
+ {
+ if ((clientPermissions & methodIterator->second.permission) != methodIterator->second.permission || ((transportCapabilities & methodIterator->second.transportneed) != methodIterator->second.transportneed && filterByTransport))
+ continue;
+
+ CVariant currentMethod = CVariant(CVariant::VariantTypeObject);
+
+ currentMethod["type"] = "method";
+ if (printDescriptions && !methodIterator->second.description.empty())
+ currentMethod["description"] = methodIterator->second.description;
+ if (printMetadata)
+ {
+ CVariant permissions(CVariant::VariantTypeArray);
+ for (int i = ReadData; i <= OPERATION_PERMISSION_ALL; i *= 2)
+ {
+ if ((methodIterator->second.permission & i) == i)
+ permissions.push_back(PermissionToString((OperationPermission)i));
+ }
+
+ if (permissions.size() == 1)
+ currentMethod["permission"] = permissions[0];
+ else
+ currentMethod["permission"] = permissions;
+ }
+
+ currentMethod["params"] = CVariant(CVariant::VariantTypeArray);
+ for (unsigned int paramIndex = 0; paramIndex < methodIterator->second.parameters.size(); paramIndex++)
+ {
+ CVariant param = CVariant(CVariant::VariantTypeObject);
+ methodIterator->second.parameters.at(paramIndex)->Print(true, false, true, printDescriptions, param);
+ currentMethod["params"].append(param);
+ }
+
+ methodIterator->second.returns->Print(false, false, false, printDescriptions, currentMethod["returns"]);
+
+ result["methods"][methodIterator->second.name] = currentMethod;
+ }
+
+ // Print notification description
+ std::map<std::string, CVariant>::const_iterator notificationIterator;
+ std::map<std::string, CVariant>::const_iterator notificationIteratorEnd = notifications.end();
+ for (notificationIterator = notifications.begin(); notificationIterator != notificationIteratorEnd; ++notificationIterator)
+ result["notifications"][notificationIterator->first] = notificationIterator->second[notificationIterator->first];
+
+ return OK;
+}
+
+JSONRPC_STATUS CJSONServiceDescription::CheckCall(const char* const method, const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters)
+{
+ CJsonRpcMethodMap::JsonRpcMethodIterator iter = m_actionMap.find(method);
+ if (iter != m_actionMap.end())
+ return iter->second.Check(requestParameters, transport, client, notification, methodCall, outputParameters);
+
+ return MethodNotFound;
+}
+
+JSONSchemaTypeDefinitionPtr CJSONServiceDescription::GetType(const std::string &identification)
+{
+ std::map<std::string, JSONSchemaTypeDefinitionPtr>::iterator iter = m_types.find(identification);
+ if (iter == m_types.end())
+ return JSONSchemaTypeDefinitionPtr();
+
+ return iter->second;
+}
+
+bool CJSONServiceDescription::parseJSONSchemaType(const CVariant &value, std::vector<JSONSchemaTypeDefinitionPtr>& typeDefinitions, JSONSchemaType &schemaType, std::string &missingReference)
+{
+ missingReference.clear();
+ schemaType = AnyValue;
+
+ if (value.isArray())
+ {
+ int parsedType = 0;
+ // If the defined type is an array, we have
+ // to handle a union type
+ for (unsigned int typeIndex = 0; typeIndex < value.size(); typeIndex++)
+ {
+ JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition());
+ // If the type is a string try to parse it
+ if (value[typeIndex].isString())
+ definition->type = StringToSchemaValueType(value[typeIndex].asString());
+ else if (value[typeIndex].isObject())
+ {
+ if (!definition->Parse(value[typeIndex]))
+ {
+ missingReference = definition->missingReference;
+ CLog::Log(LOGERROR, "JSONRPC: Invalid type schema in union type definition");
+ return false;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGWARNING, "JSONRPC: Invalid type in union type definition");
+ return false;
+ }
+
+ definition->optional = false;
+ typeDefinitions.push_back(definition);
+ parsedType |= definition->type;
+ }
+
+ // If the type has not been set yet set it to "any"
+ if (parsedType != 0)
+ schemaType = (JSONSchemaType)parsedType;
+
+ return true;
+ }
+
+ if (value.isString())
+ {
+ schemaType = StringToSchemaValueType(value.asString());
+ return true;
+ }
+
+ return false;
+}
+
+void CJSONServiceDescription::addReferenceTypeDefinition(
+ const JSONSchemaTypeDefinitionPtr& typeDefinition)
+{
+ // If the given json value is no object or does not contain an "id" field
+ // of type string it is no valid type definition
+ if (typeDefinition->ID.empty())
+ return;
+
+ // If the id has already been defined we ignore the type definition
+ if (m_types.find(typeDefinition->ID) != m_types.end())
+ return;
+
+ // Add the type to the list of type definitions
+ m_types[typeDefinition->ID] = typeDefinition;
+
+ IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(typeDefinition->ID);
+ if (iter == m_incompleteDefinitions.end())
+ return;
+
+ CLog::Log(LOGINFO, "JSONRPC: Resolving incomplete types/methods referencing {}",
+ typeDefinition->ID);
+ for (unsigned int index = 0; index < iter->second.size(); index++)
+ {
+ if (iter->second[index].Type == SchemaDefinitionType)
+ AddType(iter->second[index].Schema);
+ else
+ AddMethod(iter->second[index].Schema, iter->second[index].Method);
+ }
+
+ m_incompleteDefinitions.erase(typeDefinition->ID);
+}
+
+void CJSONServiceDescription::removeReferenceTypeDefinition(const std::string &typeID)
+{
+ if (typeID.empty())
+ return;
+
+ std::map<std::string, JSONSchemaTypeDefinitionPtr>::iterator type = m_types.find(typeID);
+ if (type != m_types.end())
+ m_types.erase(type);
+}
+
+void CJSONServiceDescription::getReferencedTypes(const JSONSchemaTypeDefinitionPtr& type,
+ std::vector<std::string>& referencedTypes)
+{
+ // If the current type is a referenceable object, we can add it to the list
+ if (type->ID.size() > 0)
+ {
+ for (unsigned int index = 0; index < referencedTypes.size(); index++)
+ {
+ // The referenceable object has already been added to the list so we can just skip it
+ if (type->ID == referencedTypes.at(index))
+ return;
+ }
+
+ referencedTypes.push_back(type->ID);
+ }
+
+ // If the current type is an object we need to check its properties
+ if (HasType(type->type, ObjectValue))
+ {
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iter;
+ JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iterEnd = type->properties.end();
+ for (iter = type->properties.begin(); iter != iterEnd; ++iter)
+ getReferencedTypes(iter->second, referencedTypes);
+ }
+ // If the current type is an array we need to check its items
+ if (HasType(type->type, ArrayValue))
+ {
+ unsigned int index;
+ for (index = 0; index < type->items.size(); index++)
+ getReferencedTypes(type->items.at(index), referencedTypes);
+
+ for (index = 0; index < type->additionalItems.size(); index++)
+ getReferencedTypes(type->additionalItems.at(index), referencedTypes);
+ }
+
+ // If the current type extends others type we need to check those types
+ for (unsigned int index = 0; index < type->extends.size(); index++)
+ getReferencedTypes(type->extends.at(index), referencedTypes);
+
+ // If the current type is a union type we need to check those types
+ for (unsigned int index = 0; index < type->unionTypes.size(); index++)
+ getReferencedTypes(type->unionTypes.at(index), referencedTypes);
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::CJsonRpcMethodMap():
+ m_actionmap(std::map<std::string, JsonRpcMethod>())
+{
+}
+
+void CJSONServiceDescription::CJsonRpcMethodMap::clear()
+{
+ m_actionmap.clear();
+}
+
+void CJSONServiceDescription::CJsonRpcMethodMap::add(const JsonRpcMethod &method)
+{
+ std::string name = method.name;
+ StringUtils::ToLower(name);
+ m_actionmap[name] = method;
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::begin() const
+{
+ return m_actionmap.begin();
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::find(const std::string& key) const
+{
+ return m_actionmap.find(key);
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::end() const
+{
+ return m_actionmap.end();
+}