1e6c42b96SMarkus Armbruster# -*- coding: utf-8 -*- 2e6c42b96SMarkus Armbruster# 3e6c42b96SMarkus Armbruster# Copyright IBM, Corp. 2011 4e81718c6SJohn Snow# Copyright (c) 2013-2021 Red Hat Inc. 5e6c42b96SMarkus Armbruster# 6e6c42b96SMarkus Armbruster# Authors: 7e6c42b96SMarkus Armbruster# Anthony Liguori <aliguori@us.ibm.com> 8e6c42b96SMarkus Armbruster# Markus Armbruster <armbru@redhat.com> 9e6c42b96SMarkus Armbruster# Eric Blake <eblake@redhat.com> 10e6c42b96SMarkus Armbruster# Marc-André Lureau <marcandre.lureau@redhat.com> 11e81718c6SJohn Snow# John Snow <jsnow@redhat.com> 12e6c42b96SMarkus Armbruster# 13e6c42b96SMarkus Armbruster# This work is licensed under the terms of the GNU GPL, version 2. 14e6c42b96SMarkus Armbruster# See the COPYING file in the top-level directory. 15e6c42b96SMarkus Armbruster 16a4865363SJohn Snow""" 17a4865363SJohn SnowNormalize and validate (context-free) QAPI schema expression structures. 18a4865363SJohn Snow 19a4865363SJohn Snow`QAPISchemaParser` parses a QAPI schema into abstract syntax trees 20a4865363SJohn Snowconsisting of dict, list, str, bool, and int nodes. This module ensures 21a4865363SJohn Snowthat these nested structures have the correct type(s) and key(s) where 22a4865363SJohn Snowappropriate for the QAPI context-free grammar. 23a4865363SJohn Snow 24a4865363SJohn SnowThe QAPI schema expression language allows for certain syntactic sugar; 25a4865363SJohn Snowthis module also handles the normalization process of these nested 26a4865363SJohn Snowstructures. 27a4865363SJohn Snow 28a4865363SJohn SnowSee `check_exprs` for the main entry point. 29a4865363SJohn Snow 30a4865363SJohn SnowSee `schema.QAPISchema` for processing into native Python data 31a4865363SJohn Snowstructures and contextual semantic validation. 32a4865363SJohn Snow""" 33a4865363SJohn Snow 3467fea575SJohn Snowimport re 35b9ad358aSJohn Snowfrom typing import ( 36b9ad358aSJohn Snow Dict, 37b9ad358aSJohn Snow Iterable, 38b9ad358aSJohn Snow List, 39b9ad358aSJohn Snow Optional, 40b9ad358aSJohn Snow Union, 41b9ad358aSJohn Snow cast, 42b9ad358aSJohn Snow) 4367fea575SJohn Snow 447137a960SJohn Snowfrom .common import c_name 457137a960SJohn Snowfrom .error import QAPISemError 4642011059SJohn Snowfrom .parser import QAPIExpression 4759b5556cSJohn Snowfrom .source import QAPISourceInfo 4859b5556cSJohn Snow 4959b5556cSJohn Snow 50a4865363SJohn Snow# See check_name_str(), below. 51eaab06faSMarkus Armbrustervalid_name = re.compile(r'(__[a-z0-9.-]+_)?' 52eaab06faSMarkus Armbruster r'(x-)?' 53eaab06faSMarkus Armbruster r'([a-z][a-z0-9_-]*)$', re.IGNORECASE) 54e6c42b96SMarkus Armbruster 55e6c42b96SMarkus Armbruster 56b9ad358aSJohn Snowdef check_name_is_str(name: object, 57b9ad358aSJohn Snow info: QAPISourceInfo, 58b9ad358aSJohn Snow source: str) -> None: 59a4865363SJohn Snow """ 60a4865363SJohn Snow Ensure that ``name`` is a ``str``. 61a4865363SJohn Snow 62a4865363SJohn Snow :raise QAPISemError: When ``name`` fails validation. 63a4865363SJohn Snow """ 64e6c42b96SMarkus Armbruster if not isinstance(name, str): 65e6c42b96SMarkus Armbruster raise QAPISemError(info, "%s requires a string name" % source) 66e6c42b96SMarkus Armbruster 67e6c42b96SMarkus Armbruster 68b9ad358aSJohn Snowdef check_name_str(name: str, info: QAPISourceInfo, source: str) -> str: 69a4865363SJohn Snow """ 70a4865363SJohn Snow Ensure that ``name`` is a valid QAPI name. 71a4865363SJohn Snow 72a4865363SJohn Snow A valid name consists of ASCII letters, digits, ``-``, and ``_``, 73a4865363SJohn Snow starting with a letter. It may be prefixed by a downstream prefix 74a4865363SJohn Snow of the form __RFQDN_, or the experimental prefix ``x-``. If both 75a4865363SJohn Snow prefixes are present, the __RFDQN_ prefix goes first. 76a4865363SJohn Snow 77a4865363SJohn Snow A valid name cannot start with ``q_``, which is reserved. 78a4865363SJohn Snow 79a4865363SJohn Snow :param name: Name to check. 80a4865363SJohn Snow :param info: QAPI schema source file information. 81a4865363SJohn Snow :param source: Error string describing what ``name`` belongs to. 82a4865363SJohn Snow 83a4865363SJohn Snow :raise QAPISemError: When ``name`` fails validation. 84a4865363SJohn Snow :return: The stem of the valid name, with no prefixes. 85a4865363SJohn Snow """ 86e6c42b96SMarkus Armbruster # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty' 87e6c42b96SMarkus Armbruster # and 'q_obj_*' implicit type names. 88eaab06faSMarkus Armbruster match = valid_name.match(name) 89eaab06faSMarkus Armbruster if not match or c_name(name, False).startswith('q_'): 90e6c42b96SMarkus Armbruster raise QAPISemError(info, "%s has an invalid name" % source) 91eaab06faSMarkus Armbruster return match.group(3) 92eaab06faSMarkus Armbruster 93eaab06faSMarkus Armbruster 94b9ad358aSJohn Snowdef check_name_upper(name: str, info: QAPISourceInfo, source: str) -> None: 95a4865363SJohn Snow """ 96a4865363SJohn Snow Ensure that ``name`` is a valid event name. 97a4865363SJohn Snow 98a4865363SJohn Snow This means it must be a valid QAPI name as checked by 99a4865363SJohn Snow `check_name_str()`, but where the stem prohibits lowercase 100a4865363SJohn Snow characters and ``-``. 101a4865363SJohn Snow 102a4865363SJohn Snow :param name: Name to check. 103a4865363SJohn Snow :param info: QAPI schema source file information. 104a4865363SJohn Snow :param source: Error string describing what ``name`` belongs to. 105a4865363SJohn Snow 106a4865363SJohn Snow :raise QAPISemError: When ``name`` fails validation. 107a4865363SJohn Snow """ 108d224e0c0SMarkus Armbruster stem = check_name_str(name, info, source) 109d4f4cae8SMarkus Armbruster if re.search(r'[a-z-]', stem): 110d4f4cae8SMarkus Armbruster raise QAPISemError( 111d4f4cae8SMarkus Armbruster info, "name of %s must not use lowercase or '-'" % source) 112eaab06faSMarkus Armbruster 113eaab06faSMarkus Armbruster 114b9ad358aSJohn Snowdef check_name_lower(name: str, info: QAPISourceInfo, source: str, 115b9ad358aSJohn Snow permit_upper: bool = False, 116b9ad358aSJohn Snow permit_underscore: bool = False) -> None: 117a4865363SJohn Snow """ 118a4865363SJohn Snow Ensure that ``name`` is a valid command or member name. 119a4865363SJohn Snow 120a4865363SJohn Snow This means it must be a valid QAPI name as checked by 121a4865363SJohn Snow `check_name_str()`, but where the stem prohibits uppercase 122a4865363SJohn Snow characters and ``_``. 123a4865363SJohn Snow 124a4865363SJohn Snow :param name: Name to check. 125a4865363SJohn Snow :param info: QAPI schema source file information. 126a4865363SJohn Snow :param source: Error string describing what ``name`` belongs to. 127a4865363SJohn Snow :param permit_upper: Additionally permit uppercase. 128a4865363SJohn Snow :param permit_underscore: Additionally permit ``_``. 129a4865363SJohn Snow 130a4865363SJohn Snow :raise QAPISemError: When ``name`` fails validation. 131a4865363SJohn Snow """ 132d224e0c0SMarkus Armbruster stem = check_name_str(name, info, source) 1339af4b6b9SMarkus Armbruster if ((not permit_upper and re.search(r'[A-Z]', stem)) 1349af4b6b9SMarkus Armbruster or (not permit_underscore and '_' in stem)): 135d224e0c0SMarkus Armbruster raise QAPISemError( 1369af4b6b9SMarkus Armbruster info, "name of %s must not use uppercase or '_'" % source) 137eaab06faSMarkus Armbruster 138eaab06faSMarkus Armbruster 139b9ad358aSJohn Snowdef check_name_camel(name: str, info: QAPISourceInfo, source: str) -> None: 140a4865363SJohn Snow """ 141a4865363SJohn Snow Ensure that ``name`` is a valid user-defined type name. 142a4865363SJohn Snow 143a4865363SJohn Snow This means it must be a valid QAPI name as checked by 144a4865363SJohn Snow `check_name_str()`, but where the stem must be in CamelCase. 145a4865363SJohn Snow 146a4865363SJohn Snow :param name: Name to check. 147a4865363SJohn Snow :param info: QAPI schema source file information. 148a4865363SJohn Snow :param source: Error string describing what ``name`` belongs to. 149a4865363SJohn Snow 150a4865363SJohn Snow :raise QAPISemError: When ``name`` fails validation. 151a4865363SJohn Snow """ 152d224e0c0SMarkus Armbruster stem = check_name_str(name, info, source) 1533e6c8a63SMarkus Armbruster if not re.match(r'[A-Z][A-Za-z0-9]*[a-z][A-Za-z0-9]*$', stem): 1543e6c8a63SMarkus Armbruster raise QAPISemError(info, "name of %s must use CamelCase" % source) 155e6c42b96SMarkus Armbruster 156e6c42b96SMarkus Armbruster 157b9ad358aSJohn Snowdef check_defn_name_str(name: str, info: QAPISourceInfo, meta: str) -> None: 158a4865363SJohn Snow """ 159a4865363SJohn Snow Ensure that ``name`` is a valid definition name. 160a4865363SJohn Snow 161a4865363SJohn Snow Based on the value of ``meta``, this means that: 162a4865363SJohn Snow - 'event' names adhere to `check_name_upper()`. 163a4865363SJohn Snow - 'command' names adhere to `check_name_lower()`. 164a4865363SJohn Snow - Else, meta is a type, and must pass `check_name_camel()`. 1658ebc3120SMarkus Armbruster These names must not end with ``List``. 166a4865363SJohn Snow 167a4865363SJohn Snow :param name: Name to check. 168a4865363SJohn Snow :param info: QAPI schema source file information. 169a4865363SJohn Snow :param meta: Meta-type name of the QAPI expression. 170a4865363SJohn Snow 171a4865363SJohn Snow :raise QAPISemError: When ``name`` fails validation. 172a4865363SJohn Snow """ 173eaab06faSMarkus Armbruster if meta == 'event': 174eaab06faSMarkus Armbruster check_name_upper(name, info, meta) 175eaab06faSMarkus Armbruster elif meta == 'command': 17605ebf841SMarkus Armbruster check_name_lower( 17705ebf841SMarkus Armbruster name, info, meta, 17805ebf841SMarkus Armbruster permit_underscore=name in info.pragma.command_name_exceptions) 179eaab06faSMarkus Armbruster else: 180eaab06faSMarkus Armbruster check_name_camel(name, info, meta) 1818ebc3120SMarkus Armbruster if name.endswith('List'): 182e6c42b96SMarkus Armbruster raise QAPISemError( 1838ebc3120SMarkus Armbruster info, "%s name should not end in 'List'" % meta) 184e6c42b96SMarkus Armbruster 185e6c42b96SMarkus Armbruster 18667a81f9fSJohn Snowdef check_keys(value: Dict[str, object], 187b9ad358aSJohn Snow info: QAPISourceInfo, 188b9ad358aSJohn Snow source: str, 189c60caf80SJohn Snow required: List[str], 190c60caf80SJohn Snow optional: List[str]) -> None: 191a4865363SJohn Snow """ 192a4865363SJohn Snow Ensure that a dict has a specific set of keys. 193a4865363SJohn Snow 194a4865363SJohn Snow :param value: The dict to check. 195a4865363SJohn Snow :param info: QAPI schema source file information. 196a4865363SJohn Snow :param source: Error string describing this ``value``. 197a4865363SJohn Snow :param required: Keys that *must* be present. 198a4865363SJohn Snow :param optional: Keys that *may* be present. 199a4865363SJohn Snow 200a4865363SJohn Snow :raise QAPISemError: When unknown keys are present. 201a4865363SJohn Snow """ 202e6c42b96SMarkus Armbruster 203b9ad358aSJohn Snow def pprint(elems: Iterable[str]) -> str: 204e6c42b96SMarkus Armbruster return ', '.join("'" + e + "'" for e in sorted(elems)) 205e6c42b96SMarkus Armbruster 206e6c42b96SMarkus Armbruster missing = set(required) - set(value) 207e6c42b96SMarkus Armbruster if missing: 208e6c42b96SMarkus Armbruster raise QAPISemError( 209e6c42b96SMarkus Armbruster info, 210e6c42b96SMarkus Armbruster "%s misses key%s %s" 211e6c42b96SMarkus Armbruster % (source, 's' if len(missing) > 1 else '', 212e6c42b96SMarkus Armbruster pprint(missing))) 213538cd410SJohn Snow allowed = set(required) | set(optional) 214e6c42b96SMarkus Armbruster unknown = set(value) - allowed 215e6c42b96SMarkus Armbruster if unknown: 216e6c42b96SMarkus Armbruster raise QAPISemError( 217e6c42b96SMarkus Armbruster info, 218e6c42b96SMarkus Armbruster "%s has unknown key%s %s\nValid keys are %s." 219e6c42b96SMarkus Armbruster % (source, 's' if len(unknown) > 1 else '', 220e6c42b96SMarkus Armbruster pprint(unknown), pprint(allowed))) 221e6c42b96SMarkus Armbruster 222e6c42b96SMarkus Armbruster 22342011059SJohn Snowdef check_flags(expr: QAPIExpression) -> None: 224a4865363SJohn Snow """ 225a4865363SJohn Snow Ensure flag members (if present) have valid values. 226a4865363SJohn Snow 227a4865363SJohn Snow :param expr: The expression to validate. 228a4865363SJohn Snow 229a4865363SJohn Snow :raise QAPISemError: 230a4865363SJohn Snow When certain flags have an invalid value, or when 231a4865363SJohn Snow incompatible flags are present. 232a4865363SJohn Snow """ 233eab99939SJohn Snow for key in ('gen', 'success-response'): 234e6c42b96SMarkus Armbruster if key in expr and expr[key] is not False: 235e6c42b96SMarkus Armbruster raise QAPISemError( 23642011059SJohn Snow expr.info, "flag '%s' may only use false value" % key) 237eab99939SJohn Snow for key in ('boxed', 'allow-oob', 'allow-preconfig', 'coroutine'): 238e6c42b96SMarkus Armbruster if key in expr and expr[key] is not True: 239e6c42b96SMarkus Armbruster raise QAPISemError( 24042011059SJohn Snow expr.info, "flag '%s' may only use true value" % key) 24104f22362SKevin Wolf if 'allow-oob' in expr and 'coroutine' in expr: 24204f22362SKevin Wolf # This is not necessarily a fundamental incompatibility, but 24304f22362SKevin Wolf # we don't have a use case and the desired semantics isn't 24404f22362SKevin Wolf # obvious. The simplest solution is to forbid it until we get 24504f22362SKevin Wolf # a use case for it. 24642011059SJohn Snow raise QAPISemError( 24742011059SJohn Snow expr.info, "flags 'allow-oob' and 'coroutine' are incompatible") 248e6c42b96SMarkus Armbruster 249e6c42b96SMarkus Armbruster 25067a81f9fSJohn Snowdef check_if(expr: Dict[str, object], 25167a81f9fSJohn Snow info: QAPISourceInfo, source: str) -> None: 252a4865363SJohn Snow """ 2535d83b9a1SMarc-André Lureau Validate the ``if`` member of an object. 254e6c42b96SMarkus Armbruster 2555d83b9a1SMarc-André Lureau The ``if`` member may be either a ``str`` or a dict. 256a4865363SJohn Snow 257a4865363SJohn Snow :param expr: The expression containing the ``if`` member to validate. 258a4865363SJohn Snow :param info: QAPI schema source file information. 259a4865363SJohn Snow :param source: Error string describing ``expr``. 260a4865363SJohn Snow 261a4865363SJohn Snow :raise QAPISemError: 262a4865363SJohn Snow When the "if" member fails validation, or when there are no 263a4865363SJohn Snow non-empty conditions. 2645d83b9a1SMarc-André Lureau :return: None 265a4865363SJohn Snow """ 2665d83b9a1SMarc-André Lureau 2675d83b9a1SMarc-André Lureau def _check_if(cond: Union[str, object]) -> None: 2685d83b9a1SMarc-André Lureau if isinstance(cond, str): 269555dd1aaSMarkus Armbruster if not re.fullmatch(r'[A-Z][A-Z0-9_]*', cond): 2705d83b9a1SMarc-André Lureau raise QAPISemError( 2715d83b9a1SMarc-André Lureau info, 2728a9f1e1dSMarc-André Lureau "'if' condition '%s' of %s is not a valid identifier" 2735d83b9a1SMarc-André Lureau % (cond, source)) 2745d83b9a1SMarc-André Lureau return 2755d83b9a1SMarc-André Lureau 2765d83b9a1SMarc-André Lureau if not isinstance(cond, dict): 2775d83b9a1SMarc-André Lureau raise QAPISemError( 2785d83b9a1SMarc-André Lureau info, 2795d83b9a1SMarc-André Lureau "'if' condition of %s must be a string or an object" % source) 28034f7b25eSMarkus Armbruster check_keys(cond, info, "'if' condition of %s" % source, [], 28134f7b25eSMarkus Armbruster ["all", "any", "not"]) 2825d83b9a1SMarc-André Lureau if len(cond) != 1: 2835d83b9a1SMarc-André Lureau raise QAPISemError( 2845d83b9a1SMarc-André Lureau info, 28534f7b25eSMarkus Armbruster "'if' condition of %s has conflicting keys" % source) 2865d83b9a1SMarc-André Lureau 28762f27589SMarkus Armbruster if 'not' in cond: 28862f27589SMarkus Armbruster _check_if(cond['not']) 28962f27589SMarkus Armbruster elif 'all' in cond: 29062f27589SMarkus Armbruster _check_infix('all', cond['all']) 29162f27589SMarkus Armbruster else: 29262f27589SMarkus Armbruster _check_infix('any', cond['any']) 29362f27589SMarkus Armbruster 29462f27589SMarkus Armbruster def _check_infix(operator: str, operands: object) -> None: 29562f27589SMarkus Armbruster if not isinstance(operands, list): 29662f27589SMarkus Armbruster raise QAPISemError( 29762f27589SMarkus Armbruster info, 29862f27589SMarkus Armbruster "'%s' condition of %s must be an array" 29962f27589SMarkus Armbruster % (operator, source)) 3005d83b9a1SMarc-André Lureau if not operands: 3015d83b9a1SMarc-André Lureau raise QAPISemError( 3025d83b9a1SMarc-André Lureau info, "'if' condition [] of %s is useless" % source) 3035d83b9a1SMarc-André Lureau for operand in operands: 3045d83b9a1SMarc-André Lureau _check_if(operand) 3055d83b9a1SMarc-André Lureau 306210fd631SJohn Snow ifcond = expr.get('if') 307210fd631SJohn Snow if ifcond is None: 308210fd631SJohn Snow return 309210fd631SJohn Snow 3105d83b9a1SMarc-André Lureau _check_if(ifcond) 311e6c42b96SMarkus Armbruster 312e6c42b96SMarkus Armbruster 313b9ad358aSJohn Snowdef normalize_members(members: object) -> None: 314a4865363SJohn Snow """ 315a4865363SJohn Snow Normalize a "members" value. 316a4865363SJohn Snow 317a4865363SJohn Snow If ``members`` is a dict, for every value in that dict, if that 318a4865363SJohn Snow value is not itself already a dict, normalize it to 319a4865363SJohn Snow ``{'type': value}``. 320a4865363SJohn Snow 321a4865363SJohn Snow :forms: 322a4865363SJohn Snow :sugared: ``Dict[str, Union[str, TypeRef]]`` 323a4865363SJohn Snow :canonical: ``Dict[str, TypeRef]`` 324a4865363SJohn Snow 325a4865363SJohn Snow :param members: The members value to normalize. 326a4865363SJohn Snow 327a4865363SJohn Snow :return: None, ``members`` is normalized in-place as needed. 328a4865363SJohn Snow """ 3290f231dcfSJohn Snow if isinstance(members, dict): 330e6c42b96SMarkus Armbruster for key, arg in members.items(): 331e6c42b96SMarkus Armbruster if isinstance(arg, dict): 332e6c42b96SMarkus Armbruster continue 333e6c42b96SMarkus Armbruster members[key] = {'type': arg} 334e6c42b96SMarkus Armbruster 335e6c42b96SMarkus Armbruster 33606cc46eeSMarkus Armbrusterdef check_type_name(value: Optional[object], 33706cc46eeSMarkus Armbruster info: QAPISourceInfo, source: str) -> None: 3387c407519SMarkus Armbruster if value is not None and not isinstance(value, str): 33906cc46eeSMarkus Armbruster raise QAPISemError(info, "%s should be a type name" % source) 34006cc46eeSMarkus Armbruster 34106cc46eeSMarkus Armbruster 34206cc46eeSMarkus Armbrusterdef check_type_name_or_array(value: Optional[object], 34306cc46eeSMarkus Armbruster info: QAPISourceInfo, source: str) -> None: 3447c407519SMarkus Armbruster if value is None or isinstance(value, str): 34506cc46eeSMarkus Armbruster return 34606cc46eeSMarkus Armbruster 3476f2ab609SMarkus Armbruster if not isinstance(value, list): 3486f2ab609SMarkus Armbruster raise QAPISemError(info, 3496f2ab609SMarkus Armbruster "%s should be a type name or array" % source) 3506f2ab609SMarkus Armbruster 351e6c42b96SMarkus Armbruster if len(value) != 1 or not isinstance(value[0], str): 352e6c42b96SMarkus Armbruster raise QAPISemError(info, 353e6c42b96SMarkus Armbruster "%s: array type must contain single type name" % 354e6c42b96SMarkus Armbruster source) 355e6c42b96SMarkus Armbruster 35606cc46eeSMarkus Armbruster 357*e2050ef6SMarkus Armbrusterdef check_type_implicit(value: Optional[object], 35806cc46eeSMarkus Armbruster info: QAPISourceInfo, source: str, 35906cc46eeSMarkus Armbruster parent_name: Optional[str]) -> None: 36006cc46eeSMarkus Armbruster """ 36106cc46eeSMarkus Armbruster Normalize and validate an optional implicit struct type. 36206cc46eeSMarkus Armbruster 363*e2050ef6SMarkus Armbruster Accept ``None`` or a ``dict`` defining an implicit struct type. 364*e2050ef6SMarkus Armbruster The latter is normalized in place. 36506cc46eeSMarkus Armbruster 36606cc46eeSMarkus Armbruster :param value: The value to check. 36706cc46eeSMarkus Armbruster :param info: QAPI schema source file information. 36806cc46eeSMarkus Armbruster :param source: Error string describing this ``value``. 36906cc46eeSMarkus Armbruster :param parent_name: 37006cc46eeSMarkus Armbruster When the value of ``parent_name`` is in pragma 37106cc46eeSMarkus Armbruster ``member-name-exceptions``, an implicit struct type may 37206cc46eeSMarkus Armbruster violate the member naming rules. 37306cc46eeSMarkus Armbruster 37406cc46eeSMarkus Armbruster :raise QAPISemError: When ``value`` fails validation. 37506cc46eeSMarkus Armbruster :return: None 37606cc46eeSMarkus Armbruster """ 37706cc46eeSMarkus Armbruster if value is None: 37806cc46eeSMarkus Armbruster return 37906cc46eeSMarkus Armbruster 3800f231dcfSJohn Snow if not isinstance(value, dict): 381e6c42b96SMarkus Armbruster raise QAPISemError(info, 382e6c42b96SMarkus Armbruster "%s should be an object or type name" % source) 383e6c42b96SMarkus Armbruster 38406cc46eeSMarkus Armbruster permissive = parent_name in info.pragma.member_name_exceptions 385e6c42b96SMarkus Armbruster 386e6c42b96SMarkus Armbruster for (key, arg) in value.items(): 387e6c42b96SMarkus Armbruster key_source = "%s member '%s'" % (source, key) 388dbfe3c7cSMarkus Armbruster if key.startswith('*'): 389dbfe3c7cSMarkus Armbruster key = key[1:] 3909af4b6b9SMarkus Armbruster check_name_lower(key, info, key_source, 3915aceeac0SMarkus Armbruster permit_upper=permissive, 3925aceeac0SMarkus Armbruster permit_underscore=permissive) 393e6c42b96SMarkus Armbruster if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'): 394e6c42b96SMarkus Armbruster raise QAPISemError(info, "%s uses reserved name" % key_source) 39584ab0086SMarkus Armbruster check_keys(arg, info, key_source, ['type'], ['if', 'features']) 396e6c42b96SMarkus Armbruster check_if(arg, info, key_source) 39784ab0086SMarkus Armbruster check_features(arg.get('features'), info) 39806cc46eeSMarkus Armbruster check_type_name_or_array(arg['type'], info, key_source) 399e6c42b96SMarkus Armbruster 400e6c42b96SMarkus Armbruster 401*e2050ef6SMarkus Armbrusterdef check_type_name_or_implicit(value: Optional[object], 402*e2050ef6SMarkus Armbruster info: QAPISourceInfo, source: str, 403*e2050ef6SMarkus Armbruster parent_name: Optional[str]) -> None: 404*e2050ef6SMarkus Armbruster if value is None or isinstance(value, str): 405*e2050ef6SMarkus Armbruster return 406*e2050ef6SMarkus Armbruster 407*e2050ef6SMarkus Armbruster check_type_implicit(value, info, source, parent_name) 408*e2050ef6SMarkus Armbruster 409*e2050ef6SMarkus Armbruster 410b9ad358aSJohn Snowdef check_features(features: Optional[object], 411b9ad358aSJohn Snow info: QAPISourceInfo) -> None: 412a4865363SJohn Snow """ 413a4865363SJohn Snow Normalize and validate the ``features`` member. 414a4865363SJohn Snow 415a4865363SJohn Snow ``features`` may be a ``list`` of either ``str`` or ``dict``. 416a4865363SJohn Snow Any ``str`` element will be normalized to ``{'name': element}``. 417a4865363SJohn Snow 418a4865363SJohn Snow :forms: 419a4865363SJohn Snow :sugared: ``List[Union[str, Feature]]`` 420a4865363SJohn Snow :canonical: ``List[Feature]`` 421a4865363SJohn Snow 422a4865363SJohn Snow :param features: The features member value to validate. 423a4865363SJohn Snow :param info: QAPI schema source file information. 424a4865363SJohn Snow 425a4865363SJohn Snow :raise QAPISemError: When ``features`` fails validation. 426a4865363SJohn Snow :return: None, ``features`` is normalized in-place as needed. 427a4865363SJohn Snow """ 42823394b4cSPeter Krempa if features is None: 42923394b4cSPeter Krempa return 43023394b4cSPeter Krempa if not isinstance(features, list): 43123394b4cSPeter Krempa raise QAPISemError(info, "'features' must be an array") 4322ce51ef6SMarkus Armbruster features[:] = [f if isinstance(f, dict) else {'name': f} 4332ce51ef6SMarkus Armbruster for f in features] 434e42648dcSJohn Snow for feat in features: 43523394b4cSPeter Krempa source = "'features' member" 436e42648dcSJohn Snow assert isinstance(feat, dict) 437e42648dcSJohn Snow check_keys(feat, info, source, ['name'], ['if']) 438e42648dcSJohn Snow check_name_is_str(feat['name'], info, source) 439e42648dcSJohn Snow source = "%s '%s'" % (source, feat['name']) 4409e191d33SMarkus Armbruster check_name_lower(feat['name'], info, source) 441e42648dcSJohn Snow check_if(feat, info, source) 44223394b4cSPeter Krempa 44323394b4cSPeter Krempa 44442011059SJohn Snowdef check_enum(expr: QAPIExpression) -> None: 445a4865363SJohn Snow """ 446a4865363SJohn Snow Normalize and validate this expression as an ``enum`` definition. 447a4865363SJohn Snow 448a4865363SJohn Snow :param expr: The expression to validate. 449a4865363SJohn Snow 450a4865363SJohn Snow :raise QAPISemError: When ``expr`` is not a valid ``enum``. 451a4865363SJohn Snow :return: None, ``expr`` is normalized in-place as needed. 452a4865363SJohn Snow """ 453e6c42b96SMarkus Armbruster name = expr['enum'] 454e6c42b96SMarkus Armbruster members = expr['data'] 455e6c42b96SMarkus Armbruster prefix = expr.get('prefix') 45642011059SJohn Snow info = expr.info 457e6c42b96SMarkus Armbruster 458e6c42b96SMarkus Armbruster if not isinstance(members, list): 459e6c42b96SMarkus Armbruster raise QAPISemError(info, "'data' must be an array") 460e6c42b96SMarkus Armbruster if prefix is not None and not isinstance(prefix, str): 461e6c42b96SMarkus Armbruster raise QAPISemError(info, "'prefix' must be a string") 462e6c42b96SMarkus Armbruster 463407efbf9SMarkus Armbruster permissive = name in info.pragma.member_name_exceptions 464e6c42b96SMarkus Armbruster 465ad1ecfc6SMarkus Armbruster members[:] = [m if isinstance(m, dict) else {'name': m} 466ad1ecfc6SMarkus Armbruster for m in members] 467e6c42b96SMarkus Armbruster for member in members: 468e6c42b96SMarkus Armbruster source = "'data' member" 469b6c18755SMarkus Armbruster check_keys(member, info, source, ['name'], ['if', 'features']) 470a0c7b99bSMarkus Armbruster member_name = member['name'] 4710825f62cSMarkus Armbruster check_name_is_str(member_name, info, source) 4720825f62cSMarkus Armbruster source = "%s '%s'" % (source, member_name) 4730825f62cSMarkus Armbruster # Enum members may start with a digit 4740825f62cSMarkus Armbruster if member_name[0].isdigit(): 4750825f62cSMarkus Armbruster member_name = 'd' + member_name # Hack: hide the digit 4769af4b6b9SMarkus Armbruster check_name_lower(member_name, info, source, 477407efbf9SMarkus Armbruster permit_upper=permissive, 478407efbf9SMarkus Armbruster permit_underscore=permissive) 479e6c42b96SMarkus Armbruster check_if(member, info, source) 480b6c18755SMarkus Armbruster check_features(member.get('features'), info) 481e6c42b96SMarkus Armbruster 482e6c42b96SMarkus Armbruster 48342011059SJohn Snowdef check_struct(expr: QAPIExpression) -> None: 484a4865363SJohn Snow """ 485a4865363SJohn Snow Normalize and validate this expression as a ``struct`` definition. 486a4865363SJohn Snow 487a4865363SJohn Snow :param expr: The expression to validate. 488a4865363SJohn Snow 489a4865363SJohn Snow :raise QAPISemError: When ``expr`` is not a valid ``struct``. 490a4865363SJohn Snow :return: None, ``expr`` is normalized in-place as needed. 491a4865363SJohn Snow """ 4927a783ce5SJohn Snow name = cast(str, expr['struct']) # Checked in check_exprs 493e6c42b96SMarkus Armbruster members = expr['data'] 494e6c42b96SMarkus Armbruster 495*e2050ef6SMarkus Armbruster check_type_implicit(members, expr.info, "'data'", name) 49606cc46eeSMarkus Armbruster check_type_name(expr.get('base'), expr.info, "'base'") 497e6c42b96SMarkus Armbruster 498e6c42b96SMarkus Armbruster 49942011059SJohn Snowdef check_union(expr: QAPIExpression) -> None: 500a4865363SJohn Snow """ 501a4865363SJohn Snow Normalize and validate this expression as a ``union`` definition. 502a4865363SJohn Snow 503a4865363SJohn Snow :param expr: The expression to validate. 504a4865363SJohn Snow 505a4865363SJohn Snow :raise QAPISemError: when ``expr`` is not a valid ``union``. 506a4865363SJohn Snow :return: None, ``expr`` is normalized in-place as needed. 507a4865363SJohn Snow """ 5087a783ce5SJohn Snow name = cast(str, expr['union']) # Checked in check_exprs 5094e99f4b1SMarkus Armbruster base = expr['base'] 5104e99f4b1SMarkus Armbruster discriminator = expr['discriminator'] 511e6c42b96SMarkus Armbruster members = expr['data'] 51242011059SJohn Snow info = expr.info 513e6c42b96SMarkus Armbruster 51406cc46eeSMarkus Armbruster check_type_name_or_implicit(base, info, "'base'", name) 515e6c42b96SMarkus Armbruster check_name_is_str(discriminator, info, "'discriminator'") 516e6c42b96SMarkus Armbruster 5174918bb7dSJohn Snow if not isinstance(members, dict): 5184918bb7dSJohn Snow raise QAPISemError(info, "'data' must be an object") 5194918bb7dSJohn Snow 520e6c42b96SMarkus Armbruster for (key, value) in members.items(): 521e6c42b96SMarkus Armbruster source = "'data' member '%s'" % key 522e6c42b96SMarkus Armbruster check_keys(value, info, source, ['type'], ['if']) 523e6c42b96SMarkus Armbruster check_if(value, info, source) 52406cc46eeSMarkus Armbruster check_type_name(value['type'], info, source) 525e6c42b96SMarkus Armbruster 526e6c42b96SMarkus Armbruster 52742011059SJohn Snowdef check_alternate(expr: QAPIExpression) -> None: 528a4865363SJohn Snow """ 529a4865363SJohn Snow Normalize and validate this expression as an ``alternate`` definition. 530a4865363SJohn Snow 531a4865363SJohn Snow :param expr: The expression to validate. 532a4865363SJohn Snow 533a4865363SJohn Snow :raise QAPISemError: When ``expr`` is not a valid ``alternate``. 534a4865363SJohn Snow :return: None, ``expr`` is normalized in-place as needed. 535a4865363SJohn Snow """ 536e6c42b96SMarkus Armbruster members = expr['data'] 53742011059SJohn Snow info = expr.info 538e6c42b96SMarkus Armbruster 5398ec0e1a4SMarkus Armbruster if not members: 540e6c42b96SMarkus Armbruster raise QAPISemError(info, "'data' must not be empty") 5414918bb7dSJohn Snow 5424918bb7dSJohn Snow if not isinstance(members, dict): 5434918bb7dSJohn Snow raise QAPISemError(info, "'data' must be an object") 5444918bb7dSJohn Snow 545e6c42b96SMarkus Armbruster for (key, value) in members.items(): 546e6c42b96SMarkus Armbruster source = "'data' member '%s'" % key 547d83b4764SMarkus Armbruster check_name_lower(key, info, source) 548e6c42b96SMarkus Armbruster check_keys(value, info, source, ['type'], ['if']) 549e6c42b96SMarkus Armbruster check_if(value, info, source) 55006cc46eeSMarkus Armbruster check_type_name_or_array(value['type'], info, source) 551e6c42b96SMarkus Armbruster 552e6c42b96SMarkus Armbruster 55342011059SJohn Snowdef check_command(expr: QAPIExpression) -> None: 554a4865363SJohn Snow """ 555a4865363SJohn Snow Normalize and validate this expression as a ``command`` definition. 556a4865363SJohn Snow 557a4865363SJohn Snow :param expr: The expression to validate. 558a4865363SJohn Snow 559a4865363SJohn Snow :raise QAPISemError: When ``expr`` is not a valid ``command``. 560a4865363SJohn Snow :return: None, ``expr`` is normalized in-place as needed. 561a4865363SJohn Snow """ 562e6c42b96SMarkus Armbruster args = expr.get('data') 563e6c42b96SMarkus Armbruster rets = expr.get('returns') 564e6c42b96SMarkus Armbruster boxed = expr.get('boxed', False) 565e6c42b96SMarkus Armbruster 56606cc46eeSMarkus Armbruster if boxed: 56706cc46eeSMarkus Armbruster if args is None: 56842011059SJohn Snow raise QAPISemError(expr.info, "'boxed': true requires 'data'") 56906cc46eeSMarkus Armbruster check_type_name(args, expr.info, "'data'") 57006cc46eeSMarkus Armbruster else: 57106cc46eeSMarkus Armbruster check_type_name_or_implicit(args, expr.info, "'data'", None) 57206cc46eeSMarkus Armbruster check_type_name_or_array(rets, expr.info, "'returns'") 573e6c42b96SMarkus Armbruster 574e6c42b96SMarkus Armbruster 57542011059SJohn Snowdef check_event(expr: QAPIExpression) -> None: 576a4865363SJohn Snow """ 577a4865363SJohn Snow Normalize and validate this expression as an ``event`` definition. 578a4865363SJohn Snow 579a4865363SJohn Snow :param expr: The expression to validate. 580a4865363SJohn Snow 581a4865363SJohn Snow :raise QAPISemError: When ``expr`` is not a valid ``event``. 582a4865363SJohn Snow :return: None, ``expr`` is normalized in-place as needed. 583a4865363SJohn Snow """ 584e6c42b96SMarkus Armbruster args = expr.get('data') 585e6c42b96SMarkus Armbruster boxed = expr.get('boxed', False) 586e6c42b96SMarkus Armbruster 58706cc46eeSMarkus Armbruster if boxed: 58806cc46eeSMarkus Armbruster if args is None: 58942011059SJohn Snow raise QAPISemError(expr.info, "'boxed': true requires 'data'") 59006cc46eeSMarkus Armbruster check_type_name(args, expr.info, "'data'") 59106cc46eeSMarkus Armbruster else: 59206cc46eeSMarkus Armbruster check_type_name_or_implicit(args, expr.info, "'data'", None) 593e6c42b96SMarkus Armbruster 594e6c42b96SMarkus Armbruster 59542011059SJohn Snowdef check_exprs(exprs: List[QAPIExpression]) -> List[QAPIExpression]: 596a4865363SJohn Snow """ 597a4865363SJohn Snow Validate and normalize a list of parsed QAPI schema expressions. 598a4865363SJohn Snow 599a4865363SJohn Snow This function accepts a list of expressions and metadata as returned 600a4865363SJohn Snow by the parser. It destructively normalizes the expressions in-place. 601a4865363SJohn Snow 602a4865363SJohn Snow :param exprs: The list of expressions to normalize and validate. 603a4865363SJohn Snow 604a4865363SJohn Snow :raise QAPISemError: When any expression fails validation. 605a4865363SJohn Snow :return: The same list of expressions (now modified). 606a4865363SJohn Snow """ 60742011059SJohn Snow for expr in exprs: 60842011059SJohn Snow info = expr.info 60942011059SJohn Snow doc = expr.doc 610e6c42b96SMarkus Armbruster 611e6c42b96SMarkus Armbruster if 'include' in expr: 612e6c42b96SMarkus Armbruster continue 613e6c42b96SMarkus Armbruster 6146dcf0371SMarkus Armbruster metas = expr.keys() & {'enum', 'struct', 'union', 'alternate', 6156dcf0371SMarkus Armbruster 'command', 'event'} 6166dcf0371SMarkus Armbruster if len(metas) != 1: 6176dcf0371SMarkus Armbruster raise QAPISemError( 6186dcf0371SMarkus Armbruster info, 6196dcf0371SMarkus Armbruster "expression must have exactly one key" 6206dcf0371SMarkus Armbruster " 'enum', 'struct', 'union', 'alternate'," 6216dcf0371SMarkus Armbruster " 'command', 'event'") 6226dcf0371SMarkus Armbruster meta = metas.pop() 623e6c42b96SMarkus Armbruster 6247a783ce5SJohn Snow check_name_is_str(expr[meta], info, "'%s'" % meta) 6257a783ce5SJohn Snow name = cast(str, expr[meta]) 626e6c42b96SMarkus Armbruster info.set_defn(meta, name) 627e6c42b96SMarkus Armbruster check_defn_name_str(name, info, meta) 628e6c42b96SMarkus Armbruster 629e6c42b96SMarkus Armbruster if doc: 630e6c42b96SMarkus Armbruster if doc.symbol != name: 631e6c42b96SMarkus Armbruster raise QAPISemError( 632e6c42b96SMarkus Armbruster info, "documentation comment is for '%s'" % doc.symbol) 633e6c42b96SMarkus Armbruster doc.check_expr(expr) 634e6c42b96SMarkus Armbruster elif info.pragma.doc_required: 635e6c42b96SMarkus Armbruster raise QAPISemError(info, 636e6c42b96SMarkus Armbruster "documentation comment required") 637e6c42b96SMarkus Armbruster 638e6c42b96SMarkus Armbruster if meta == 'enum': 639e6c42b96SMarkus Armbruster check_keys(expr, info, meta, 640013b4efcSMarkus Armbruster ['enum', 'data'], ['if', 'features', 'prefix']) 64142011059SJohn Snow check_enum(expr) 642e6c42b96SMarkus Armbruster elif meta == 'union': 643e6c42b96SMarkus Armbruster check_keys(expr, info, meta, 6444e99f4b1SMarkus Armbruster ['union', 'base', 'discriminator', 'data'], 6454e99f4b1SMarkus Armbruster ['if', 'features']) 646e6c42b96SMarkus Armbruster normalize_members(expr.get('base')) 647e6c42b96SMarkus Armbruster normalize_members(expr['data']) 64842011059SJohn Snow check_union(expr) 649e6c42b96SMarkus Armbruster elif meta == 'alternate': 650e6c42b96SMarkus Armbruster check_keys(expr, info, meta, 651013b4efcSMarkus Armbruster ['alternate', 'data'], ['if', 'features']) 652e6c42b96SMarkus Armbruster normalize_members(expr['data']) 65342011059SJohn Snow check_alternate(expr) 654e6c42b96SMarkus Armbruster elif meta == 'struct': 655e6c42b96SMarkus Armbruster check_keys(expr, info, meta, 656e6c42b96SMarkus Armbruster ['struct', 'data'], ['base', 'if', 'features']) 657e6c42b96SMarkus Armbruster normalize_members(expr['data']) 65842011059SJohn Snow check_struct(expr) 659e6c42b96SMarkus Armbruster elif meta == 'command': 660e6c42b96SMarkus Armbruster check_keys(expr, info, meta, 661e6c42b96SMarkus Armbruster ['command'], 66223394b4cSPeter Krempa ['data', 'returns', 'boxed', 'if', 'features', 663e6c42b96SMarkus Armbruster 'gen', 'success-response', 'allow-oob', 66404f22362SKevin Wolf 'allow-preconfig', 'coroutine']) 665e6c42b96SMarkus Armbruster normalize_members(expr.get('data')) 66642011059SJohn Snow check_command(expr) 667e6c42b96SMarkus Armbruster elif meta == 'event': 668e6c42b96SMarkus Armbruster check_keys(expr, info, meta, 669013b4efcSMarkus Armbruster ['event'], ['data', 'boxed', 'if', 'features']) 670e6c42b96SMarkus Armbruster normalize_members(expr.get('data')) 67142011059SJohn Snow check_event(expr) 672e6c42b96SMarkus Armbruster else: 673e6c42b96SMarkus Armbruster assert False, 'unexpected meta type' 674e6c42b96SMarkus Armbruster 675e6c42b96SMarkus Armbruster check_if(expr, info, meta) 676013b4efcSMarkus Armbruster check_features(expr.get('features'), info) 67742011059SJohn Snow check_flags(expr) 678e6c42b96SMarkus Armbruster 679e6c42b96SMarkus Armbruster return exprs 680