def main(ylib: str = None, path: str = None, scope: ValidationScope = ValidationScope.all, ctype: ContentType = ContentType.config, set_id: bool = False, tree: bool = False, no_types: bool = False, digest: bool = False, validate: str = None) -> int: """Entry-point for a validation script. Args: ylib: Name of the file with YANG library path: Colon-separated list of directories to search for YANG modules. scope: Validation scope (syntax, semantics or all). ctype: Content type of the data instance (config, nonconfig or all) set_id: If `True`, print module set id. tree: If `True`, print schema tree. no_types: If `True`, don't print types in schema tree. digest: If `True`, print schema digest. validate: Name of file to validate against the schema. Returns: Numeric return code (0=no error, 2=YANG error, 1=other) """ if ylib is None: parser = argparse.ArgumentParser( prog="yangson", description="Validate JSON data against a YANG data model.") parser.add_argument( "-V", "--version", action="version", version= f"%(prog)s {pkg_resources.get_distribution('yangson').version}") parser.add_argument( "ylib", metavar="YLIB", help=("name of the file with description of the data model" " in JSON-encoded YANG library format [RFC 7895]")) parser.add_argument( "-p", "--path", help=("colon-separated list of directories to search" " for YANG modules")) grp = parser.add_mutually_exclusive_group() grp.add_argument("-i", "--id", action="store_true", help="print module set id") grp.add_argument("-t", "--tree", action="store_true", help="print schema tree as ASCII art") grp.add_argument("-d", "--digest", action="store_true", help="print schema digest in JSON format") grp.add_argument( "-v", "--validate", metavar="INST", help="name of the file with JSON-encoded instance data") parser.add_argument("-s", "--scope", choices=["syntax", "semantics", "all"], default="all", help="validation scope (default: %(default)s)") parser.add_argument( "-c", "--ctype", type=str, choices=["config", "nonconfig", "all"], default="config", help="content type of the data instance (default: %(default)s)") parser.add_argument("-n", "--no-types", action="store_true", help="suppress type info in tree output") args = parser.parse_args() ylib: str = args.ylib path: Optional[str] = args.path scope = ValidationScope[args.scope] ctype = ContentType[args.ctype] set_id: bool = args.id tree: bool = args.tree no_types = args.no_types digest: bool = args.digest validate: str = args.validate try: with open(ylib, encoding="utf-8") as infile: yl = infile.read() except (FileNotFoundError, PermissionError, json.decoder.JSONDecodeError) as e: print("YANG library:", str(e), file=sys.stderr) return 1 sp = path if path else os.environ.get("YANG_MODPATH", ".") try: dm = DataModel(yl, tuple(sp.split(":"))) except BadYangLibraryData as e: print("Invalid YANG library:", str(e), file=sys.stderr) return 2 except FeaturePrerequisiteError as e: print("Unsupported pre-requisite feature:", str(e), file=sys.stderr) return 2 except MultipleImplementedRevisions as e: print("Multiple implemented revisions:", str(e), file=sys.stderr) return 2 except ModuleNotFound as e: print("Module not found:", str(e), file=sys.stderr) return 2 except ModuleNotRegistered as e: print("Module not registered:", str(e), file=sys.stderr) return 2 if set_id: print(dm.module_set_id()) return 0 if tree: print(dm.ascii_tree(no_types)) return 0 if digest: print(dm.schema_digest()) return 0 if not validate: return 0 try: with open(validate, encoding="utf-8") as infile: itxt = json.load(infile) except (FileNotFoundError, PermissionError, json.decoder.JSONDecodeError) as e: print("Instance data:", str(e), file=sys.stderr) return 1 try: i = dm.from_raw(itxt) except RawMemberError as e: print("Illegal object member:", str(e), file=sys.stderr) return 3 except RawTypeError as e: print("Invalid type:", str(e), file=sys.stderr) return 3 try: i.validate(scope, ctype) except SchemaError as e: print("Schema error:", str(e), file=sys.stderr) return 3 except SemanticError as e: print("Semantic error:", str(e), file=sys.stderr) return 3 return 0
def validate_yangson(instance_data, yang_mod_dir, yang_mod_lib=None, validation_scope="all", content_type="all", to_xml=False, metadata=True): """ Validate instance_data for compliance with YANG modules at yang_mod_dir directory. Args: instance_data (dictionary or list): parsing results to validate yang_mod_dir (str): OS path to directory with YANG modules yang_mod_lib (str): optional, OS path to file with JSON-encoded YANG library data [RFC7895] content_type (str): optional, content type as per https://yangson.labs.nic.cz/enumerations.html supported - all, config, nonconfig validation_scope (str): optional, validation scope as per https://yangson.labs.nic.cz/enumerations.html supported - all, semantics, syntax to_xml (bool): default is False, converts results to XML if True metadata (bool): default is True, return data with validation results Returns: Dictionary of if metadata is True:: { "result": instance_data or to_xml results, "exception": {}, "valid": {}, "comment": "" } If metadata is False returns results as is on successful validation or False otherwise If metadata is False but to_xml is True, return parsing results converted to XML string """ if to_xml: from xml.etree import cElementTree as ET ret = { "result": [] if to_xml else instance_data, "exception": {}, "valid": {}, "comment": "", } if not HAS_LIBS: ret["comment"] = "Failed to import yangson library, make sure it is installed." ret["exception"] = {0: "ImportError"} ret["valid"] = {0: False} output_tag_load = _ttp_["output_object"].tag_load # load yang_modules_library and instantiate DataModel object try: if output_tag_load and isinstance(output_tag_load, str): yang_modules_library = output_tag_load elif yang_mod_lib: with open(yang_mod_lib, "r") as f: yang_modules_library = f.read() else: yang_modules_library = _make_library(yang_mod_dir) dm = DataModel(yltxt=yang_modules_library, mod_path=[yang_mod_dir]) except: ret["exception"] = traceback.format_exc() ret["success"] = False ret["comment"] = "Failed to instantiate DataModel, check YANG library and path to YANG modules." if not metadata: return False else: return ret # decide on scopes and content if validation_scope == "all": scope = enumerations.ValidationScope.all elif validation_scope == "semantics": scope = enumerations.ValidationScope.semantics elif validation_scope == "syntax": scope = enumerations.ValidationScope.syntax if content_type == "all": ctype = enumerations.ContentType.all elif content_type == "config": ctype = enumerations.ContentType.config elif content_type == "nonconfig": ctype = enumerations.ContentType.nonconfig # run validation of data if isinstance(instance_data, list): for index, item in enumerate(instance_data): try: inst = dm.from_raw(item) inst.validate(scope=scope, ctype=ctype) ret["valid"][index] = True if to_xml: ret["result"].append( ET.tostring(inst.to_xml(), encoding="unicode")) except: if not metadata: return False ret["exception"][index] = traceback.format_exc() ret["valid"][index] = False elif isinstance(instance_data, dict): try: inst = dm.from_raw(instance_data) inst.validate(scope=scope, ctype=ctype) ret["valid"] = True if to_xml: ret["result"] = ET.tostring(inst.to_xml(), encoding="unicode") except: if not metadata: return False ret["exception"] = traceback.format_exc() ret["valid"] = False # return results if not metadata: return ret["result"] else: return ret