JSON validation using Introspect()
#1
Dear all,

I am trying to write yet another python wrapper for the XBMC JSON-RPC so I can send commands and also subscribe to notifications. The reason for this is that I am working on a larger project in which I want to be able to control my HTPC and everything that happens in it (torrent downloading, xbmc updates, etc.) from a single place (you can see the progress in https://github.com/apuignav/pythonhtpc (xbmc is in pythonhtpc/rpcs/xbmclib.py).

I wanted to add automatic validation of the parameters passed to methods and the responses from XBMC in combination with "discovery" using JSONRPC.Introspect(). As far as I understand, this call should return a valid JSON schema to validate against, but I have not been able to manage to do so in a general way. I think I am probably making a mistake or I am not understanding what to expect from this function, so let me try to give some examples that will clarify my problems:

Code:
from validictory import validate
schema = xbmc.execute_method('JSONRPC.Introspect')['methods']['JSONRPC.Version']['returns']
returns = xbmc.execute_method('JSONRPC.Version')
validate(returns, schema)
which fails because
Code:
validictory.validator.FieldValidationError: Required field 'major' is missing

However, if i do
Code:
from validictory import validate
schema = xbmc.execute_method('JSONRPC.Introspect')['methods']['JSONRPC.Ping']['returns']
returns = xbmc.execute_method('JSONRPC.Ping')
validate(returns, schema)
everything works beautifully.

In the first case, the returns variable contains
Code:
{u'version': {u'major': 6, u'minor': 0, u'patch': 3}}
while in the other it gives a simple string
Code:
u'pong'

In the case of version, I can solve it with
Code:
from validictory import validate
schema = xbmc.execute_method('JSONRPC.Introspect')['methods']['JSONRPC.Version']['returns']
returns = xbmc.execute_method('JSONRPC.Version')
validate(returns['version'], schema)
but of course it is not nice because I need to know the call returns a dictionary with a key called 'version' (I can hack this, but I would like to avoid it if possible). In addition, this would break the Ping case.

In addition to this, I have problems in validating parameters, since 'params' is a list that needs to be converted to
Code:
{'type': 'object', 'properties': params}
in order to successfully validate params with the form
Code:
{'param1': value, 'param2': value}
(however, this is probably my fault).

Can somebody point me to the right path? I have the feeling there is something very stupid I am not understanding... There a few other things I don't understand, but I think if you can help me solve these fundamental problems I will be able to move forward and understand the rest.

Thanks you very much in advance,
Albert

PS: I've found that JSONRPC.Introspect doesn't contain information on the OnScreensaverActivated and OnScreensaverDeactivated notifications, at least in my XBMC version. Is this known?

Code:
>>> xbmc.execute_method('JSONRPC.Introspect')['notifications']['GUI.OnScreensaverActivated']
[2014-03-28 20:25:44,763] htpc.RaspiXBMC::DEBUG [core.py:execute_method:110] Executing method JSONRPC.Introspect with parameters {}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'GUI.OnScreensaverActivated'
Reply
#2
Introspect will tell you whether the returned value is just a string or an object/dictionary. But I don't know anything about validictory etc so I can't say what happens in there.

Btw you can do filtering by methods in JSONRPC.Introspect. It looks like you currently always retrieve the full introspect (which is pretty big) and then get the method's "returns" part.

I don't really understand your issue with "params". Are you talking about "params" from introspect or the "params" you specify in your requests? Maybe you could re-phrase your question under the assumption that I know nothing about the tools you use.

And thanks for the hint concerning OnScreensaverActivated/OnScreensaverDeactivated. They are really missing from introspect.
Always read the online manual (wiki), FAQ (wiki) and search the forum before posting.
Do not e-mail Team Kodi members directly asking for support. Read/follow the forum rules (wiki).
Please read the pages on troubleshooting (wiki) and bug reporting (wiki) before reporting issues.
Reply
#3
Hi,

yes, you are right! Sorry, my post was indeed too obscure and failed completely to convey my question. Let me retry :-)

Let me start with JSONRPC.Ping. From Introspect (I know it can be called just method by method, in my previous post I just did that to go faster) I get

Code:
"JSONRPC.Ping": {
            "description": "Ping responder",
            "params": [

            ],
            "returns": {
                "type": "string"
            },
            "type": "method"
        }

So I make a JSON call with the following configuration
Code:
{u'jsonrpc': u'2.0', u'id': 0, 'method': u'JSONRPC.Ping', 'params':{}}
and I get the following answer
Code:
{u'jsonrpc': u'2.0', u'id': 0, u'result': u'pong'}
which is perfectly consistent with the 'returns' schema.

Now, if I go to JSONRPC.Version:
Code:
"JSONRPC.Version": {
            "description": "Retrieve the JSON-RPC protocol version.",
            "params": [

            ],
            "returns": {
                "properties": {
                    "major": {
                        "description": "Bumped on backwards incompatible changes to the API definition",
                        "minimum": 0,
                        "required": true,
                        "type": "integer"
                    },
                    "minor": {
                        "description": "Bumped on backwards compatible additions/changes to the API definition",
                        "minimum": 0,
                        "required": true,
                        "type": "integer"
                    },
                    "patch": {
                        "description": "Bumped on any changes to the internal implementation but not to the API definition",
                        "minimum": 0,
                        "required": true,
                        "type": "integer"
                    }
                },
                "type": "object"
            },
            "type": "method"
        }

I can make a call such as
Code:
{u'jsonrpc': u'2.0', u'id': 1, 'method': u'JSONRPC.Version', 'params':{}}
which yields
Code:
{u'jsonrpc': u'2.0', u'id': 1, u'result': {u'version': {u'major': 6, u'minor': 0, u'patch': 3}}}
which, as far as I understand, is not matching the schema given by returns:
Code:
            "returns": {
                "properties": {
                    "major": {
                        "description": "Bumped on backwards incompatible changes to the API definition",
                        "minimum": 0,
                        "required": true,
                        "type": "integer"
                    },
                    "minor": {
                        "description": "Bumped on backwards compatible additions/changes to the API definition",
                        "minimum": 0,
                        "required": true,
                        "type": "integer"
                    },
                    "patch": {
                        "description": "Bumped on any changes to the internal implementation but not to the API definition",
                        "minimum": 0,
                        "required": true,
                        "type": "integer"
                    }
                },
                "type": "object"
            }

If I have understood correctly (and probably I haven't), to be consistent with that schema the answer should be
Code:
{u'jsonrpc': u'2.0', u'id': 1, u'result': {u'major': 6, u'minor': 0, u'patch': 3}}

Alternatively, one could define the 'returns' with an object inside an object, something along the lines of
Code:
"returns": {
                          'properties': {
                                'version': { "properties": { "major": {...},    "minor": {...}, "patch": {...} },
                                "type": "object"
                                              },
                           'type': 'object'
            }

So, I was wondering if I am just misunderstanding how this works, how JSON schema works, or I am just trying to use Introspect for something it was not designed to do...

Regarding the params issue I mentioned, in JSONRPC.Ping, for example, the method params are defined as
Code:
"params": []
which is not what I would expect. I expect params to be either an array, so the schema corresponding to it it would be
Code:
"params": {'type': 'array'}
or (more possibly) an object
Code:
"params": {'type': 'object'}
but probably I'm just misunderstanding.

In more complex cases, for example
Code:
"JSONRPC.NotifyAll": {
            "description": "Notify all other connected clients",
            "params": [
                {
                    "name": "sender",
                    "required": true,
                    "type": "string"
                },
                {
                    "name": "message",
                    "required": true,
                    "type": "string"
                },
                {
                    "default": null,
                    "name": "data",
                    "type": "any"
                }
            ],
            "returns": {
                "type": "any"
            },
            "type": "method"
        }

I see that 'params' doesn't really look like a JSON schema. Looking and understanding what the JSONRPC server wants, I think the correct schema for the 'params' in this case would be something like
Code:
"params": { "type": "object",
                "properties": {
                                "sender":{
                    "required": true,
                    "type": "string"
                },
                "message": {
                    "required": true,
                    "type": "string"
                },
                "data": {
                    "default": null,
                    "type": "any"
                }}
            }

As you can see, I am very confused on how to interpret the Introspect results, and I have the strong feeling I am just looking at them the wrong way. Could you please help me?

Thanks a lot,
Albert
Reply
#4
Hehe you were a bit unlucky in trying your work on JSONRPC.Version because that one was broken until a few weeks ago. The schema has since been fixed to contain the "version" property in the "returns" object.

Concerning "params": You're assumption that the whole method definition is JSON schema is not correct. It's based on "JSON Service Description" which vanished in the meantime. There have been some new approaches to this but I haven't seen anything yet that really covers the full JSON-RPC 2.0 specification.
The main problem is that the JSON-RPC 2.0 specification dictates that "params" must either be an array or an object. So when doing a request you have the choice between passing your parameters as an array in the right order (without a name) or as an object with the properties being in any order but with a specific name.
Therefore the API definition cannot just use JSON schema's "type": "object" because in a JSON object the properties don't have an absolute order which must be the case to fulfill the JSON-RPC 2.0 specification. But you also can't use JSON schema's "type": "array" because JSON schema doesn't allow the definition of the object instances in an array (it does but only very generically).

So in the end the JSON-RPC API definition is a combination of "JSON service description" for methods, notifications, parameters and the type containers and of "JSON schema" for the definition of the actual types.
Always read the online manual (wiki), FAQ (wiki) and search the forum before posting.
Do not e-mail Team Kodi members directly asking for support. Read/follow the forum rules (wiki).
Please read the pages on troubleshooting (wiki) and bug reporting (wiki) before reporting issues.
Reply
#5
Actually I just found a website that took over the "JSON service description" specification at http://www.simple-is-better.org/json-rpc...iptor.html
Always read the online manual (wiki), FAQ (wiki) and search the forum before posting.
Do not e-mail Team Kodi members directly asking for support. Read/follow the forum rules (wiki).
Please read the pages on troubleshooting (wiki) and bug reporting (wiki) before reporting issues.
Reply
#6
Hi!

Thanks a lot, now I understand... So, it is clearly more difficult than I thought, unfortunately. Anyway, I am left with one doubt about the whole params business: does your explanation mean that parameters need to be passed in the specified order when making the RPC call?

Thanks a lot for all the help,
Albert
Reply
#7
Only if you pass them in as an array (i.e. "params" is an array). If you pass them in as properties of the "params" object they can be in any order.
Always read the online manual (wiki), FAQ (wiki) and search the forum before posting.
Do not e-mail Team Kodi members directly asking for support. Read/follow the forum rules (wiki).
Please read the pages on troubleshooting (wiki) and bug reporting (wiki) before reporting issues.
Reply
#8
OK! Now I understand! Then this is easily hackable :-)

Thanks,
Albert
Reply

Logout Mark Read Team Forum Stats Members Help
JSON validation using Introspect()0