2 * Copyright (C) 2005-2012 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 #include "ServiceDescription.h"
23 #include "JSONServiceDescription.h"
24 #include "utils/log.h"
25 #include "utils/StdString.h"
26 #include "utils/JSONVariantParser.h"
28 #include "PlayerOperations.h"
29 #include "PlaylistOperations.h"
30 #include "FileOperations.h"
31 #include "AudioLibrary.h"
32 #include "VideoLibrary.h"
33 #include "GUIOperations.h"
34 #include "SystemOperations.h"
35 #include "InputOperations.h"
36 #include "XBMCOperations.h"
37 #include "ApplicationOperations.h"
40 using namespace JSONRPC;
42 std::map<std::string, CVariant> CJSONServiceDescription::m_notifications = std::map<std::string, CVariant>();
43 CJSONServiceDescription::CJsonRpcMethodMap CJSONServiceDescription::m_actionMap;
44 std::map<std::string, JSONSchemaTypeDefinition> CJSONServiceDescription::m_types = std::map<std::string, JSONSchemaTypeDefinition>();
45 CJSONServiceDescription::IncompleteSchemaDefinitionMap CJSONServiceDescription::m_incompleteDefinitions = CJSONServiceDescription::IncompleteSchemaDefinitionMap();
47 JsonRpcMethodMap CJSONServiceDescription::m_methodMaps[] = {
49 { "JSONRPC.Introspect", CJSONRPC::Introspect },
50 { "JSONRPC.Version", CJSONRPC::Version },
51 { "JSONRPC.Permission", CJSONRPC::Permission },
52 { "JSONRPC.Ping", CJSONRPC::Ping },
53 { "JSONRPC.GetConfiguration", CJSONRPC::GetConfiguration },
54 { "JSONRPC.SetConfiguration", CJSONRPC::SetConfiguration },
55 { "JSONRPC.NotifyAll", CJSONRPC::NotifyAll },
58 { "Player.GetActivePlayers", CPlayerOperations::GetActivePlayers },
59 { "Player.GetProperties", CPlayerOperations::GetProperties },
60 { "Player.GetItem", CPlayerOperations::GetItem },
62 { "Player.PlayPause", CPlayerOperations::PlayPause },
63 { "Player.Stop", CPlayerOperations::Stop },
64 { "Player.SetSpeed", CPlayerOperations::SetSpeed },
65 { "Player.Seek", CPlayerOperations::Seek },
67 { "Player.MoveLeft", CPlayerOperations::MoveLeft },
68 { "Player.MoveRight", CPlayerOperations::MoveRight },
69 { "Player.MoveDown", CPlayerOperations::MoveDown },
70 { "Player.MoveUp", CPlayerOperations::MoveUp },
72 { "Player.ZoomOut", CPlayerOperations::ZoomOut },
73 { "Player.ZoomIn", CPlayerOperations::ZoomIn },
74 { "Player.Zoom", CPlayerOperations::Zoom },
75 { "Player.Rotate", CPlayerOperations::Rotate },
77 { "Player.Open", CPlayerOperations::Open },
78 { "Player.GoPrevious", CPlayerOperations::GoPrevious },
79 { "Player.GoNext", CPlayerOperations::GoNext },
80 { "Player.GoTo", CPlayerOperations::GoTo },
81 { "Player.Shuffle", CPlayerOperations::Shuffle },
82 { "Player.UnShuffle", CPlayerOperations::UnShuffle },
83 { "Player.Repeat", CPlayerOperations::Repeat },
85 { "Player.SetAudioStream", CPlayerOperations::SetAudioStream },
86 { "Player.SetSubtitle", CPlayerOperations::SetSubtitle },
89 { "Playlist.GetPlaylists", CPlaylistOperations::GetPlaylists },
90 { "Playlist.GetProperties", CPlaylistOperations::GetProperties },
91 { "Playlist.GetItems", CPlaylistOperations::GetItems },
92 { "Playlist.Add", CPlaylistOperations::Add },
93 { "Playlist.Insert", CPlaylistOperations::Insert },
94 { "Playlist.Clear", CPlaylistOperations::Clear },
95 { "Playlist.Remove", CPlaylistOperations::Remove },
96 { "Playlist.Swap", CPlaylistOperations::Swap },
99 { "Files.GetSources", CFileOperations::GetRootDirectory },
100 { "Files.GetDirectory", CFileOperations::GetDirectory },
101 { "Files.PrepareDownload", CFileOperations::PrepareDownload },
102 { "Files.Download", CFileOperations::Download },
105 { "AudioLibrary.GetArtists", CAudioLibrary::GetArtists },
106 { "AudioLibrary.GetArtistDetails", CAudioLibrary::GetArtistDetails },
107 { "AudioLibrary.GetAlbums", CAudioLibrary::GetAlbums },
108 { "AudioLibrary.GetAlbumDetails", CAudioLibrary::GetAlbumDetails },
109 { "AudioLibrary.GetSongs", CAudioLibrary::GetSongs },
110 { "AudioLibrary.GetSongDetails", CAudioLibrary::GetSongDetails },
111 { "AudioLibrary.GetRecentlyAddedAlbums", CAudioLibrary::GetRecentlyAddedAlbums },
112 { "AudioLibrary.GetRecentlyAddedSongs", CAudioLibrary::GetRecentlyAddedSongs },
113 { "AudioLibrary.GetRecentlyPlayedAlbums", CAudioLibrary::GetRecentlyPlayedAlbums },
114 { "AudioLibrary.GetRecentlyPlayedSongs", CAudioLibrary::GetRecentlyPlayedSongs },
115 { "AudioLibrary.GetGenres", CAudioLibrary::GetGenres },
116 { "AudioLibrary.SetArtistDetails", CAudioLibrary::SetArtistDetails },
117 { "AudioLibrary.SetAlbumDetails", CAudioLibrary::SetAlbumDetails },
118 { "AudioLibrary.SetSongDetails", CAudioLibrary::SetSongDetails },
119 { "AudioLibrary.Scan", CAudioLibrary::Scan },
120 { "AudioLibrary.Export", CAudioLibrary::Export },
121 { "AudioLibrary.Clean", CAudioLibrary::Clean },
124 { "VideoLibrary.GetGenres", CVideoLibrary::GetGenres },
125 { "VideoLibrary.GetMovies", CVideoLibrary::GetMovies },
126 { "VideoLibrary.GetMovieDetails", CVideoLibrary::GetMovieDetails },
127 { "VideoLibrary.GetMovieSets", CVideoLibrary::GetMovieSets },
128 { "VideoLibrary.GetMovieSetDetails", CVideoLibrary::GetMovieSetDetails },
129 { "VideoLibrary.GetTVShows", CVideoLibrary::GetTVShows },
130 { "VideoLibrary.GetTVShowDetails", CVideoLibrary::GetTVShowDetails },
131 { "VideoLibrary.GetSeasons", CVideoLibrary::GetSeasons },
132 { "VideoLibrary.GetEpisodes", CVideoLibrary::GetEpisodes },
133 { "VideoLibrary.GetEpisodeDetails", CVideoLibrary::GetEpisodeDetails },
134 { "VideoLibrary.GetMusicVideos", CVideoLibrary::GetMusicVideos },
135 { "VideoLibrary.GetMusicVideoDetails", CVideoLibrary::GetMusicVideoDetails },
136 { "VideoLibrary.GetRecentlyAddedMovies", CVideoLibrary::GetRecentlyAddedMovies },
137 { "VideoLibrary.GetRecentlyAddedEpisodes", CVideoLibrary::GetRecentlyAddedEpisodes },
138 { "VideoLibrary.GetRecentlyAddedMusicVideos", CVideoLibrary::GetRecentlyAddedMusicVideos },
139 { "VideoLibrary.SetMovieDetails", CVideoLibrary::SetMovieDetails },
140 { "VideoLibrary.SetTVShowDetails", CVideoLibrary::SetTVShowDetails },
141 { "VideoLibrary.SetEpisodeDetails", CVideoLibrary::SetEpisodeDetails },
142 { "VideoLibrary.SetMusicVideoDetails", CVideoLibrary::SetMusicVideoDetails },
143 { "VideoLibrary.RemoveMovie", CVideoLibrary::RemoveMovie },
144 { "VideoLibrary.RemoveTVShow", CVideoLibrary::RemoveTVShow },
145 { "VideoLibrary.RemoveEpisode", CVideoLibrary::RemoveEpisode },
146 { "VideoLibrary.RemoveMusicVideo", CVideoLibrary::RemoveMusicVideo },
147 { "VideoLibrary.Scan", CVideoLibrary::Scan },
148 { "VideoLibrary.Export", CVideoLibrary::Export },
149 { "VideoLibrary.Clean", CVideoLibrary::Clean },
152 { "GUI.GetProperties", CGUIOperations::GetProperties },
153 { "GUI.ShowNotification", CGUIOperations::ShowNotification },
154 { "GUI.SetFullscreen", CGUIOperations::SetFullscreen },
157 { "System.GetProperties", CSystemOperations::GetProperties },
158 { "System.EjectOpticalDrive", CSystemOperations::EjectOpticalDrive },
159 { "System.Shutdown", CSystemOperations::Shutdown },
160 { "System.Suspend", CSystemOperations::Suspend },
161 { "System.Hibernate", CSystemOperations::Hibernate },
162 { "System.Reboot", CSystemOperations::Reboot },
165 { "Input.SendText", CInputOperations::SendText },
166 { "Input.ExecuteAction", CInputOperations::ExecuteAction },
167 { "Input.Left", CInputOperations::Left },
168 { "Input.Right", CInputOperations::Right },
169 { "Input.Down", CInputOperations::Down },
170 { "Input.Up", CInputOperations::Up },
171 { "Input.Select", CInputOperations::Select },
172 { "Input.Back", CInputOperations::Back },
173 { "Input.ContextMenu", CInputOperations::ContextMenu },
174 { "Input.Info", CInputOperations::Info },
175 { "Input.Home", CInputOperations::Home },
176 { "Input.ShowCodec", CInputOperations::ShowCodec },
177 { "Input.ShowOSD", CInputOperations::ShowOSD },
179 // Application operations
180 { "Application.GetProperties", CApplicationOperations::GetProperties },
181 { "Application.SetVolume", CApplicationOperations::SetVolume },
182 { "Application.SetMute", CApplicationOperations::SetMute },
183 { "Application.Quit", CApplicationOperations::Quit },
186 { "XBMC.GetInfoLabels", CXBMCOperations::GetInfoLabels },
187 { "XBMC.GetInfoBooleans", CXBMCOperations::GetInfoBooleans }
190 JSONSchemaTypeDefinition::JSONSchemaTypeDefinition()
191 : missingReference(""),
192 type(AnyValue), minimum(-std::numeric_limits<double>::max()), maximum(std::numeric_limits<double>::max()),
193 exclusiveMinimum(false), exclusiveMaximum(false), divisibleBy(0),
194 minLength(-1), maxLength(-1),
195 minItems(0), maxItems(0), uniqueItems(false),
196 hasAdditionalProperties(false), additionalProperties(NULL)
199 bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* = false */)
201 bool isReferenceType = false;
202 bool hasReference = false;
204 // Check if the type of the parameter defines a json reference
205 // to a type defined somewhere else
206 if (value.isMember("$ref") && value["$ref"].isString())
208 // Get the name of the referenced type
209 std::string refType = value["$ref"].asString();
210 // Check if the referenced type exists
211 JSONSchemaTypeDefinition *referencedType = CJSONServiceDescription::GetType(refType);
212 if (refType.length() <= 0 || referencedType == NULL)
214 CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type %s references an unknown type %s", name.c_str(), refType.c_str());
215 missingReference = refType;
219 std::string typeName = name;
220 *this = *referencedType;
221 if (!typeName.empty())
225 else if (value.isMember("id") && value["id"].isString())
227 ID = GetString(value["id"], "");
228 isReferenceType = true;
231 // Check if the "required" field has been defined
232 optional = value.isMember("required") && value["required"].isBoolean() ? !value["required"].asBoolean() : true;
234 // Get the "description"
235 if (!hasReference || (value.isMember("description") && value["description"].isString()))
236 description = GetString(value["description"], "");
240 // If there is a specific default value, read it
241 if (value.isMember("default") && IsType(value["default"], type))
244 if (enums.size() <= 0)
246 // If the type has an enum definition we must make
247 // sure that the default value is a valid enum value
250 for (std::vector<CVariant>::const_iterator itr = enums.begin(); itr != enums.end(); itr++)
252 if (value["default"] == *itr)
261 defaultValue = value["default"];
267 // Check whether this type extends an existing type
268 if (value.isMember("extends"))
270 if (value["extends"].isString())
272 std::string extendsName = GetString(value["extends"], "");
273 if (!extendsName.empty())
275 JSONSchemaTypeDefinition *referencedType = CJSONServiceDescription::GetType(extendsName);
276 if (referencedType == NULL)
278 CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type %s extends an unknown type %s", name.c_str(), extendsName.c_str());
279 missingReference = extendsName;
283 type = referencedType->type;
284 extends.push_back(*referencedType);
287 else if (value["extends"].isArray())
289 JSONSchemaType extendedType = AnyValue;
290 for (unsigned int extendsIndex = 0; extendsIndex < value["extends"].size(); extendsIndex++)
292 std::string extendsName = GetString(value["extends"][extendsIndex], "");
293 if (!extendsName.empty())
295 JSONSchemaTypeDefinition *referencedType = CJSONServiceDescription::GetType(extendsName);
296 if (referencedType == NULL)
299 CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type %s extends an unknown type %s", name.c_str(), extendsName.c_str());
300 missingReference = extendsName;
304 if (extendsIndex == 0)
305 extendedType = referencedType->type;
306 else if (extendedType != referencedType->type)
309 CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type %s extends multiple JSON schema types of mismatching types", name.c_str());
313 extends.push_back(*referencedType);
321 // Only read the "type" attribute if it's
322 // not an extending type
323 if (extends.size() <= 0)
325 // Get the defined type of the parameter
326 if (!CJSONServiceDescription::parseJSONSchemaType(value["type"], unionTypes, type, missingReference))
330 if (HasType(type, ObjectValue))
332 // If the type definition is of type "object"
333 // and has a "properties" definition we need
334 // to handle these as well
335 if (value.isMember("properties") && value["properties"].isObject())
337 // Get all child elements of the "properties"
338 // object and loop through them
339 for (CVariant::const_iterator_map itr = value["properties"].begin_map(); itr != value["properties"].end_map(); itr++)
341 // Create a new type definition, store the name
342 // of the current property into it, parse it
343 // recursively and add its default value
344 // to the current type's default value
345 JSONSchemaTypeDefinition propertyType;
346 propertyType.name = itr->first;
347 if (!propertyType.Parse(itr->second))
349 missingReference = propertyType.missingReference;
352 defaultValue[itr->first] = propertyType.defaultValue;
353 properties.add(propertyType);
357 hasAdditionalProperties = true;
358 additionalProperties = new JSONSchemaTypeDefinition();
359 if (value.isMember("additionalProperties"))
361 if (value["additionalProperties"].isBoolean())
363 hasAdditionalProperties = value["additionalProperties"].asBoolean();
364 if (!hasAdditionalProperties)
366 delete additionalProperties;
367 additionalProperties = NULL;
370 else if (value["additionalProperties"].isObject() && !value["additionalProperties"].isNull())
372 if (!additionalProperties->Parse(value["additionalProperties"]))
374 missingReference = additionalProperties->missingReference;
375 hasAdditionalProperties = false;
376 delete additionalProperties;
377 additionalProperties = NULL;
379 CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties schema definition in type %s", name.c_str());
385 CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties definition in type %s", name.c_str());
391 // If the defined parameter is an array
392 // we need to check for detailed definitions
393 // of the array items
394 if (HasType(type, ArrayValue))
396 // Check for "uniqueItems" field
397 if (value.isMember("uniqueItems") && value["uniqueItems"].isBoolean())
398 uniqueItems = value["uniqueItems"].asBoolean();
402 // Check for "additionalItems" field
403 if (value.isMember("additionalItems"))
405 // If it is an object, there is only one schema for it
406 if (value["additionalItems"].isObject())
408 JSONSchemaTypeDefinition additionalItem;
409 if (additionalItem.Parse(value["additionalItems"]))
410 additionalItems.push_back(additionalItem);
413 CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value for type %s", name.c_str());
414 missingReference = additionalItem.missingReference;
418 // If it is an array there may be multiple schema definitions
419 else if (value["additionalItems"].isArray())
421 for (unsigned int itemIndex = 0; itemIndex < value["additionalItems"].size(); itemIndex++)
423 JSONSchemaTypeDefinition additionalItem;
425 if (additionalItem.Parse(value["additionalItems"][itemIndex]))
426 additionalItems.push_back(additionalItem);
429 CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value (item %d) for type %s", itemIndex, name.c_str());
430 missingReference = additionalItem.missingReference;
435 // If it is not a (array of) schema and not a bool (default value is false)
436 // it has an invalid value
437 else if (!value["additionalItems"].isBoolean())
439 CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" definition for type %s", name.c_str());
444 // If the "items" field is a single object
445 // we can parse that directly
446 if (value.isMember("items"))
448 if (value["items"].isObject())
450 JSONSchemaTypeDefinition item;
451 if (!item.Parse(value["items"]))
453 CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" for type %s", name.c_str());
454 missingReference = item.missingReference;
457 items.push_back(item);
459 // Otherwise if it is an array we need to
460 // parse all elements and store them
461 else if (value["items"].isArray())
463 for (CVariant::const_iterator_array itemItr = value["items"].begin_array(); itemItr != value["items"].end_array(); itemItr++)
465 JSONSchemaTypeDefinition item;
466 if (!item.Parse(*itemItr))
468 CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" array for type %s", name.c_str());
469 missingReference = item.missingReference;
472 items.push_back(item);
477 minItems = (unsigned int)value["minItems"].asUnsignedInteger(0);
478 maxItems = (unsigned int)value["maxItems"].asUnsignedInteger(0);
481 if (HasType(type, NumberValue) || HasType(type, IntegerValue))
483 if ((type & NumberValue) == NumberValue)
485 minimum = value["minimum"].asDouble(-numeric_limits<double>::max());
486 maximum = value["maximum"].asDouble(numeric_limits<double>::max());
488 else if ((type & IntegerValue) == IntegerValue)
490 minimum = (double)value["minimum"].asInteger(numeric_limits<int>::min());
491 maximum = (double)value["maximum"].asInteger(numeric_limits<int>::max());
494 exclusiveMinimum = value["exclusiveMinimum"].asBoolean(false);
495 exclusiveMaximum = value["exclusiveMaximum"].asBoolean(false);
496 divisibleBy = (unsigned int)value["divisibleBy"].asUnsignedInteger(0);
499 if (HasType(type, StringValue))
501 minLength = (int)value["minLength"].asInteger(-1);
502 maxLength = (int)value["maxLength"].asInteger(-1);
505 // If the type definition is neither an
506 // "object" nor an "array" we can check
507 // for an "enum" definition
508 if (value.isMember("enum") && value["enum"].isArray())
510 // Loop through all elements in the "enum" array
511 for (CVariant::const_iterator_array enumItr = value["enum"].begin_array(); enumItr != value["enum"].end_array(); enumItr++)
513 // Check for duplicates and eliminate them
514 bool approved = true;
515 for (unsigned int approvedIndex = 0; approvedIndex < enums.size(); approvedIndex++)
517 if (*enumItr == enums.at(approvedIndex))
524 // Only add the current item to the enum value
525 // list if it is not duplicate
527 enums.push_back(*enumItr);
531 if (type != ObjectValue)
533 // If there is a definition for a default value and its type
534 // matches the type of the parameter we can parse it
536 if (value.isMember("default") && IsType(value["default"], type))
538 if (enums.size() <= 0)
540 // If the type has an enum definition we must make
541 // sure that the default value is a valid enum value
544 for (std::vector<CVariant>::const_iterator itr = enums.begin(); itr != enums.end(); itr++)
546 if (value["default"] == *itr)
556 defaultValue = value["default"];
559 // If the type of the default value definition does not
560 // match the type of the parameter we have to log this
561 if (value.isMember("default") && !IsType(value["default"], type))
562 CLog::Log(LOGDEBUG, "JSONRPC: Parameter %s has an invalid default value", name.c_str());
564 // If the type contains an "enum" we need to get the
565 // default value from the first enum value
566 if (enums.size() > 0)
567 defaultValue = enums.at(0);
568 // otherwise set a default value instead
570 SetDefaultValue(defaultValue, type);
575 CJSONServiceDescription::addReferenceTypeDefinition(*this);
580 JSONRPC_STATUS JSONSchemaTypeDefinition::Check(const CVariant &value, CVariant &outputValue, CVariant &errorData) const
583 errorData["name"] = name;
584 SchemaValueTypeToJson(type, errorData["type"]);
585 CStdString errorMessage;
587 // Let's check the type of the provided parameter
588 if (!IsType(value, type))
590 CLog::Log(LOGDEBUG, "JSONRPC: Type mismatch in type %s", name.c_str());
591 errorMessage.Format("Invalid type %s received", ValueTypeToString(value.type()));
592 errorData["message"] = errorMessage.c_str();
593 return InvalidParams;
595 else if (value.isNull() && !HasType(type, NullValue))
597 CLog::Log(LOGDEBUG, "JSONRPC: Value is NULL in type %s", name.c_str());
598 errorData["message"] = "Received value is null";
599 return InvalidParams;
602 // Let's check if we have to handle a union type
603 if (unionTypes.size() > 0)
606 for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++)
609 CVariant testOutput = outputValue;
610 if (unionTypes.at(unionIndex).Check(value, testOutput, dummyError) == OK)
613 outputValue = testOutput;
620 CLog::Log(LOGDEBUG, "JSONRPC: Value in type %s does not match any of the union type definitions", name.c_str());
621 errorData["message"] = "Received value does not match any of the union type definitions";
622 return InvalidParams;
626 // First we need to check if this type extends another
627 // type and if so we need to check against the extended
629 if (extends.size() > 0)
631 for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++)
633 JSONRPC_STATUS status = extends.at(extendsIndex).Check(value, outputValue, errorData);
637 CLog::Log(LOGDEBUG, "JSONRPC: Value does not match extended type %s of type %s", extends.at(extendsIndex).ID.c_str(), name.c_str());
638 errorMessage.Format("value does not match extended type %s", extends.at(extendsIndex).ID.c_str(), name.c_str());
639 errorData["message"] = errorMessage.c_str();
645 // If it is an array we need to
646 // - check the type of every element ("items")
647 // - check if they need to be unique ("uniqueItems")
648 if (HasType(type, ArrayValue) && value.isArray())
650 outputValue = CVariant(CVariant::VariantTypeArray);
651 // Check the number of items against minItems and maxItems
652 if ((minItems > 0 && value.size() < minItems) || (maxItems > 0 && value.size() > maxItems))
654 CLog::Log(LOGDEBUG, "JSONRPC: Number of array elements does not match minItems and/or maxItems in type %s", name.c_str());
655 if (minItems > 0 && maxItems > 0)
656 errorMessage.Format("Between %d and %d array items expected but %d received", minItems, maxItems, value.size());
657 else if (minItems > 0)
658 errorMessage.Format("At least %d array items expected but only %d received", minItems, value.size());
660 errorMessage.Format("Only %d array items expected but %d received", maxItems, value.size());
661 errorData["message"] = errorMessage.c_str();
662 return InvalidParams;
665 if (items.size() == 0)
667 else if (items.size() == 1)
669 JSONSchemaTypeDefinition itemType = items.at(0);
671 // Loop through all array elements
672 for (unsigned int arrayIndex = 0; arrayIndex < value.size(); arrayIndex++)
675 JSONRPC_STATUS status = itemType.Check(value[arrayIndex], temp, errorData["property"]);
676 outputValue.push_back(temp);
679 CLog::Log(LOGDEBUG, "JSONRPC: Array element at index %u does not match in type %s", arrayIndex, name.c_str());
680 errorMessage.Format("array element at index %u does not match", arrayIndex);
681 errorData["message"] = errorMessage.c_str();
686 // We have more than one element in "items"
687 // so we have tuple typing, which means that
688 // every element in the value array must match
689 // with the type at the same position in the
693 // If the number of elements in the value array
694 // does not match the number of elements in the
695 // "items" array and additional items are not
696 // allowed there is no need to check every element
697 if (value.size() < items.size() || (value.size() != items.size() && additionalItems.size() == 0))
699 CLog::Log(LOGDEBUG, "JSONRPC: One of the array elements does not match in type %s", name.c_str());
700 errorMessage.Format("%d array elements expected but %d received", items.size(), value.size());
701 errorData["message"] = errorMessage.c_str();
702 return InvalidParams;
705 // Loop through all array elements until there
706 // are either no more schemas in the "items"
707 // array or no more elements in the value's array
708 unsigned int arrayIndex;
709 for (arrayIndex = 0; arrayIndex < min(items.size(), (size_t)value.size()); arrayIndex++)
711 JSONRPC_STATUS status = items.at(arrayIndex).Check(value[arrayIndex], outputValue[arrayIndex], errorData["property"]);
714 CLog::Log(LOGDEBUG, "JSONRPC: Array element at index %u does not match with items schema in type %s", arrayIndex, name.c_str());
719 if (additionalItems.size() > 0)
721 // Loop through the rest of the elements
722 // in the array and check them against the
724 for (; arrayIndex < value.size(); arrayIndex++)
727 for (unsigned int additionalIndex = 0; additionalIndex < additionalItems.size(); additionalIndex++)
730 if (additionalItems.at(additionalIndex).Check(value[arrayIndex], outputValue[arrayIndex], dummyError) == OK)
739 CLog::Log(LOGDEBUG, "JSONRPC: Array contains non-conforming additional items in type %s", name.c_str());
740 errorMessage.Format("Array element at index %u does not match the \"additionalItems\" schema", arrayIndex);
741 errorData["message"] = errorMessage.c_str();
742 return InvalidParams;
748 // If every array element is unique we need to check each one
751 for (unsigned int checkingIndex = 0; checkingIndex < outputValue.size(); checkingIndex++)
753 for (unsigned int checkedIndex = checkingIndex + 1; checkedIndex < outputValue.size(); checkedIndex++)
755 // If two elements are the same they are not unique
756 if (outputValue[checkingIndex] == outputValue[checkedIndex])
758 CLog::Log(LOGDEBUG, "JSONRPC: Not unique array element at index %u and %u in type %s", checkingIndex, checkedIndex, name.c_str());
759 errorMessage.Format("Array element at index %u is not unique (same as array element at index %u)", checkingIndex, checkedIndex);
760 errorData["message"] = errorMessage.c_str();
761 return InvalidParams;
770 // If it is an object we need to check every element
771 // against the defined "properties"
772 if (HasType(type, ObjectValue) && value.isObject())
774 unsigned int handled = 0;
775 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end();
776 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator;
777 for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; propertiesIterator++)
779 if (value.isMember(propertiesIterator->second.name))
781 JSONRPC_STATUS status = propertiesIterator->second.Check(value[propertiesIterator->second.name], outputValue[propertiesIterator->second.name], errorData["property"]);
784 CLog::Log(LOGDEBUG, "JSONRPC: Invalid property \"%s\" in type %s", propertiesIterator->second.name.c_str(), name.c_str());
789 else if (propertiesIterator->second.optional)
790 outputValue[propertiesIterator->second.name] = propertiesIterator->second.defaultValue;
793 CLog::Log(LOGDEBUG, "JSONRPC: Missing property \"%s\" in type %s", propertiesIterator->second.name.c_str(), name.c_str());
794 errorData["property"]["name"] = propertiesIterator->second.name.c_str();
795 errorData["property"]["type"] = SchemaValueTypeToString(propertiesIterator->second.type);
796 errorData["message"] = "Missing property";
797 return InvalidParams;
801 // Additional properties are not allowed
802 if (handled < value.size())
804 // If additional properties are allowed we need to check if
805 // they match the defined schema
806 if (hasAdditionalProperties && additionalProperties != NULL)
808 CVariant::const_iterator_map iter;
809 CVariant::const_iterator_map iterEnd = value.end_map();
810 for (iter = value.begin_map(); iter != iterEnd; iter++)
812 if (properties.find(iter->first) != properties.end())
815 // If the additional property is of type "any"
816 // we can simply copy its value to the output
818 if (additionalProperties->type == AnyValue)
820 outputValue[iter->first] = value[iter->first];
824 JSONRPC_STATUS status = additionalProperties->Check(value[iter->first], outputValue[iter->first], errorData["property"]);
827 CLog::Log(LOGDEBUG, "JSONRPC: Invalid additional property \"%s\" in type %s", iter->first.c_str(), name.c_str());
832 // If we still have unchecked properties but additional
833 // properties are not allowed, we have invalid parameters
834 else if (!hasAdditionalProperties || additionalProperties == NULL)
836 errorData["message"] = "Unexpected additional properties received";
837 errorData.erase("property");
838 return InvalidParams;
845 // It's neither an array nor an object
847 // If it can only take certain values ("enum")
848 // we need to check against those
849 if (enums.size() > 0)
852 for (std::vector<CVariant>::const_iterator enumItr = enums.begin(); enumItr != enums.end(); enumItr++)
854 if (*enumItr == value)
863 CLog::Log(LOGDEBUG, "JSONRPC: Value does not match any of the enum values in type %s", name.c_str());
864 errorData["message"] = "Received value does not match any of the defined enum values";
865 return InvalidParams;
869 // If we have a number or an integer type, we need
870 // to check the minimum and maximum values
871 if ((HasType(type, NumberValue) && value.isDouble()) || (HasType(type, IntegerValue) && value.isInteger()))
874 if (value.isDouble())
875 numberValue = value.asDouble();
877 numberValue = (double)value.asInteger();
879 if ((exclusiveMinimum && numberValue <= minimum) || (!exclusiveMinimum && numberValue < minimum) ||
881 (exclusiveMaximum && numberValue >= maximum) || (!exclusiveMaximum && numberValue > maximum))
883 CLog::Log(LOGDEBUG, "JSONRPC: Value does not lay between minimum and maximum in type %s", name.c_str());
884 if (value.isDouble())
885 errorMessage.Format("Value between %f (%s) and %f (%s) expected but %f received",
886 minimum, exclusiveMinimum ? "exclusive" : "inclusive", maximum, exclusiveMaximum ? "exclusive" : "inclusive", numberValue);
888 errorMessage.Format("Value between %d (%s) and %d (%s) expected but %d received",
889 (int)minimum, exclusiveMinimum ? "exclusive" : "inclusive", (int)maximum, exclusiveMaximum ? "exclusive" : "inclusive", (int)numberValue);
890 errorData["message"] = errorMessage.c_str();
891 return InvalidParams;
894 if ((HasType(type, IntegerValue) && divisibleBy > 0 && ((int)numberValue % divisibleBy) != 0))
896 CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet divisibleBy requirements in type %s", name.c_str());
897 errorMessage.Format("Value should be divisible by %d but %d received", divisibleBy, (int)numberValue);
898 errorData["message"] = errorMessage.c_str();
899 return InvalidParams;
903 // If we have a string, we need to check the length
904 if (HasType(type, StringValue) && value.isString())
906 int size = value.asString().size();
907 if (size < minLength)
909 CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet minLength requirements in type %s", name.c_str());
910 errorMessage.Format("Value should have a minimum length of %d but has a length of %d", minLength, size);
911 errorData["message"] = errorMessage.c_str();
912 return InvalidParams;
915 if (maxLength >= 0 && size > maxLength)
917 CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet maxLength requirements in type %s", name.c_str());
918 errorMessage.Format("Value should have a maximum length of %d but has a length of %d", maxLength, size);
919 errorData["message"] = errorMessage.c_str();
920 return InvalidParams;
924 // Otherwise it can have any value
929 void JSONSchemaTypeDefinition::Print(bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, CVariant &output) const
931 bool typeReference = false;
933 // Printing general fields
935 output["name"] = name;
939 else if (!ID.empty())
942 typeReference = true;
945 if (printDescriptions && !description.empty())
946 output["description"] = description;
948 if (isParameter || printDefault)
951 output["required"] = true;
952 if (optional && type != ObjectValue && type != ArrayValue)
953 output["default"] = defaultValue;
958 if (extends.size() == 1)
960 output["extends"] = extends.at(0).ID;
962 else if (extends.size() > 1)
964 output["extends"] = CVariant(CVariant::VariantTypeArray);
965 for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++)
966 output["extends"].append(extends.at(extendsIndex).ID);
968 else if (unionTypes.size() > 0)
970 output["type"] = CVariant(CVariant::VariantTypeArray);
971 for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++)
973 CVariant unionOutput = CVariant(CVariant::VariantTypeObject);
974 unionTypes.at(unionIndex).Print(false, false, false, printDescriptions, unionOutput);
975 output["type"].append(unionOutput);
979 CJSONUtils::SchemaValueTypeToJson(type, output["type"]);
981 // Printing enum field
982 if (enums.size() > 0)
984 output["enums"] = CVariant(CVariant::VariantTypeArray);
985 for (unsigned int enumIndex = 0; enumIndex < enums.size(); enumIndex++)
986 output["enums"].append(enums.at(enumIndex));
989 // Printing integer/number fields
990 if (CJSONUtils::HasType(type, IntegerValue) || CJSONUtils::HasType(type, NumberValue))
992 if (CJSONUtils::HasType(type, NumberValue))
994 if (minimum > -numeric_limits<double>::max())
995 output["minimum"] = minimum;
996 if (maximum < numeric_limits<double>::max())
997 output["maximum"] = maximum;
1001 if (minimum > numeric_limits<int>::min())
1002 output["minimum"] = (int)minimum;
1003 if (maximum < numeric_limits<int>::max())
1004 output["maximum"] = (int)maximum;
1007 if (exclusiveMinimum)
1008 output["exclusiveMinimum"] = true;
1009 if (exclusiveMaximum)
1010 output["exclusiveMaximum"] = true;
1011 if (divisibleBy > 0)
1012 output["divisibleBy"] = divisibleBy;
1014 if (CJSONUtils::HasType(type, StringValue))
1017 output["minLength"] = minLength;
1019 output["maxLength"] = maxLength;
1022 // Print array fields
1023 if (CJSONUtils::HasType(type, ArrayValue))
1025 if (items.size() == 1)
1027 items.at(0).Print(false, false, false, printDescriptions, output["items"]);
1029 else if (items.size() > 1)
1031 output["items"] = CVariant(CVariant::VariantTypeArray);
1032 for (unsigned int itemIndex = 0; itemIndex < items.size(); itemIndex++)
1034 CVariant item = CVariant(CVariant::VariantTypeObject);
1035 items.at(itemIndex).Print(false, false, false, printDescriptions, item);
1036 output["items"].append(item);
1041 output["minItems"] = minItems;
1043 output["maxItems"] = maxItems;
1045 if (additionalItems.size() == 1)
1047 additionalItems.at(0).Print(false, false, false, printDescriptions, output["additionalItems"]);
1049 else if (additionalItems.size() > 1)
1051 output["additionalItems"] = CVariant(CVariant::VariantTypeArray);
1052 for (unsigned int addItemIndex = 0; addItemIndex < additionalItems.size(); addItemIndex++)
1054 CVariant item = CVariant(CVariant::VariantTypeObject);
1055 additionalItems.at(addItemIndex).Print(false, false, false, printDescriptions, item);
1056 output["additionalItems"].append(item);
1061 output["uniqueItems"] = true;
1064 // Print object fields
1065 if (CJSONUtils::HasType(type, ObjectValue))
1067 if (properties.size() > 0)
1069 output["properties"] = CVariant(CVariant::VariantTypeObject);
1071 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end();
1072 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator;
1073 for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; propertiesIterator++)
1075 propertiesIterator->second.Print(false, false, true, printDescriptions, output["properties"][propertiesIterator->first]);
1079 if (!hasAdditionalProperties)
1080 output["additionalProperties"] = false;
1081 else if (additionalProperties != NULL && additionalProperties->type != AnyValue)
1082 additionalProperties->Print(false, false, true, printDescriptions, output["additionalProperties"]);
1087 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::CJsonSchemaPropertiesMap()
1089 m_propertiesmap = std::map<std::string, JSONSchemaTypeDefinition>();
1092 void JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::add(const JSONSchemaTypeDefinition &property)
1094 CStdString name = property.name;
1095 name = name.ToLower();
1096 m_propertiesmap[name] = property;
1099 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::begin() const
1101 return m_propertiesmap.begin();
1104 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::find(const std::string& key) const
1106 return m_propertiesmap.find(key);
1109 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::end() const
1111 return m_propertiesmap.end();
1114 unsigned int JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::size() const
1116 return m_propertiesmap.size();
1119 JsonRpcMethod::JsonRpcMethod()
1120 : missingReference(""), method(NULL)
1123 bool JsonRpcMethod::Parse(const CVariant &value)
1125 // Parse XBMC specific information about the method
1126 if (value.isMember("transport") && value["transport"].isArray())
1129 for (unsigned int index = 0; index < value["transport"].size(); index++)
1130 transport |= StringToTransportLayer(value["transport"][index].asString());
1132 transportneed = (TransportLayerCapability)transport;
1135 transportneed = StringToTransportLayer(value.isMember("transport") ? value["transport"].asString() : "");
1137 if (value.isMember("permission") && value["permission"].isArray())
1139 int permissions = 0;
1140 for (unsigned int index = 0; index < value["permission"].size(); index++)
1141 permissions |= StringToPermission(value["permission"][index].asString());
1143 permission = (OperationPermission)permissions;
1146 permission = StringToPermission(value.isMember("permission") ? value["permission"].asString() : "");
1148 description = GetString(value["description"], "");
1150 // Check whether there are parameters defined
1151 if (value.isMember("params") && value["params"].isArray())
1153 // Loop through all defined parameters
1154 for (unsigned int paramIndex = 0; paramIndex < value["params"].size(); paramIndex++)
1156 CVariant parameter = value["params"][paramIndex];
1157 // If the parameter definition does not contain a valid "name" or
1158 // "type" element we will ignore it
1159 if (!parameter.isMember("name") || !parameter["name"].isString() ||
1160 (!parameter.isMember("type") && !parameter.isMember("$ref") && !parameter.isMember("extends")) ||
1161 (parameter.isMember("type") && !parameter["type"].isString() && !parameter["type"].isArray()) ||
1162 (parameter.isMember("$ref") && !parameter["$ref"].isString()) ||
1163 (parameter.isMember("extends") && !parameter["extends"].isString() && !parameter["extends"].isArray()))
1165 CLog::Log(LOGDEBUG, "JSONRPC: Method %s has a badly defined parameter", name.c_str());
1169 // Parse the parameter and add it to the list
1170 // of defined parameters
1171 JSONSchemaTypeDefinition param;
1172 if (!parseParameter(parameter, param))
1174 missingReference = param.missingReference;
1177 parameters.push_back(param);
1181 // Parse the return value of the method
1182 if (!parseReturn(value))
1184 missingReference = returns.missingReference;
1191 JSONRPC_STATUS JsonRpcMethod::Check(const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) const
1193 if (transport != NULL && (transport->GetCapabilities() & transportneed) == transportneed)
1195 if (client != NULL && (client->GetPermissionFlags() & permission) == permission && (!notification || (permission & OPERATION_PERMISSION_NOTIFICATION) == permission))
1197 methodCall = method;
1199 // Count the number of actually handled (present)
1201 unsigned int handled = 0;
1202 CVariant errorData = CVariant(CVariant::VariantTypeObject);
1203 errorData["method"] = name;
1205 // Loop through all the parameters to check
1206 for (unsigned int i = 0; i < parameters.size(); i++)
1208 // Evaluate the current parameter
1209 JSONRPC_STATUS status = checkParameter(requestParameters, parameters.at(i), i, outputParameters, handled, errorData);
1212 // Return the error data object in the outputParameters reference
1213 outputParameters = errorData;
1218 // Check if there were unnecessary parameters
1219 if (handled < requestParameters.size())
1221 errorData["message"] = "Too many parameters";
1222 outputParameters = errorData;
1223 return InvalidParams;
1229 return BadPermission;
1232 return MethodNotFound;
1235 bool JsonRpcMethod::parseParameter(const CVariant &value, JSONSchemaTypeDefinition ¶meter)
1237 parameter.name = GetString(value["name"], "");
1239 // Parse the type and default value of the parameter
1240 return parameter.Parse(value, true);
1243 bool JsonRpcMethod::parseReturn(const CVariant &value)
1245 // Only parse the "returns" definition if there is one
1246 if (!value.isMember("returns"))
1248 returns.type = NullValue;
1252 // If the type of the return value is defined as a simple string we can parse it directly
1253 if (value["returns"].isString())
1254 return CJSONServiceDescription::parseJSONSchemaType(value["returns"], returns.unionTypes, returns.type, missingReference);
1256 // otherwise we have to parse the whole type definition
1257 if (!returns.Parse(value["returns"]))
1259 missingReference = returns.missingReference;
1266 JSONRPC_STATUS JsonRpcMethod::checkParameter(const CVariant &requestParameters, const JSONSchemaTypeDefinition &type, unsigned int position, CVariant &outputParameters, unsigned int &handled, CVariant &errorData)
1268 // Let's check if the parameter has been provided
1269 if (ParameterExists(requestParameters, type.name, position))
1271 // Get the parameter
1272 CVariant parameterValue = GetParameter(requestParameters, type.name, position);
1274 // Evaluate the type of the parameter
1275 JSONRPC_STATUS status = type.Check(parameterValue, outputParameters[type.name], errorData["stack"]);
1279 // The parameter was present and valid
1282 // If the parameter has not been provided but is optional
1283 // we can use its default value
1284 else if (type.optional)
1285 outputParameters[type.name] = type.defaultValue;
1286 // The parameter is required but has not been provided => invalid
1289 errorData["stack"]["name"] = type.name;
1290 SchemaValueTypeToJson(type.type, errorData["stack"]["type"]);
1291 errorData["stack"]["message"] = "Missing parameter";
1292 return InvalidParams;
1298 bool CJSONServiceDescription::prepareDescription(std::string &description, CVariant &descriptionObject, std::string &name)
1300 if (description.empty())
1302 CLog::Log(LOGERROR, "JSONRPC: Missing JSON Schema definition for \"%s\"", name.c_str());
1306 if (description.at(0) != '{')
1309 json.Format("{%s}", description);
1313 descriptionObject = CJSONVariantParser::Parse((const unsigned char *)description.c_str(), description.size());
1315 // Make sure the method description actually exists and represents an object
1316 if (!descriptionObject.isObject())
1318 CLog::Log(LOGERROR, "JSONRPC: Unable to parse JSON Schema definition for \"%s\"", name.c_str());
1322 CVariant::const_iterator_map member = descriptionObject.begin_map();
1323 if (member != descriptionObject.end_map())
1324 name = member->first;
1327 (!descriptionObject[name].isMember("type") && !descriptionObject[name].isMember("$ref") && !descriptionObject[name].isMember("extends")))
1329 CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for \"%s\"", name.c_str());
1336 bool CJSONServiceDescription::addMethod(const std::string &jsonMethod, MethodCall method)
1338 CVariant descriptionObject;
1339 std::string methodName;
1341 std::string modJsonMethod = jsonMethod;
1342 // Make sure the method description actually exists and represents an object
1343 if (!prepareDescription(modJsonMethod, descriptionObject, methodName))
1345 CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for method \"%s\"", methodName.c_str());
1349 if (m_actionMap.find(methodName) != m_actionMap.end())
1351 CLog::Log(LOGERROR, "JSONRPC: There already is a method with the name \"%s\"", methodName.c_str());
1355 std::string type = GetString(descriptionObject[methodName]["type"], "");
1356 if (type.compare("method") != 0)
1358 CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for method \"%s\"", methodName.c_str());
1364 unsigned int size = sizeof(m_methodMaps) / sizeof(JsonRpcMethodMap);
1365 for (unsigned int index = 0; index < size; index++)
1367 if (methodName.compare(m_methodMaps[index].name) == 0)
1369 method = m_methodMaps[index].method;
1376 CLog::Log(LOGERROR, "JSONRPC: Missing implementation for method \"%s\"", methodName.c_str());
1381 // Parse the details of the method
1382 JsonRpcMethod newMethod;
1383 newMethod.name = methodName;
1384 newMethod.method = method;
1386 if (!newMethod.Parse(descriptionObject[newMethod.name]))
1388 CLog::Log(LOGERROR, "JSONRPC: Could not parse method \"%s\"", methodName.c_str());
1389 if (!newMethod.missingReference.empty())
1391 IncompleteSchemaDefinition incomplete;
1392 incomplete.Schema = modJsonMethod;
1393 incomplete.Type = SchemaDefinitionMethod;
1394 incomplete.Method = method;
1396 IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(newMethod.missingReference);
1397 if (iter == m_incompleteDefinitions.end())
1398 m_incompleteDefinitions[newMethod.missingReference] = std::vector<IncompleteSchemaDefinition>();
1400 CLog::Log(LOGINFO, "JSONRPC: Adding method \"%s\" to list of incomplete definitions (waiting for \"%s\")", methodName.c_str(), newMethod.missingReference.c_str());
1401 m_incompleteDefinitions[newMethod.missingReference].push_back(incomplete);
1407 m_actionMap.add(newMethod);
1412 bool CJSONServiceDescription::AddType(const std::string &jsonType)
1414 CVariant descriptionObject;
1415 std::string typeName;
1417 std::string modJsonType = jsonType;
1418 if (!prepareDescription(modJsonType, descriptionObject, typeName))
1420 CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for type \"%s\"", typeName.c_str());
1424 if (m_types.find(typeName) != m_types.end())
1426 CLog::Log(LOGERROR, "JSONRPC: There already is a type with the name \"%s\"", typeName.c_str());
1430 // Make sure the "id" attribute is correctly populated
1431 descriptionObject[typeName]["id"] = typeName;
1433 JSONSchemaTypeDefinition globalType;
1434 globalType.name = typeName;
1436 if (!globalType.Parse(descriptionObject[typeName]))
1438 CLog::Log(LOGERROR, "JSONRPC: Could not parse type \"%s\"", typeName.c_str());
1439 if (!globalType.missingReference.empty())
1441 IncompleteSchemaDefinition incomplete;
1442 incomplete.Schema = modJsonType;
1443 incomplete.Type = SchemaDefinitionType;
1445 IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(globalType.missingReference);
1446 if (iter == m_incompleteDefinitions.end())
1447 m_incompleteDefinitions[globalType.missingReference] = std::vector<IncompleteSchemaDefinition>();
1449 CLog::Log(LOGINFO, "JSONRPC: Adding type \"%s\" to list of incomplete definitions (waiting for \"%s\")", typeName.c_str(), globalType.missingReference.c_str());
1450 m_incompleteDefinitions[globalType.missingReference].push_back(incomplete);
1459 bool CJSONServiceDescription::AddMethod(const std::string &jsonMethod, MethodCall method)
1463 CLog::Log(LOGERROR, "JSONRPC: Invalid JSONRPC method implementation");
1467 return addMethod(jsonMethod, method);
1470 bool CJSONServiceDescription::AddBuiltinMethod(const std::string &jsonMethod)
1472 return addMethod(jsonMethod, NULL);
1475 bool CJSONServiceDescription::AddNotification(const std::string &jsonNotification)
1477 CVariant descriptionObject;
1478 std::string notificationName;
1480 std::string modJsonNotification = jsonNotification;
1481 // Make sure the notification description actually exists and represents an object
1482 if (!prepareDescription(modJsonNotification, descriptionObject, notificationName))
1484 CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for notification \"%s\"", notificationName.c_str());
1488 if (m_notifications.find(notificationName) != m_notifications.end())
1490 CLog::Log(LOGERROR, "JSONRPC: There already is a notification with the name \"%s\"", notificationName.c_str());
1494 std::string type = GetString(descriptionObject[notificationName]["type"], "");
1495 if (type.compare("notification") != 0)
1497 CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for notification \"%s\"", notificationName.c_str());
1501 m_notifications[notificationName] = descriptionObject;
1506 bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<CVariant> &values, CVariant::VariantType type /* = CVariant::VariantTypeNull */, const CVariant &defaultValue /* = CVariant::ConstNullVariant */)
1508 if (name.empty() || m_types.find(name) != m_types.end() ||
1512 JSONSchemaTypeDefinition definition;
1513 definition.ID = name;
1515 std::vector<CVariant::VariantType> types;
1516 bool autoType = false;
1517 if (type == CVariant::VariantTypeNull)
1520 types.push_back(type);
1522 for (unsigned int index = 0; index < values.size(); index++)
1525 types.push_back(values[index].type());
1526 else if (type != CVariant::VariantTypeConstNull && type != values[index].type())
1529 definition.enums.insert(definition.enums.begin(), values.begin(), values.end());
1531 int schemaType = (int)AnyValue;
1532 for (unsigned int index = 0; index < types.size(); index++)
1534 JSONSchemaType currentType;
1537 case CVariant::VariantTypeString:
1538 currentType = StringValue;
1540 case CVariant::VariantTypeDouble:
1541 currentType = NumberValue;
1543 case CVariant::VariantTypeInteger:
1544 case CVariant::VariantTypeUnsignedInteger:
1545 currentType = IntegerValue;
1547 case CVariant::VariantTypeBoolean:
1548 currentType = BooleanValue;
1550 case CVariant::VariantTypeArray:
1551 currentType = ArrayValue;
1553 case CVariant::VariantTypeObject:
1554 currentType = ObjectValue;
1556 case CVariant::VariantTypeConstNull:
1557 currentType = AnyValue;
1560 case CVariant::VariantTypeNull:
1565 schemaType = currentType;
1567 schemaType |= (int)currentType;
1569 definition.type = (JSONSchemaType)schemaType;
1571 if (defaultValue.type() == CVariant::VariantTypeConstNull)
1572 definition.defaultValue = definition.enums.at(0);
1574 definition.defaultValue = defaultValue;
1576 addReferenceTypeDefinition(definition);
1581 bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<std::string> &values)
1583 std::vector<CVariant> enums;
1584 for (std::vector<std::string>::const_iterator it = values.begin(); it != values.end(); it++)
1585 enums.push_back(CVariant(*it));
1587 return AddEnum(name, enums, CVariant::VariantTypeString);
1590 bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<int> &values)
1592 std::vector<CVariant> enums;
1593 for (std::vector<int>::const_iterator it = values.begin(); it != values.end(); it++)
1594 enums.push_back(CVariant(*it));
1596 return AddEnum(name, enums, CVariant::VariantTypeInteger);
1599 int CJSONServiceDescription::GetVersion()
1601 return JSONRPC_SERVICE_VERSION;
1604 JSONRPC_STATUS CJSONServiceDescription::Print(CVariant &result, ITransportLayer *transport, IClient *client,
1605 bool printDescriptions /* = true */, bool printMetadata /* = false */, bool filterByTransport /* = true */,
1606 const std::string &filterByName /* = "" */, const std::string &filterByType /* = "" */, bool printReferences /* = true */)
1608 std::map<std::string, JSONSchemaTypeDefinition> types;
1609 CJsonRpcMethodMap methods;
1610 std::map<std::string, CVariant> notifications;
1612 int clientPermissions = client->GetPermissionFlags();
1613 int transportCapabilities = transport->GetCapabilities();
1615 if (filterByName.size() > 0)
1617 CStdString name = filterByName;
1619 if (filterByType == "method")
1621 name = name.ToLower();
1623 CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator = m_actionMap.find(name);
1624 if (methodIterator != m_actionMap.end() &&
1625 (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport))
1626 methods.add(methodIterator->second);
1628 return InvalidParams;
1630 else if (filterByType == "namespace")
1632 // append a . delimiter to make sure we check for a namespace
1633 name = name.ToLower().append(".");
1635 CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator;
1636 CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = m_actionMap.end();
1637 for (methodIterator = m_actionMap.begin(); methodIterator != methodIteratorEnd; methodIterator++)
1639 // Check if the given name is at the very beginning of the method name
1640 if (methodIterator->first.find(name) == 0 &&
1641 (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport))
1642 methods.add(methodIterator->second);
1645 if (methods.begin() == methods.end())
1646 return InvalidParams;
1648 else if (filterByType == "type")
1650 std::map<std::string, JSONSchemaTypeDefinition>::const_iterator typeIterator = m_types.find(name);
1651 if (typeIterator != m_types.end())
1652 types[typeIterator->first] = typeIterator->second;
1654 return InvalidParams;
1656 else if (filterByType == "notification")
1658 std::map<std::string, CVariant>::const_iterator notificationIterator = m_notifications.find(name);
1659 if (notificationIterator != m_notifications.end())
1660 notifications[notificationIterator->first] = notificationIterator->second;
1662 return InvalidParams;
1665 return InvalidParams;
1667 // If we need to print all referenced types we have to go through all parameters etc
1668 if (printReferences)
1670 std::vector<std::string> referencedTypes;
1672 // Loop through all printed types to get all referenced types
1673 std::map<std::string, JSONSchemaTypeDefinition>::const_iterator typeIterator;
1674 std::map<std::string, JSONSchemaTypeDefinition>::const_iterator typeIteratorEnd = types.end();
1675 for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; typeIterator++)
1676 getReferencedTypes(typeIterator->second, referencedTypes);
1678 // Loop through all printed method's parameters and return value to get all referenced types
1679 CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator;
1680 CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end();
1681 for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++)
1683 for (unsigned int index = 0; index < methodIterator->second.parameters.size(); index++)
1684 getReferencedTypes(methodIterator->second.parameters.at(index), referencedTypes);
1686 getReferencedTypes(methodIterator->second.returns, referencedTypes);
1689 for (unsigned int index = 0; index < referencedTypes.size(); index++)
1691 std::map<std::string, JSONSchemaTypeDefinition>::const_iterator typeIterator = m_types.find(referencedTypes.at(index));
1692 if (typeIterator != m_types.end())
1693 types[typeIterator->first] = typeIterator->second;
1700 methods = m_actionMap;
1701 notifications = m_notifications;
1705 result["id"] = JSONRPC_SERVICE_ID;
1706 result["version"] = JSONRPC_SERVICE_VERSION;
1707 result["description"] = JSONRPC_SERVICE_DESCRIPTION;
1709 std::map<std::string, JSONSchemaTypeDefinition>::const_iterator typeIterator;
1710 std::map<std::string, JSONSchemaTypeDefinition>::const_iterator typeIteratorEnd = types.end();
1711 for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; typeIterator++)
1713 CVariant currentType = CVariant(CVariant::VariantTypeObject);
1714 typeIterator->second.Print(false, true, true, printDescriptions, currentType);
1716 result["types"][typeIterator->first] = currentType;
1719 // Iterate through all json rpc methods
1720 CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator;
1721 CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end();
1722 for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++)
1724 if ((clientPermissions & methodIterator->second.permission) != methodIterator->second.permission || ((transportCapabilities & methodIterator->second.transportneed) != methodIterator->second.transportneed && filterByTransport))
1727 CVariant currentMethod = CVariant(CVariant::VariantTypeObject);
1729 currentMethod["type"] = "method";
1730 if (printDescriptions && !methodIterator->second.description.empty())
1731 currentMethod["description"] = methodIterator->second.description;
1734 CVariant permissions(CVariant::VariantTypeArray);
1735 for (int i = ReadData; i <= OPERATION_PERMISSION_ALL; i *= 2)
1737 if ((methodIterator->second.permission & i) == i)
1738 permissions.push_back(PermissionToString((OperationPermission)i));
1741 if (permissions.size() == 1)
1742 currentMethod["permission"] = permissions[0];
1744 currentMethod["permission"] = permissions;
1747 currentMethod["params"] = CVariant(CVariant::VariantTypeArray);
1748 for (unsigned int paramIndex = 0; paramIndex < methodIterator->second.parameters.size(); paramIndex++)
1750 CVariant param = CVariant(CVariant::VariantTypeObject);
1751 methodIterator->second.parameters.at(paramIndex).Print(true, false, true, printDescriptions, param);
1752 currentMethod["params"].append(param);
1755 methodIterator->second.returns.Print(false, false, false, printDescriptions, currentMethod["returns"]);
1757 result["methods"][methodIterator->second.name] = currentMethod;
1760 // Print notification description
1761 std::map<std::string, CVariant>::const_iterator notificationIterator;
1762 std::map<std::string, CVariant>::const_iterator notificationIteratorEnd = notifications.end();
1763 for (notificationIterator = notifications.begin(); notificationIterator != notificationIteratorEnd; notificationIterator++)
1764 result["notifications"][notificationIterator->first] = notificationIterator->second[notificationIterator->first];
1769 JSONRPC_STATUS CJSONServiceDescription::CheckCall(const char* const method, const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters)
1771 CJsonRpcMethodMap::JsonRpcMethodIterator iter = m_actionMap.find(method);
1772 if (iter != m_actionMap.end())
1773 return iter->second.Check(requestParameters, transport, client, notification, methodCall, outputParameters);
1775 return MethodNotFound;
1778 JSONSchemaTypeDefinition* CJSONServiceDescription::GetType(const std::string &identification)
1780 std::map<std::string, JSONSchemaTypeDefinition>::iterator iter = m_types.find(identification);
1781 if (iter == m_types.end())
1784 return &(iter->second);
1787 bool CJSONServiceDescription::parseJSONSchemaType(const CVariant &value, std::vector<JSONSchemaTypeDefinition>& typeDefinitions, JSONSchemaType &schemaType, std::string &missingReference)
1789 missingReference.clear();
1790 schemaType = AnyValue;
1792 if (value.isArray())
1795 // If the defined type is an array, we have
1796 // to handle a union type
1797 for (unsigned int typeIndex = 0; typeIndex < value.size(); typeIndex++)
1799 JSONSchemaTypeDefinition definition;
1800 // If the type is a string try to parse it
1801 if (value[typeIndex].isString())
1802 definition.type = StringToSchemaValueType(value[typeIndex].asString());
1803 else if (value[typeIndex].isObject())
1805 if (!definition.Parse(value[typeIndex]))
1807 missingReference = definition.missingReference;
1808 CLog::Log(LOGERROR, "JSONRPC: Invalid type schema in union type definition");
1814 CLog::Log(LOGWARNING, "JSONRPC: Invalid type in union type definition");
1818 definition.optional = false;
1819 typeDefinitions.push_back(definition);
1820 parsedType |= definition.type;
1823 // If the type has not been set yet set it to "any"
1824 if (parsedType != 0)
1825 schemaType = (JSONSchemaType)parsedType;
1830 if (value.isString())
1832 schemaType = StringToSchemaValueType(value.asString());
1839 void CJSONServiceDescription::addReferenceTypeDefinition(const JSONSchemaTypeDefinition &typeDefinition)
1841 // If the given json value is no object or does not contain an "id" field
1842 // of type string it is no valid type definition
1843 if (typeDefinition.ID.empty())
1846 // If the id has already been defined we ignore the type definition
1847 if (m_types.find(typeDefinition.ID) != m_types.end())
1850 // Add the type to the list of type definitions
1851 m_types[typeDefinition.ID] = typeDefinition;
1853 IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(typeDefinition.ID);
1854 if (iter == m_incompleteDefinitions.end())
1857 CLog::Log(LOGINFO, "JSONRPC: Resolving incomplete types/methods referencing %s", typeDefinition.ID.c_str());
1858 for (unsigned int index = 0; index < iter->second.size(); index++)
1860 if (iter->second[index].Type == SchemaDefinitionType)
1861 AddType(iter->second[index].Schema);
1863 AddMethod(iter->second[index].Schema, iter->second[index].Method);
1866 m_incompleteDefinitions.erase(typeDefinition.ID);
1869 void CJSONServiceDescription::getReferencedTypes(const JSONSchemaTypeDefinition &type, std::vector<std::string> &referencedTypes)
1871 // If the current type is a referenceable object, we can add it to the list
1872 if (type.ID.size() > 0)
1874 for (unsigned int index = 0; index < referencedTypes.size(); index++)
1876 // The referenceable object has already been added to the list so we can just skip it
1877 if (type.ID == referencedTypes.at(index))
1881 referencedTypes.push_back(type.ID);
1884 // If the current type is an object we need to check its properties
1885 if (HasType(type.type, ObjectValue))
1887 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iter;
1888 JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iterEnd = type.properties.end();
1889 for (iter = type.properties.begin(); iter != iterEnd; iter++)
1890 getReferencedTypes(iter->second, referencedTypes);
1892 // If the current type is an array we need to check its items
1893 if (HasType(type.type, ArrayValue))
1896 for (index = 0; index < type.items.size(); index++)
1897 getReferencedTypes(type.items.at(index), referencedTypes);
1899 for (index = 0; index < type.additionalItems.size(); index++)
1900 getReferencedTypes(type.additionalItems.at(index), referencedTypes);
1903 // If the current type extends others type we need to check those types
1904 for (unsigned int index = 0; index < type.extends.size(); index++)
1905 getReferencedTypes(type.extends.at(index), referencedTypes);
1907 // If the current type is a union type we need to check those types
1908 for (unsigned int index = 0; index < type.unionTypes.size(); index++)
1909 getReferencedTypes(type.unionTypes.at(index), referencedTypes);
1912 CJSONServiceDescription::CJsonRpcMethodMap::CJsonRpcMethodMap()
1914 m_actionmap = std::map<std::string, JsonRpcMethod>();
1917 void CJSONServiceDescription::CJsonRpcMethodMap::add(const JsonRpcMethod &method)
1919 CStdString name = method.name;
1920 name = name.ToLower();
1921 m_actionmap[name] = method;
1924 CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::begin() const
1926 return m_actionmap.begin();
1929 CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::find(const std::string& key) const
1931 return m_actionmap.find(key);
1934 CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::end() const
1936 return m_actionmap.end();