text: # -*- coding: utf-8 -*-
  # Copyright 2020 Red Hat
  # GNU General Public License v3.0+
  # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
  
  """Use AnsibleModule's argspec validation
  
  def _check_argspec(self):
      aav = AnsibleArgSpecValidator(
          data=self._task.args,
          schema=DOCUMENTATION,
          schema_format="doc",
          schema_conditionals={},
          other_args={},
          name=self._task.action,
      )
      valid, errors, updated_data = aav.validate()
      if not valid:
          raise AnsibleActionFail(errors)
  
  """
  from __future__ import absolute_import, division, print_function
  
  __metaclass__ = type
  
  import re
  from ansible.module_utils.basic import AnsibleModule
  from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
      dict_merge,
  )
  from ansible.module_utils.six import iteritems
  
  try:
      import yaml
  
      # use C version if possible for speedup
      try:
          from yaml import CSafeLoader as SafeLoader
      except ImportError:
          from yaml import SafeLoader
      HAS_YAML = True
  except ImportError:
      HAS_YAML = False
  
  # TODO: Update this to point to functionality being exposed in 2.11
  # ansible-base 2.11 should expose argspec validation outside of the
  # ansiblemodule class
  try:
      from ansible.module_utils.common.arg_spec import ArgumentSpecValidator
  
      HAS_ANSIBLE_ARG_SPEC_VALIDATOR = True
  except ImportError:
      HAS_ANSIBLE_ARG_SPEC_VALIDATOR = False
  
  
  OPTION_METADATA = (
      "type",
      "choices",
      "default",
      "required",
      "aliases",
      "elements",
      "fallback",
      "no_log",
      "apply_defaults",
      "deprecated_aliases",
      "removed_in_version",
  )
  OPTION_CONDITIONALS = (
      "mutually_exclusive",
      "required_one_of",
      "required_together",
      "required_by",
      "required_if",
  )
  
  VALID_ANSIBLEMODULE_ARGS = (
      "argument_spec",
      "bypass_checks",
      "no_log",
      "add_file_common_args",
      "supports_check_mode",
  ) + OPTION_CONDITIONALS
  
  BASE_ARG_AVAIL = 2.11
  
  
  class MonkeyModule(AnsibleModule):
      """A derivative of the AnsibleModule used
      to just validate the data (task.args) against
      the schema(argspec)
      """
  
      def __init__(self, data, schema, name):
          self._errors = None
          self._valid = True
          self._schema = schema
          self.name = name
          self.params = data
  
      def fail_json(self, msg):
          """Replace the AnsibleModule fail_json here
          :param msg: The message for the failure
          :type msg: str
          """
          if self.name:
              msg = re.sub(
                  r"\(basic\.pyc?\)", "'{name}'".format(name=self.name), msg
              )
          self._valid = False
          self._errors = msg
  
      def _load_params(self):
          """This replaces the AnsibleModule _load_params
          fn because we already set self.params in init
          """
          pass
  
      def validate(self):
          """Instantiate the super, validating the schema
          against the data
          :return valid: if the data passed
          :rtype valid: bool
          :return errors: errors reported during validation
          :rtype errors: str
          :return params: The original data updated with defaults
          :rtype params: dict
          """
          super(MonkeyModule, self).__init__(**self._schema)
          return self._valid, self._errors, self.params
  
  
  class AnsibleArgSpecValidator:
      def __init__(
          self,
          data,
          schema,
          schema_format="doc",
          schema_conditionals=None,
          name=None,
          other_args=None,
      ):
          """Validate some data against a schema
          :param data: The data to valdiate
          :type data: dict
          :param schema: A schema in ansible argspec format
          :type schema: dict
          :param schema_format: 'doc' (ansible docstring) or 'argspec' (ansible argspec)
          :type schema: str if doc, dict if argspec
          :param schema_conditionals: A dict of schema conditionals, ie required_if
          :type schema_conditionals: dict
          :param name: the name of the plugin calling this class, used in error messages
          :type name: str
          :param other_args: Other valid kv pairs for the argspec, eg no_log, bypass_checks
          :type other_args: dict
  
          note:
          - the schema conditionals can be root conditionals or deeply nested conditionals
            these get dict_merged into the argspec from the docstring, since the docstring cannot
            contain them.
          """
          self._errors = ""
          self._name = name
          self._other_args = other_args
          self._schema = schema
          self._schema_format = schema_format
          self._schema_conditionals = schema_conditionals
          self._data = data
  
      def _extract_schema_from_doc(self, doc_obj, temp_schema):
          """Extract the schema from a doc string
          :param doc_obj: The doc as a python obj
          :type doc_obj: dictionary
          :params temp_schema: The dict in which we stuff the schema parts
          :type temp_schema: dict
          """
          options_obj = doc_obj.get("options")
          for okey, ovalue in iteritems(options_obj):
              temp_schema[okey] = {}
              for metakey in list(ovalue):
                  if metakey == "suboptions":
                      temp_schema[okey].update({"options": {}})
                      suboptions_obj = {"options": ovalue["suboptions"]}
                      self._extract_schema_from_doc(
                          suboptions_obj, temp_schema[okey]["options"]
                      )
                  elif metakey in OPTION_METADATA + OPTION_CONDITIONALS:
                      temp_schema[okey].update({metakey: ovalue[metakey]})
  
      # TODO: Support extends_documentation_fragment
      def _convert_doc_to_schema(self):
          """Convert the doc string to an obj, was yaml
          add back other valid conditionals and params
          """
          doc_obj = yaml.load(self._schema, SafeLoader)
          temp_schema = {}
          self._extract_schema_from_doc(doc_obj, temp_schema)
          self._schema = {"argument_spec": temp_schema}
  
      def _validate(self):
          """Validate the data gainst the schema
          convert doc string in argspec if necessary
  
          :return valid: if the data passed
          :rtype valid: bool
          :return errors: errors reported during validation
          :rtype errors: str
          :return params: The original data updated with defaults
          :rtype params: dict
          """
          if self._schema_format == "doc":
              self._convert_doc_to_schema()
          if self._schema_conditionals is not None:
              self._schema = dict_merge(self._schema, self._schema_conditionals)
          if self._other_args is not None:
              self._schema = dict_merge(self._schema, self._other_args)
          invalid_keys = [
              k for k in self._schema.keys() if k not in VALID_ANSIBLEMODULE_ARGS
          ]
          if invalid_keys:
              valid = False
              errors = "Invalid schema. Invalid keys found: {ikeys}".format(
                  ikeys=",".join(invalid_keys)
              )
              updated_data = {}
          else:
              mm = MonkeyModule(
                  data=self._data, schema=self._schema, name=self._name
              )
              valid, errors, updated_data = mm.validate()
          return valid, errors, updated_data
  
      def validate(self):
          """The public validate method
          check for future argspec validation
          that is coming in 2.11, change the check according above
          """
          if HAS_ANSIBLE_ARG_SPEC_VALIDATOR:
              if self._schema_format == "doc":
                  self._convert_doc_to_schema()
              if self._schema_conditionals is not None:
                  self._schema = dict_merge(
                      self._schema, self._schema_conditionals
                  )
              invalid_keys = [
                  k
                  for k in self._schema.keys()
                  if k not in VALID_ANSIBLEMODULE_ARGS
              ]
              if invalid_keys:
                  valid = False
                  errors = [
                      "Invalid schema. Invalid keys found: {ikeys}".format(
                          ikeys=",".join(invalid_keys)
                      )
                  ]
                  updated_data = {}
                  return valid, errors, updated_data
              else:
                  validator = ArgumentSpecValidator(**self._schema)
                  result = validator.validate(self._data)
                  valid = not bool(result.error_messages)
                  return (
                      valid,
                      result.error_messages,
                      result.validated_parameters,
                  )
          else:
              return self._validate()
  
  
  def check_argspec(
      schema, name, schema_format="doc", schema_conditionals=None, **args
  ):
      if schema_conditionals is None:
          schema_conditionals = {}
  
      aav = AnsibleArgSpecValidator(
          data=args,
          schema=schema,
          schema_format=schema_format,
          schema_conditionals=schema_conditionals,
          name=name,
      )
      result = {}
      valid, errors, updated_params = aav.validate()
      if not valid:
          result["errors"] = errors
          result["failed"] = True
          result["msg"] = "argspec validation failed for {name} plugin".format(
              name=name
          )
  
      return valid, result, updated_params
  