Advertisement
Guest User

JsonValidate.py

a guest
May 3rd, 2024
73
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 33.39 KB | None | 0 0
  1. """
  2.    JSONValidate.py - JSON data structures are often received from a uncontrolled sources such as
  3.                      Web Clients, as a string representation. Standard python tools such as json.py
  4.                      may be used to parse and validate the string representation into Python data
  5.                      structures, comprised of dicts, lists, and assorted basic types.
  6.                      
  7.                      JSONValidate.py provides application level schema validation.
  8.                          - Schema validation.
  9.                          - Data type validation.
  10.                          - Required/optional data members in dicts.
  11.                          - Lists with optional min/max lengths.
  12.                          - List member tye validation.
  13.                          - Strings with optional min/max lengths.
  14.                          - Integers with optional min/max values.
  15.                          - String value checks
  16.                          - Integer Value checks
  17.                          - Extensible schema types.
  18.                          
  19. """
  20. import sys
  21.  
  22.  
  23. class SchemaBase(object):
  24.     """SchemaBase - Base class for all schema definition classes.
  25.        Extend on this and implement the following methods, to implement your own special schema types.
  26.        Parameters:
  27.            optional - Specifies whether a particular schema element is optional in the data.
  28.            custom_validate - Custom Function to validate the data.
  29.                            - Same prototype as def validate(self, fqn, json_dict, errors) below.
  30.                            - Only called if the validate() implementation would return True.
  31.    """
  32.     def __init__(self, optional=False, custom_validate=None):
  33.         self.optional = optional
  34.         self.custom_validate = custom_validate
  35.  
  36.     def is_optional(self):
  37.         return self.optional
  38.  
  39.     def has_custom_validation(self):
  40.         return self.custom_validate is not None
  41.  
  42.     def self_check(self, fqn, errors):
  43.         """self_check - Implement this, to validate that an instance of a schema class is valid.
  44.            returns: True  - Schema element and subordinate schema elements are all valid.
  45.                     False - Schema element  or one or more subordinate schema elements are invalid.
  46.            """
  47.         errors.append("{}: self_check() must be implemented for SchemaBase derived classes.".format(self.__class__.__name__))
  48.         return False
  49.    
  50.     def valid_type(self, data, errors):
  51.         """valid_type - Implement this, to validate that a python data structure(representative of JSON) is of the correct type.
  52.            returns: True  - Data element has the right type according to the schema element.
  53.                     False - Data element has the wrong type according to the schema element.
  54.            """
  55.         errors.append("{}: valid_type() must be implemented for SchemaBase derived classes.".format(self.__class__.__name__))
  56.         return False
  57.    
  58.     def validate(self, fqn, data, errors):
  59.         """validate - Implement this, to validate that a python data structure(representative of JSON) is comprehensively correct.
  60.            returns: True  - Data element and subordinate data elements are all valid.
  61.                     False - Data element  or one or more subordinate data elements are invalid.
  62.            """
  63.         errors.append("{}: validate() must be implemented for SchemaBase derived classes.".format(self.__class__.__name__))
  64.         return False
  65.  
  66.  
  67. class SchemaAny(SchemaBase):
  68.     """SchemaAny - Schema type that matches any data types.
  69.        This should be used only sparingly. It represents the case where there is an application level interpretation of some
  70.        sub-data structure of the message which may not be validated using this module.
  71.        Parameters:
  72.            optional - Whether the Any element is optional in the containing structure (dict or list).
  73.            custom_validate - Custom Function to validate the data.
  74.                            - Same prototype as def validate(self, fqn, json_dict, errors) below.
  75.                            - Only called if the validate() implementation would return True.
  76.        """
  77.     def __init__(self, optional=False, custom_validate=None):
  78.         SchemaBase.__init__(self, optional, custom_validate)
  79.  
  80.     def self_check(self, fqn, errors):
  81.         return True
  82.  
  83.     def valid_type(self, data, errors):
  84.         return True
  85.  
  86.     def validate(self, fqn, json_any, errors):
  87.         return self.custom_validate(self, fqn, json_any, errors) if self.has_custom_validation() else True
  88.  
  89.  
  90. class SchemaStr(SchemaBase):
  91.     """SchemaStr - Validates a data structure member that is supposed to be a string.
  92.        Parameters:
  93.            minlen   - minimum length of string. Target data string must be >= this length.
  94.                       You can not specify this as a negative value.
  95.                         minlen must <= maxlen.
  96.            maxlen   - maximum length of string. Target data string must be <= this length.
  97.            valid    - None or a list of valid string values.
  98.            optional - Whether the string element is optional in the containing structure (dict or list).
  99.            custom_validate - Custom Function to validate the data.
  100.                            - Same prototype as def validate(self, fqn, json_dict, errors) below.
  101.                            - Only called if the validate() implementation would return True.
  102.    """
  103.     def __init__(self, minlen=None, maxlen=None, valid=None, optional=False, custom_validate=None):
  104.         SchemaBase.__init__(self, optional, custom_validate)
  105.         self.minlen = minlen
  106.         self.maxlen = maxlen
  107.         self.valid = valid
  108.  
  109.     def self_check(self, fqn, errors):
  110.         result = True
  111.         if self.minlen is not None:
  112.             if not isinstance(self.minlen, int) or self.minlen < 0:
  113.                 errors.append("{}({}): minlen({}) must be Integer(and >= 0) or None. Default=0.".format(self.__class__.__name__, fqn, repr(self.minlen)))
  114.                 result = False
  115.         if self.maxlen is not None:
  116.             if not isinstance(self.maxlen, int):
  117.                 errors.append("{}({}): maxlen({}) must be Integer or None".format(self.__class__.__name__, fqn, repr(self.maxlen)))
  118.                 result = False
  119.             elif isinstance(self.minlen, int):
  120.                 if self.minlen > self.maxlen:
  121.                     errors.append("{}({}): maxlen({}) must be >= minlen({}) or None".format(self.__class__.__name__, fqn, self.maxlen, self.minlen))
  122.                     result = False
  123.         if self.valid is not None:
  124.             if not isinstance(self.valid, list):
  125.                 errors.append("{}({}): valid param must be None or a list. Got {}".format(self.__class__.__name__, fqn, repr(self.valid)))
  126.                 result = False
  127.             else:
  128.                 if len(self.valid) == 0:
  129.                     errors.append("{}({}): Valid list may not be empty.".format(self.__class__.__name__, fqn))
  130.                     result = False
  131.                 else:
  132.                     for v in self.valid:
  133.                         if not isinstance(v, str):
  134.                             errors.append("{}({}): element({}) of valid list must be of type string.".format(self.__class__.__name__, fqn, repr(v)))
  135.                             result = False
  136.         return result
  137.  
  138.     def valid_type(self, json_str, errors):
  139.         return isinstance(json_str, str)
  140.  
  141.     def validate(self, fqn, json_str, errors):
  142.         result = True
  143.         if not self.valid_type(json_str, errors):
  144.             errors.append("{}({}): Expected 'str', got {}".format(self.__class__.__name__, fqn, repr(json_str)))
  145.             result = False
  146.         elif self.minlen is not None and len(json_str) < self.minlen:
  147.             errors.append("{}({}): String({}) shorter than minlen({}).".format(self.__class__.__name__, fqn, json_str, self.minlen))
  148.             result = False
  149.         elif self.maxlen is not None and len(json_str) > self.maxlen:
  150.             errors.append("{}({}): String({}) longer than maxlen({}).".format(self.__class__.__name__, fqn, json_str, self.maxlen))
  151.             result = False
  152.         if result and self.valid is not None:
  153.             if json_str not in self.valid:
  154.                 errors.append("{}({}): String({}) not in valid list({}).".format(self.__class__.__name__, fqn, json_str, self.valid))
  155.                 result = False
  156.         return self.custom_validate(self, fqn, json_str, errors) if result and self.has_custom_validation() else result
  157.  
  158.  
  159. class SchemaInt(SchemaBase):
  160.     """SchemaInt - Validates a data structure member that is supposed to be an integer.
  161.        Parameters:
  162.            minval   - minimum value of integer. Target data value must be >= this value.
  163.                       minval must be <= maxval.
  164.            maxval   - maximum value of integer. Target data value must be <= this value.
  165.            valid    - None or a list of valid integer values.
  166.            optional - Whether the integer element is optional in the containing structure (dict or list).
  167.            custom_validate - Custom Function to validate the data.
  168.                            - Same prototype as def validate(self, fqn, json_dict, errors) below.
  169.                            - Only called if the validate() implementation would return True.
  170.    """
  171.     def __init__(self, minval=None, maxval=None, valid=None, optional=False, custom_validate=None):
  172.         SchemaBase.__init__(self, optional, custom_validate)
  173.         self.minval = minval
  174.         self.maxval = maxval
  175.         self.valid = valid
  176.        
  177.     def self_check(self, fqn, errors):
  178.         result = True
  179.         if self.minval is not None:
  180.             if not isinstance(self.minval, int):
  181.                 errors.append("{}({}): minval({}) must be Integer or None.".format(self.__class__.__name__, fqn, repr(self.minval)))
  182.                 result = False
  183.         if self.maxval is not None:
  184.             if not isinstance(self.maxval, int):
  185.                 errors.append("{}({}): maxval({}) must be Integer or None".format(self.__class__.__name__, fqn, repr(self.maxval)))
  186.                 result = False
  187.             elif isinstance(self.minval, int):
  188.                 if self.minval > self.maxval:
  189.                     errors.append("{}({}): maxval({}) must be >= minval({}) or None".format(self.__class__.__name__, fqn, self.maxval, self.minval))
  190.                     result = False
  191.         if self.valid is not None:
  192.             if not isinstance(self.valid, list):
  193.                 errors.append("{}({}): valid param must be None or a list. Got {}".format(self.__class__.__name__, fqn, repr(self.valid)))
  194.                 result = False
  195.             else:
  196.                 if len(self.valid) == 0:
  197.                     errors.append("{}({}): Valid list may not be empty.".format(self.__class__.__name__, fqn))
  198.                     result = False
  199.                 else:
  200.                     for v in self.valid:
  201.                         if not isinstance(v, int):
  202.                             errors.append("{}({}): element({}) of valid list must be of type integer.".format(self.__class__.__name__, fqn, repr(v)))
  203.                             result = False
  204.         return result
  205.  
  206.     def valid_type(self, json_int, errors):
  207.         return isinstance(json_int, int)
  208.  
  209.     def validate(self, fqn, json_int, errors):
  210.         result = True
  211.         if not self.valid_type(json_int, errors):
  212.             errors.append("{}({}): Expected 'int', got {}".format(self.__class__.__name__, fqn, repr(json_int)))
  213.             result = False
  214.         elif self.minval is not None and json_int < self.minval:
  215.             errors.append("{}({}): Integer({}) less than minval({}).".format(self.__class__.__name__, fqn, json_int, self.minval))
  216.             result = False
  217.         elif self.maxval is not None and json_int > self.maxval:
  218.             errors.append("{}({}): Integer({}) greater than maxval({}).".format(self.__class__.__name__, fqn, json_int, self.maxval))
  219.             result = False
  220.         if result and self.valid is not None:
  221.             if json_int not in self.valid:
  222.                 errors.append("{}({}): Integer({}) not in valid list({}).".format(self.__class__.__name__, fqn, json_int, self.valid))
  223.                 result = False
  224.         return self.custom_validate(self, fqn, json_int, errors) if result and self.has_custom_validation() else result
  225.  
  226.  
  227. class SchemaFloat(SchemaBase):
  228.     """SchemaFloat - Validates a data structure member that is supposed to be an floating point number.
  229.        Parameters:
  230.            minval   - minimum value of float. Target data value must be >= this value.
  231.                       minval must be <= maxval.
  232.            maxval   - maximum value of float. Target data value must be <= this value.
  233.            optional - Whether the float element is optional in the containing structure (dict or list).
  234.            valid    - None or a list of valid float values.
  235.            custom_validate - Custom Function to validate the data.
  236.                            - Same prototype as def validate(self, fqn, json_dict, errors) below.
  237.                            - Only called if the validate() implementation would return True.
  238.    """
  239.     def __init__(self, minval=None, maxval=None, valid=None, optional=False, custom_validate=None):
  240.         SchemaBase.__init__(self, optional, custom_validate)
  241.         self.minval = minval
  242.         self.maxval = maxval
  243.         self.valid = valid
  244.  
  245.     def self_check(self, fqn, errors):
  246.         result = True
  247.         if self.minval is not None:
  248.             if not isinstance(self.minval, float):
  249.                 errors.append("{}({}): minval({}) must be Float.".format(self.__class__.__name__, fqn, repr(self.minval)))
  250.                 result = False
  251.         if self.maxval is not None:
  252.             if not isinstance(self.maxval, float):
  253.                 errors.append("{}({}): maxval({}) must be Float or None".format(self.__class__.__name__, fqn, repr(self.maxval)))
  254.                 result = False
  255.             elif isinstance(self.minval, float):
  256.                 if self.minval > self.maxval:
  257.                     errors.append("{}({}): maxval({}) must be >= minval({}) or None".format(self.__class__.__name__, fqn, self.maxval, self.minval))
  258.                     result = False
  259.         if self.valid is not None:
  260.             if not isinstance(self.valid, list):
  261.                 errors.append("{}({}): valid param must be None or a list. Got {}".format(self.__class__.__name__, fqn, repr(self.valid)))
  262.                 result = False
  263.             else:
  264.                 if len(self.valid) == 0:
  265.                     errors.append("{}({}): Valid list may not be empty.".format(self.__class__.__name__, fqn))
  266.                     result = False
  267.                 else:
  268.                     for v in self.valid:
  269.                         if not isinstance(v, float):
  270.                             errors.append("{}({}): element({}) of valid list must be of type Float.".format(self.__class__.__name__, fqn, repr(v)))
  271.                             result = False
  272.         return result
  273.  
  274.     def valid_type(self, json_float, errors):
  275.         return isinstance(json_float, float)
  276.  
  277.     def validate(self, fqn, json_float, errors):
  278.         result = True
  279.         if not self.valid_type(json_float, errors):
  280.             errors.append("{}({}): Expected 'float', got {}".format(self.__class__.__name__, fqn, repr(json_float)))
  281.             result = False
  282.         elif self.minval is not None and json_float < self.minval:
  283.             errors.append("{}({}): Float({}) less than minval({}).".format(self.__class__.__name__, fqn, json_float, self.minval))
  284.             result = False
  285.         elif self.maxval is not None and json_float > self.maxval:
  286.             errors.append("{}({}): Float({}) greater than maxval({}).".format(self.__class__.__name__, fqn, json_float, self.maxval))
  287.             result = False
  288.         if result and self.valid is not None:
  289.             if json_float not in self.valid:
  290.                 errors.append("{}({}): Float({}) not in valid list({}).".format(self.__class__.__name__, fqn, json_float, self.valid))
  291.                 result = False
  292.         return self.custom_validate(self, fqn, json_float, errors) if result and self.has_custom_validation() else result
  293.  
  294.  
  295. class SchemaList(SchemaBase, list):
  296.     """SchemaList - Validates a data structure member that is supposed to be a list.
  297.            e.g. SchemaList([SchemaStr()])    defines a JSON list with any number of members that must be a strings.
  298.        Parameters:
  299.            [list] - SchemaList is also derived from base Python list class.
  300.                     It may be constructed as per any list, but it is expected that it will be constructed with a list of
  301.                     other SchemaBase derived objects, defining the valid members of the list.
  302.            minlen - minimum length of list. Target data list must be >= this length.
  303.                     You can not specify this as a negative value.
  304.                     minlen must <= maxlen.
  305.            maxlen - maximum length of list. Target data list must be <= this length.
  306.            custom_validate - Custom Function to validate the data.
  307.                            - Same prototype as def validate(self, fqn, json_dict, errors) below.
  308.                            - Only called if the validate() implementation would return True.
  309.        """
  310.     def __init__(self, list_param=(), minlen=None, maxlen=None, optional=False, custom_validate=None):
  311.         if list_param is None:
  312.             list.__init__(self)
  313.         else:
  314.             list.__init__(self, list_param)
  315.         SchemaBase.__init__(self, optional, custom_validate)
  316.         self.minlen = minlen
  317.         self.maxlen = maxlen
  318.  
  319.     def self_check(self, fqn, errors):
  320.         result = True
  321.  
  322.         # Validate length parameters for list
  323.         if len(self) == 0:    # Default Empty SchemaList to allow SchemaAny
  324.             self.append(SchemaAny())
  325.         if self.minlen is not None:
  326.             if not isinstance(self.minlen, int) or self.minlen < 0:
  327.                 errors.append("{}({}): minlen({}) must be Integer(and >= 0). Default=0.".format(self.__class__.__name__, fqn, repr(self.minlen)))
  328.                 result = False
  329.         if self.maxlen is not None:
  330.             if not isinstance(self.maxlen, int):
  331.                 errors.append("{}({}): maxlen({}) must be Integer or None".format(self.__class__.__name__, fqn, repr(self.maxlen)))
  332.                 result = False
  333.             elif isinstance(self.minlen, int):
  334.                 if self.minlen > self.maxlen:
  335.                     errors.append("{}({}): maxlen({}) must be >= minlen({}) or None".format(self.__class__.__name__, fqn, self.maxlen, self.minlen))
  336.                     result = False            
  337.        
  338.         # Validate schema list members.
  339.         for seq, schema_type in enumerate(self):
  340.             elem_fqn = '.'.join([fqn, str(seq)])
  341.             if not isinstance(schema_type, SchemaBase):
  342.                 errors.append("{}({}): Member({}) must be derived from SchemaBase".format(self.__class__.__name__, elem_fqn, repr(schema_type)))
  343.                 result = False
  344.                 continue
  345.             if not schema_type.self_check(elem_fqn, errors):
  346.                 result = False
  347.         return result
  348.    
  349.     def valid_type(self, json_list, errors):
  350.         return isinstance(json_list, list)
  351.  
  352.     def validate(self, fqn, json_list, errors):
  353.         result = True
  354.         if not self.valid_type(json_list, errors):
  355.             errors.append("{}({}): Expected 'list', got {}".format(self.__class__.__name__, fqn, repr(json_list)))
  356.             return False
  357.  
  358.         # Impose length restrictions
  359.         if self.minlen is not None and len(json_list) < self.minlen:
  360.             errors.append("{}({}): List({}) shorter than minlen({}).".format(self.__class__.__name__, fqn, json_list, self.minlen))
  361.             result = False
  362.         elif self.maxlen is not None and len(json_list) > self.maxlen:
  363.             errors.append("{}({}): List({}) longer than maxlen({}).".format(self.__class__.__name__, fqn, json_list, self.maxlen))
  364.             result = False
  365.  
  366.         # Check each element of the json list
  367.         for seq, json_elem in enumerate(json_list):
  368.             elem_fqn = fqn + '[' + str(seq) + ']'    # Express fqn with [0] index notation.
  369.             for schema_type in self:                # Each element matched against the first item in the schema list that allows its type.
  370.                 if schema_type.valid_type(json_elem, errors):
  371.                     result = schema_type.validate(elem_fqn, json_elem, errors) and result
  372.                     break    # 1st match does validation, ignore others.
  373.             else:    # None matched.
  374.                 errors.append("{}({}): Expected one of {}, got {}".format(self.__class__.__name__, elem_fqn, repr(self), json_elem))
  375.                 result = False
  376.  
  377.         return self.custom_validate(self, fqn, json_list, errors) if result and self.has_custom_validation() else result
  378.  
  379.  
  380. class SchemaDict(SchemaBase, dict):
  381.     """SchemaDict - Validates a data structure member that is supposed to be a dict.
  382.            SchemaDict is also expected to be the top level schema class for any message validation schema.
  383.            e.g. SchemaDict({'a': SchemaStr(),}) defines a JSON dict with a single member named 'a' that must be a string.
  384.        Parameters:
  385.            {dict} - SchemaDict is also derived from base Python dict class.
  386.                     It may be constructed as per any dict, but it is expected that it will be constructed with a dict of
  387.                     other SchemaBase derived objects, defining the valid members of the dict.
  388.            minlen - minimum length of dict. Target data dict must be >= this length.
  389.                     You can not specify this as a negative value.
  390.                     minlen must <= maxlen.
  391.            maxlen - maximum length of dict. Target data list must be <= this length.
  392.            optional - True/False if this schema element is optional.
  393.            custom_validate - Custom Function to validate the names and values in the dictionary.
  394.                            - Same prototype as def validate(self, fqn, json_dict, errors) below.
  395.                            - Only called if the base validate() would return True.
  396.        """
  397.     def __init__(self, dict_param=None, minlen=None, maxlen=None, optional=False, custom_validate=None):
  398.         if dict_param is None:
  399.             dict.__init__(self)
  400.         else:
  401.             dict.__init__(self, dict_param)
  402.         SchemaBase.__init__(self, optional, custom_validate)
  403.         self.minlen = minlen
  404.         self.maxlen = maxlen
  405.  
  406.     def self_check(self, fqn, errors):
  407.         result = True
  408.  
  409.         # Validate length parameters for dict
  410.         if len(self) == 0:  # Default Empty SchemaDict to allow {"*":SchemaAny}
  411.             self["*"] = SchemaAny()
  412.         if self.minlen is not None:
  413.             if not isinstance(self.minlen, int) or self.minlen < 0:
  414.                 errors.append("{}({}): minlen({}) must be Integer(and >= 0). Default=0.".format(self.__class__.__name__, fqn, repr(self.minlen)))
  415.                 result = False
  416.         if self.maxlen is not None:
  417.             if not isinstance(self.maxlen, int):
  418.                 errors.append("{}({}): maxlen({}) must be Integer or None".format(self.__class__.__name__, fqn, repr(self.maxlen)))
  419.                 result = False
  420.             elif isinstance(self.minlen, int):
  421.                 if self.minlen > self.maxlen:
  422.                     errors.append("{}({}): maxlen({}) must be >= minlen({}) or None".format(self.__class__.__name__, fqn, self.maxlen, self.minlen))
  423.                     result = False
  424.  
  425.         # Validate schema elements of dict
  426.         for name, schema_type in self.items():
  427.             elem_fqn = '.'.join([fqn, name])
  428.             if not isinstance(schema_type, SchemaBase):
  429.                 errors.append("{}({}): Member({}) must be derived from SchemaBase".format(self.__class__.__name__, elem_fqn, repr(schema_type)))
  430.                 result = False
  431.                 continue
  432.             if not schema_type.self_check(elem_fqn, errors):
  433.                 result = False
  434.  
  435.         return result
  436.  
  437.     def valid_type(self, json_dict, errors):
  438.         return isinstance(json_dict, dict)
  439.    
  440.     def validate(self, fqn, json_dict, errors):
  441.         result = True
  442.         if not self.valid_type(json_dict, errors):
  443.             errors.append("{}({}): Expected 'dict', got {}".format(self.__class__.__name__, fqn, repr(json_dict)))
  444.             return False
  445.  
  446.         # Impose length restrictions
  447.         if self.minlen is not None and len(json_dict) < self.minlen:
  448.             errors.append("{}({}): dict({}) shorter than minlen({}).".format(self.__class__.__name__, fqn, json_dict, self.minlen))
  449.             result = False
  450.         elif self.maxlen is not None and len(json_dict) > self.maxlen:
  451.             errors.append("{}({}): dict({}) longer than maxlen({}).".format(self.__class__.__name__, fqn, json_dict, self.maxlen))
  452.             result = False
  453.  
  454.         # Check each element of the json dict data present.
  455.         for name, json_elem in json_dict.items():
  456.             if name in self:    # Match specific named element first.
  457.                 elem_fqn = '.'.join([fqn, name])  # Extend fqn to next dot depth.
  458.                 result = self[name].validate(elem_fqn, json_elem, errors) and result
  459.             elif "*" in self:   # If not matched to specific named element, try Wildcard to allow any label name.
  460.                 elem_fqn = '.'.join([fqn, name])  # Extend fqn to next dot depth.
  461.                 result = self["*"].validate(elem_fqn, json_elem, errors) and result
  462.             else:
  463.                 errors.append("{}({}): Unexpected element ({}={})".format(self.__class__.__name__, fqn, name, json_elem))
  464.                 result = False
  465.  
  466.         # Reverse check, that all schema elements are present in the data
  467.         for name, schema_type in self.items():
  468.             if name != "*" and name not in json_dict and not schema_type.is_optional():
  469.                 errors.append("{}({}): Expected element ({}) missing".format(self.__class__.__name__, fqn, name))
  470.                 result = False
  471.  
  472.         return self.custom_validate(self, fqn, json_dict, errors) if result and self.has_custom_validation() else result
  473.  
  474.  
  475. if __name__ == "__main__":
  476.  
  477.     def validate_schema(schema, schema_name):
  478.         errors = []
  479.         schema_valid = schema.self_check(schema_name, errors)
  480.         if not schema_valid:
  481.             print("validate_schema:", schema_name, " is invalid:", file=sys.stderr)
  482.             for error in errors:
  483.                 print("   ", error, file=sys.stderr)
  484.         else:
  485.             print("validate_schema:", schema_name, " is valid.", file=sys.stderr)
  486.  
  487.     def validate_json_data(json_data, json_data_name, schema, schema_name):
  488.         errors = []
  489.         json_valid = schema.validate(json_data_name, json_data, errors)
  490.         if not json_valid:
  491.             print("validate_json_data(using", schema_name, "):", json_data_name, "is invalid:", file=sys.stderr)
  492.             for error in errors:
  493.                 print("   ", error, file=sys.stderr)
  494.         else:
  495.             print("validate_json_data(using", schema_name, "):", json_data_name, "is valid.", file=sys.stderr)
  496.    
  497.     # Define and validate a valid schema.
  498.     def my_custom_validate(self, fqn, json_dict, errors):
  499.         if "Whatever" not in json_dict:
  500.             errors.append("{}({}): dict({}) must contain 'Whatever'.".format(self.__class__.__name__, fqn, json_dict))
  501.             return False
  502.         return True
  503.  
  504.     valid_schema = SchemaDict({
  505.         'a1': SchemaList([                            # SchemaList of types allowed in SchemaList. Construct with []
  506.             SchemaStr(maxlen=32),
  507.             SchemaInt(minval=10, maxval=42)
  508.         ]),
  509.         'a2': SchemaStr(minlen=10),
  510.         'a3': SchemaStr(maxlen=10),
  511.         'a4': SchemaInt(),                            # Any integer
  512.         'a5': SchemaDict({
  513.             'b1': SchemaInt(minval=0),                # Any maxval >= minval
  514.             'b2': SchemaInt(maxval=42),               # Any minval <= maxval
  515.             'b3': SchemaInt(minval=-1, maxval=42),
  516.             'b4': SchemaList(),                       # Defaults to SchemaList of Any
  517.             'b5': SchemaList([                        # SchemaList of schemas allowed in list
  518.                 SchemaDict({
  519.                     'c1': SchemaInt(),                # fqn of this would be "a5.b5[0].c1"
  520.                     'c2': SchemaStr(),
  521.                 }),
  522.             ]),
  523.             'b6': SchemaList([
  524.                 SchemaInt(),
  525.             ], minlen=2, maxlen=3),
  526.         }),
  527.         'a6': SchemaInt(optional=True),                # Optional member. If it's there, must be int.
  528.         'a7': SchemaStr(valid=["abc", "123"]),         # Valid string  values list
  529.         'a8': SchemaInt(valid=[1, 2, 3]),              # Valid integer values list
  530.         'a9': SchemaDict({
  531.             "*": SchemaStr(valid=["StrInWildcard", ])
  532.         }, custom_validate=my_custom_validate),
  533.     })
  534.     validate_schema(valid_schema, "valid_schema")
  535.  
  536.     # Define and validate an invalid schema.
  537.     class NonSchema:
  538.         pass
  539.     invalid_schema = SchemaDict({
  540.         'a1': NonSchema(),                                 # Invalid - Non schema member
  541.         'a2': SchemaStr(minlen=2, maxlen='z'),              # Invalid - min/maxlen types.
  542.         'a3': SchemaStr(maxlen=-1),                         # Invalid - Max string length < 0
  543.         'a4': SchemaInt(minval='a', maxval='z'),            # Invalid - min/maxval types.
  544.         'a5': SchemaDict({
  545.             'b1': NonSchema(),                             # Invalid - Non scheme member in sub-Dict
  546.             'b2': SchemaInt(maxval=42),                     # Any minval <= maxval
  547.             'b3': SchemaInt(minval=-1, maxval=42),
  548.             'b4': SchemaList(),                             # Defaults to SchemaList of Any
  549.             'b5': SchemaList([                              # SchemaList of schemas allowed in list
  550.                 NonSchema(),                               # Invalid - Non schema member in list
  551.             ]),
  552.             'b6': SchemaList([                              # Invalid - Negative list minlen
  553.                 SchemaInt(),
  554.             ], minlen=-1, maxlen=3),
  555.             'b7': SchemaList([                              # Invalid - maxlen <= minlen
  556.                 SchemaInt(),
  557.             ], minlen=3, maxlen=1),
  558.             'b8': SchemaList([], minlen='a', maxlen='z'),   # Invalid - minlen, maxlen types.
  559.         }),
  560.         'a7': SchemaStr(valid=NonSchema),                  # Invalid - should be list
  561.         'a8': SchemaStr(valid=[]),                          # Invalid - valid list can't be empty
  562.         'a9': SchemaStr(valid=[1, 2]),                      # Invalid - valid list types must match schema type.
  563.         'a10': SchemaInt(valid=NonSchema),                 # Invalid - should be list
  564.         'a11': SchemaInt(valid=[]),                         # Invalid - valid list can't be empty
  565.         'a12': SchemaInt(valid=["abc", ]),                  # Invalid - valid list types must match schema type.
  566.     })
  567.     validate_schema(invalid_schema, "invalid_schema")
  568.    
  569.     # Define and validate valid JSON data against valid schema.
  570.     valid_json_data = {
  571.         'a1': ["abc", 11, 12, "def"],
  572.         'a2': "Andrew Downing",
  573.         'a3': "Short",
  574.         'a4': 42,
  575.         'a5': {
  576.             'b1': 1,
  577.             'b2': 42,
  578.             'b3': -1,
  579.             'b4': ['abc', 'its', 'easy', 'as', 1, 2, 3],
  580.             'b5': [
  581.                 {'c1': 1, 'c2': 'abc'},
  582.                 {'c1': 2, 'c2': 'def'},
  583.                 {'c1': 3, 'c2': 'ghi'},
  584.             ],
  585.             'b6': [1, 2, 3],
  586.         },
  587.         # 'a6': 42,        # Optional and excluded.
  588.         'a7': "abc",
  589.         'a8': 1,
  590.         'a9': {"Whatever": "StrInWildcard"},
  591.     }
  592.     validate_json_data(valid_json_data, "valid_json_data", valid_schema, "valid_schema")    
  593.     valid_json_data['a6'] = 42        # Optional and included
  594.     validate_json_data(valid_json_data, "valid_json_data + optional", valid_schema, "valid_schema")    
  595.    
  596.     # Define and validate valid JSON data against valid schema.
  597.     invalid_json_data = {
  598.         'a1': ["abc", 11,            
  599.                ["InvalidList", ],      # Invalid List member of List
  600.                {"x": "Invalid Dict"},  # Invalid Dictionary member of List
  601.                "def"],
  602.         'a2': 2,                       # Invalid value  (too small)
  603.         'a3': "1234567890123",         # Invalid Length (too long)
  604.         'a4': "ABC",                   # Invalid integer
  605.         'a5': {
  606.             # 'b1': 1,                  # Invalid Missing value
  607.             'b2': 43,                  # Invalid value  (too big)
  608.             'b3': -1,
  609.             'b4': {'c1': 1},            # Invalid Dict instead of List
  610.             'b5': [
  611.                 [1, 'abc'],            # Invalid List instead of Dict
  612.                 {'c1': 2, 'c2': 'def'},
  613.                 {'c1': 3, 'c2': 'ghi'},
  614.             ],
  615.             'b6': [1, 2, 3, 4, 5, 6, 7, 8],  # Invalid list length (too long)
  616.         },
  617.         'a7': "def",                   # Invalid value not in list
  618.         'a8': 4,                       # Invalid value not in list
  619.         'a99': "Invalid data"          # Invalid extra member of Dict.
  620.     }
  621.     validate_json_data(invalid_json_data, "invalid_json_data", valid_schema, "valid_schema")
  622.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement