<commit_before># -*- coding: utf-8 -*-

# This work was created by participants in the DataONE project, and is
# jointly copyrighted by participating institutions in DataONE. For
# more information on DataONE, see our web site at http://dataone.org.
#
#   Copyright 2009-2017 DataONE
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This work was created by participants in the DataONE project, and is
# jointly copyrighted by participating institutions in DataONE. For
# more information on DataONE, see our web site at http://dataone.org.
#
#   Copyright 2010, 2011
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Native objects for holding DataONE Exceptions

- Wrap the PyXB client with Exception based classes
- PyXB based XML serialization and deserialization
- Add deserialize to string and HTTP headers

traceInformation:

traceInformation is an xs:anyType, meaning that is essentially the root of a new
XML document of arbitrary complexity. Since the contents of the elements are
unknown at the time when the PyXB bindings are created, PyXB cannot
automatically serialize and deserialize the traceInformation field together with
the rest of the DataONEException XML type.

To make it easier to use the traceInformation element, we support a special case
where it can be read and written as a single string of bytes, where the contents
are application specific. Any other content must be generated and parsed as XML by
the user.

Example of serialized DataONE Exception:

<error detailCode="1020" errorCode="404" name="NotFound" identifier="testpid">
<description>Attempted to perform operation on non-existing object</description>
<traceInformation>view_handler.py(128)
views.py(102)
auth.py(392)
auth.py(315)
</traceInformation>
</error>
"""

import io
import logging
import traceback

import d1_common.type_conversions
import d1_common.util
import d1_common.xml
from d1_common.types import dataoneErrors


def xml_is_dataone_exception(xml_str):
  try:
    return pyxb_is_dataone_exception(deserialize(xml_str))
  except ServiceFailure:
    return False


def pyxb_is_dataone_exception(obj_pyxb):
  return isinstance(obj_pyxb, DataONEException)


def deserialize(dataone_exception_xml):
  """Deserialize a DataONE Exception XML doc.
  """
  # logging.debug('dataone_exception_xml="{}"'
  # .format(d1_common.xml.pretty_xml(dataone_exception_xml)))
  try:
    dataone_exception_pyxb = d1_common.xml.deserialize_d1_exc(
      dataone_exception_xml
    )
  except ValueError as e:
    raise ServiceFailure(
      detailCode=0,
      description='Deserialization failed. error="{}" doc="{}"'.format(
        str(e),
        '<empty response>'
        if not dataone_exception_xml else dataone_exception_xml,
      ),
      traceInformation=traceback.format_exc(),
    )
  else:
    x = create_exception_by_name(
      dataone_exception_pyxb.name,
      dataone_exception_pyxb.detailCode,
      dataone_exception_pyxb.description,
      _get_trace_information_content(dataone_exception_pyxb),
      dataone_exception_pyxb.identifier,
      dataone_exception_pyxb.nodeId,
    )
    return x


def deserialize_from_headers(headers):
  """Deserialize a DataONE Exception that is stored in a map of HTTP headers
  (used in responses to HTTP HEAD requests).
  """
  return create_exception_by_name(
    _get_header(headers, 'DataONE-Exception-Name'),
    _get_header(headers, 'DataONE-Exception-DetailCode'),
    _get_header(headers, 'DataONE-Exception-Description'),
    _get_header(headers, 'DataONE-Exception-TraceInformation'),
    _get_header(headers, 'DataONE-Exception-Identifier'),
    _get_header(headers, 'DataONE-Exception-NodeId')
  )


def _get_header(headers, header):
  lower_case_headers = dict(
    list(
      zip(list(map(str.lower, list(headers.keys()))), list(headers.values()))
    )
  )
  try:
    header = lower_case_headers[header.lower()]
  except LookupError:
    return None
  # As a header must be on a single line, the Python stack uses a
  # convention of replacing newlines with " / ".
  return header.replace(' / ', '\n')


def create_exception_by_name(
    name, detailCode='0', description='', traceInformation=None,
    identifier=None, nodeId=None
):
  try:
    dataone_exception = globals()[name]
  except LookupError:
    # The defined types are not enumerated in the schema so it is possible to
    # have a document that validates against the schema but is not a valid
    # DataONE exception.
    dataone_exception = ServiceFailure
  return dataone_exception(
    detailCode, description, traceInformation, identifier, nodeId
  )


def create_exception_by_error_code(
    errorCode, detailCode='0', description='', traceInformation=None,
    identifier=None, nodeId=None
):
  try:
    dataone_exception = ERROR_CODE_TO_EXCEPTION_DICT[errorCode]
  except LookupError:
    # The defined types are not enumerated in the schema so it is possible to
    # have a document that validates against the schema but is not a valid
    # DataONE exception. In this case, we create a ServiceFailure.
    dataone_exception = ServiceFailure
  return dataone_exception(
    detailCode, description, traceInformation, identifier, nodeId
  )


# def _prep_trace(trace_information):
#   return xml.sax.saxutils.escape(trace_information)


def _get_trace_information_content(err_pyxb):
  assert d1_common.xml.is_pyxb(err_pyxb)
  if err_pyxb.traceInformation is None:
    return None
  try:
    return '\n'.join(err_pyxb.traceInformation.content())
  except TypeError:
    return d1_common.xml.serialize_to_str(
      err_pyxb.traceInformation, strip_prolog=True
    )


# def _deserialize_trace_information(self, trace_information):
#   """To PyXB TraceInformation
#   <pyxb.binding.datatypes.anyType object at 0x7fe77a9c19b0>"""
#   if trace_information is None:
#     return None
#   if isinstance(trace_information, str):
#     import xml.dom.minidom
#     return xml.dom.minidom.parseString(
#       "<traceInformation><value>" + xml.sax.saxutils.escape(<commit_msg>Improve formatting when DataONEException is serialized for display<commit_after># -*- coding: utf-8 -*-

# This work was created by participants in the DataONE project, and is
# jointly copyrighted by participating institutions in DataONE. For
# more information on DataONE, see our web site at http://dataone.org.
#
#   Copyright 2009-2017 DataONE
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This work was created by participants in the DataONE project, and is
# jointly copyrighted by participating institutions in DataONE. For
# more information on DataONE, see our web site at http://dataone.org.
#
#   Copyright 2010, 2011
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Native objects for holding DataONE Exceptions

- Wrap the PyXB client with Exception based classes
- PyXB based XML serialization and deserialization
- Add deserialize to string and HTTP headers

traceInformation:

traceInformation is an xs:anyType, meaning that is essentially the root of a new
XML document of arbitrary complexity. Since the contents of the elements are
unknown at the time when the PyXB bindings are created, PyXB cannot
automatically serialize and deserialize the traceInformation field together with
the rest of the DataONEException XML type.

To make it easier to use the traceInformation element, we support a special case
where it can be read and written as a single string of bytes, where the contents
are application specific. Any other content must be generated and parsed as XML by
the user.

Example of serialized DataONE Exception:

<error detailCode="1020" errorCode="404" name="NotFound" identifier="testpid">
<description>Attempted to perform operation on non-existing object</description>
<traceInformation>view_handler.py(128)
views.py(102)
auth.py(392)
auth.py(315)
</traceInformation>
</error>
"""

import io
import logging
import traceback

import d1_common.type_conversions
import d1_common.util
import d1_common.xml
from d1_common.types import dataoneErrors


def xml_is_dataone_exception(xml_str):
  try:
    return pyxb_is_dataone_exception(deserialize(xml_str))
  except ServiceFailure:
    return False


def pyxb_is_dataone_exception(obj_pyxb):
  return isinstance(obj_pyxb, DataONEException)


def deserialize(dataone_exception_xml):
  """Deserialize a DataONE Exception XML doc.
  """
  # logging.debug('dataone_exception_xml="{}"'
  # .format(d1_common.xml.pretty_xml(dataone_exception_xml)))
  try:
    dataone_exception_pyxb = d1_common.xml.deserialize_d1_exc(
      dataone_exception_xml
    )
  except ValueError as e:
    raise ServiceFailure(
      detailCode=0,
      description='Deserialization failed. error="{}" doc="{}"'.format(
        str(e),
        '<empty response>'
        if not dataone_exception_xml else dataone_exception_xml,
      ),
      traceInformation=traceback.format_exc(),
    )
  else:
    x = create_exception_by_name(
      dataone_exception_pyxb.name,
      dataone_exception_pyxb.detailCode,
      dataone_exception_pyxb.description,
      _get_trace_information_content(dataone_exception_pyxb),
      dataone_exception_pyxb.identifier,
      dataone_exception_pyxb.nodeId,
    )
    return x


def deserialize_from_headers(headers):
  """Deserialize a DataONE Exception that is stored in a map of HTTP headers
  (used in responses to HTTP HEAD requests).
  """
  return create_exception_by_name(
    _get_header(headers, 'DataONE-Exception-Name'),
    _get_header(headers, 'DataONE-Exception-DetailCode'),
    _get_header(headers, 'DataONE-Exception-Description'),
    _get_header(headers, 'DataONE-Exception-TraceInformation'),
    _get_header(headers, 'DataONE-Exception-Identifier'),
    _get_header(headers, 'DataONE-Exception-NodeId')
  )


def _get_header(headers, header):
  lower_case_headers = dict(
    list(
      zip(list(map(str.lower, list(headers.keys()))), list(headers.values()))
    )
  )
  try:
    header = lower_case_headers[header.lower()]
  except LookupError:
    return None
  # As a header must be on a single line, the Python stack uses a
  # convention of replacing newlines with " / ".
  return header.replace(' / ', '\n')


def create_exception_by_name(
    name, detailCode='0', description='', traceInformation=None,
    identifier=None, nodeId=None
):
  try:
    dataone_exception = globals()[name]
  except LookupError:
    # The defined types are not enumerated in the schema so it is possible to
    # have a document that validates against the schema but is not a valid
    # DataONE exception.
    dataone_exception = ServiceFailure
  return dataone_exception(
    detailCode, description, traceInformation, identifier, nodeId
  )


def create_exception_by_error_code(
    errorCode, detailCode='0', description='', traceInformation=None,
    identifier=None, nodeId=None
):
  try:
    dataone_exception = ERROR_CODE_TO_EXCEPTION_DICT[errorCode]
  except LookupError:
    # The defined types are not enumerated in the schema so it is possible to
    # have a document that validates against the schema but is not a valid
    # DataONE exception. In this case, we create a ServiceFailure.
    dataone_exception = ServiceFailure
  return dataone_exception(
    detailCode, description, traceInformation, identifier, nodeId
  )


# def _prep_trace(trace_information):
#   return xml.sax.saxutils.escape(trace_information)


def _get_trace_information_content(err_pyxb):
  assert d1_common.xml.is_pyxb(err_pyxb)
  if err_pyxb.traceInformation is None:
    return None
  try:
    return '\n'.join(err_pyxb.traceInformation.content())
  except TypeError:
    return d1_common.xml.serialize_to_xml_str(
      err_pyxb.traceInformation, strip_prolog=True
    )


# def _deserialize_trace_information(self, trace_information):
#   """To PyXB TraceInformation
#   <pyxb.binding.datatypes.anyType object at 0x7fe77a9c19b0>"""
#   if trace_information is None:
#     return None
#   if isinstance(trace_information, str):
#     import xml.dom.minidom
#     return xml.dom.minidom.parseString(
#       "<traceInformation><value>" + xml.sax.saxutils.escape(
{'fraction_non_alphanumeric': 0.07794798717491984, 'fraction_numerical': 0.009120057000356252, 'mean_word_length': 4.448757763975156, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 39}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4322779', 'n_tokens_mistral': 4081, 'n_tokens_neox': 3977, 'n_words': 1370}