예제 #1
0
def _validate_field(msg: message.Message, field: descriptor.FieldDescriptor,
                    field_name: str,
                    primitive_handler_: primitive_handler.PrimitiveHandler):
    """Validates that required fields are set, and performs basic temporal checks.

  Args:
    msg: The Message that the field belongs to.
    field: The FieldDescriptor of the field to examine.
    field_name: The name of the field.
    primitive_handler_: Responsible for returning PrimitiveWrappers.

  Raises:
    fhir_errors.InvalidFhirError: In the event that a required field is not set
    or if temporal requirements are not met.
  """
    if annotation_utils.field_is_required(
            field) and not proto_utils.field_is_set(msg, field):
        raise fhir_errors.InvalidFhirError(
            f'Required field `{field.full_name}` is missing.')

    if annotation_utils.is_reference(field.message_type):
        _validate_reference_field(msg, field)
        return

    if field.type == descriptor.FieldDescriptor.TYPE_MESSAGE:
        # Returns the first value only for a singular field
        for i in range(proto_utils.field_content_length(msg, field)):
            submessage = proto_utils.get_value_at_field_index(msg, field, i)
            _validate_fhir_constraints(submessage, field_name,
                                       primitive_handler_)

            # Run extra validation for some types, until FHIRPath validation covers
            # these as well
            if fhir_types.is_period(submessage):
                _validate_period(submessage, field_name)
예제 #2
0
  def testAppendValueAtField_repeatedCompositeValue_appendsValue(self):
    """Test append_value_at_field with a repeated composite type."""
    patient = patient_pb2.Patient()

    patient_names = [
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="Foo")),
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="Bar")),
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="Bats")),
    ]
    self.assertEqual(proto_utils.field_content_length(patient, "name"), 0)

    for name in patient_names:
      proto_utils.append_value_at_field(patient, "name", name)

    self.assertEqual(proto_utils.field_content_length(patient, "name"), 3)
    self.assertEqual(patient.name[:], patient_names)
예제 #3
0
def _validate_reference_field(parent: message.Message,
                              field: descriptor.FieldDescriptor):
    """Ensure that the provided reference field is valid.

  Args:
    parent: The containing Message.
    field: The reference field descriptor.

  Raises:
    fhir_errors.InvalidFhirError: In the event of an empty reference (no
    extensions, no identifier, no display).
  """
    oneof = field.message_type.oneofs[0]

    # Singular fields have a length of 1
    for i in range(proto_utils.field_content_length(parent, field)):
        reference = proto_utils.get_value_at_field_index(parent, field, i)
        reference_field_name = reference.WhichOneof(oneof.name)

        if reference_field_name is None:
            if not (reference.extension or reference.HasField('identifier')
                    or reference.HasField('display')):
                raise fhir_errors.InvalidFhirError(
                    f'`{reference.DESCRIPTOR.name}` is an empty reference.')
            # There's no reference field, but there is other data. This is valid.
            return

        field_options = field.GetOptions()
        if not field_options.Extensions[annotations_pb2.valid_reference_type]:
            # The reference field does not have restrictions, so any value is fine.
            return

        if reference.HasField('uri') or reference.HasField('fragment'):
            # Uri and Fragment references are untyped.
            return

        # There are no reference annotations for DSTU2; skip validation
        if annotation_utils.get_fhir_version(
                reference) == annotations_pb2.FhirVersion.DSTU2:
            return

        reference_field = reference.DESCRIPTOR.fields_by_name[
            reference_field_name]
        if annotation_utils.is_typed_reference_field(reference_field):
            # Ensure that the reference type is listed as "valid"
            reference_type = reference_field.GetOptions().Extensions[
                annotations_pb2.referenced_fhir_type]
            is_allowed = False
            for valid_type in field_options.Extensions[
                    annotations_pb2.valid_reference_type]:
                if valid_type == reference_type or valid_type == 'Resource':
                    is_allowed = True
                    break

            if not is_allowed:
                raise fhir_errors.InvalidFhirError(
                    f'Message `{parent.DESCRIPTOR.full_name}` contains an invalid '
                    f'reference type: `{reference_type}` set at: '
                    f'`{reference_field_name}`.')
예제 #4
0
def _add_fields_to_extension(msg: message.Message, extension: message.Message):
    """Adds the fields from message to extension."""
    for field in msg.DESCRIPTOR.fields:
        _verify_field_is_proto_message_type(field)

        # Add submessages to nested extensions; singular fields have a length of 1
        for i in range(proto_utils.field_content_length(msg, field)):
            child_extension = proto_utils.set_in_parent_or_add(
                extension, 'extension')
            cast(Any,
                 child_extension).url.value = get_inlined_extension_url(field)
            value = proto_utils.get_value_at_field_index(msg, field, i)
            _add_value_to_extension(
                value, child_extension,
                annotation_utils.is_choice_type_field(field))
예제 #5
0
 def testFieldContentLength_withNonExistentField_returnsZero(self):
   """Test field_content_length functionality on non-existent field input."""
   default_patient = patient_pb2.Patient()  # Leave active unset
   self.assertEqual(
       proto_utils.field_content_length(default_patient, "active"), 0)
예제 #6
0
 def testFieldContentLength_withSingularField_returnsSingleCount(self):
   """Test field_content_length functionality on singular field input."""
   patient = patient_pb2.Patient(active=datatypes_pb2.Boolean(value=True))
   self.assertEqual(proto_utils.field_content_length(patient, "active"), 1)
예제 #7
0
 def testFieldContentLength_withRepeatedField_returnsContentLength(self):
   """Test field_content_length functionality on repeated field input."""
   patient = self._create_patient_with_names(["A", "B", "C"])
   self.assertEqual(proto_utils.field_content_length(patient, "name"), 3)
예제 #8
0
    def _merge_field(self, json_value: Any, field: descriptor.FieldDescriptor,
                     parent: message.Message):
        """Merges the json_value into the provided field of the parent Message.

    Args:
      json_value: The JSON value to set.
      field: The FieldDescriptor of the field to set in parent.
      parent: The parent Message to set the value on.

    Raises:
      ValueError: In the event that a non-primitive field has already been set.
      ValueError: In the event that a oneof field has already been set.
    """
        # If the field is non-primitive, ensure that it hasn't been set yet. Note
        # that we allow primitive types to be set already, because FHIR represents
        # extensions to primitives as separate, subsequent JSON elements, with the
        # field prepended by an underscore
        if (not annotation_utils.is_primitive_type(field.message_type)
                and proto_utils.field_is_set(parent, field)):
            raise ValueError('Target field {} is already set.'.format(
                field.full_name))

        # Consider it an error if a oneof field is already set.
        #
        # Exception: When a primitive in a choice type has a value and an extension,
        # it will get set twice, once by the value (e.g. valueString) and once by an
        # extension (e.g., _valueString)
        if field.containing_oneof is not None:
            oneof_field = parent.DESCRIPTOR.oneofs_by_name[
                field.containing_oneof.name]
            if (annotation_utils.is_primitive_type(field.message_type)
                    and oneof_field.full_name == field.full_name):
                raise ValueError(
                    'Cannot set field {} since oneof field {} is already set.'.
                    format(field.full_name, oneof_field.full_name))

        # Ensure that repeated fields get proper list assignment
        existing_field_size = proto_utils.field_content_length(parent, field)
        if proto_utils.field_is_repeated(field):
            if not isinstance(json_value, list):
                raise ValueError(
                    f'Attempted to merge a repeated field, {field.name}, a json_value '
                    f'with type {type(json_value)} instead of a list.')

            if existing_field_size != 0 and existing_field_size != len(
                    json_value):
                raise ValueError(
                    'Repeated primitive list length does not match extension list for '
                    'field: {field.full_name!r}.')

        # Set the JSON values, taking care to clear the PRIMITIVE_HAS_NO_VALUE_URL
        # if we've already visited the field before.
        json_value = (json_value if proto_utils.field_is_repeated(field) else
                      [json_value])
        for (i, value) in enumerate(json_value):
            parsed_value = self._parse_field_value(field, value)
            if existing_field_size > 0:
                field_value = proto_utils.get_value_at_field_index(
                    parent, field, i)
                field_value.MergeFrom(parsed_value)
                extensions.clear_fhir_extensions_with_url(
                    field_value, extensions.PRIMITIVE_HAS_NO_VALUE_URL)
            else:
                field_value = proto_utils.set_in_parent_or_add(parent, field)
                field_value.MergeFrom(parsed_value)
예제 #9
0
def add_extension_to_message(extension: message.Message, msg: message.Message):
    """Recursively parses extension and adds to message.

  Args:
    extension: The FHIR extension to serialize and add.
    msg: The message to add the extension onto

  Raises:
    InvalidFhirError: In the event that a value is set on the extension, but the
    corresponding message field to copy it to is repeated (extension values are
    singular only).
  """
    desc = msg.DESCRIPTOR
    fields_by_url = {
        get_inlined_extension_url(field): field
        for field in desc.fields if field.name != 'id'
    }

    # Copy the id field if present
    id_field = desc.fields_by_name.get('id')
    if proto_utils.field_is_set(extension, id_field):
        proto_utils.set_value_at_field(msg, id_field, cast(Any, extension).id)

    # Handle simple extensions (only one value present)
    if proto_utils.field_is_set(extension, 'value'):
        if len(fields_by_url) != 1:
            raise fhir_errors.InvalidFhirError(
                f'Expected a single field, found {len(fields_by_url)}; '
                f'{desc.full_name} is an invalid extension type.')

        field = list(fields_by_url.items())[0][1]
        if proto_utils.field_is_repeated(field):
            raise fhir_errors.InvalidFhirError(
                f'Expected {field.full_name} to be a singular field. '
                f'{desc.full_name} is an invalid extension type.')
        _add_extension_value_to_message(extension, msg, field)
        return

    # Else, iterate through all child extensions...
    child_extensions = proto_utils.get_value_at_field(extension, 'extension')
    for child_extension in child_extensions:
        field = fields_by_url.get(child_extension.url.value)
        if field is None:
            raise ValueError(f'Message of type: {desc.full_name} has no field '
                             f'with name: {child_extension.url.value}.')

        # Simple value type on child_extension...
        if proto_utils.field_is_set(child_extension, 'value'):
            _add_extension_value_to_message(child_extension, msg, field)
            continue

        # Recurse for nested composite messages...
        if not proto_utils.field_is_repeated(field):
            if proto_utils.field_is_set(msg, field):
                raise ValueError(
                    f'Field: {field.full_name} is already set on message: '
                    f'{desc.full_name}.')

            if proto_utils.field_content_length(child_extension,
                                                'extension') > 1:
                raise ValueError(
                    f'Cardinality mismatch between field: {field.full_name} and '
                    f'extension: {desc.full_name}.')

        child_message = proto_utils.set_in_parent_or_add(msg, field)
        add_extension_to_message(child_extension, child_message)
예제 #10
0
def _add_extension_value_to_message(extension: message.Message,
                                    msg: message.Message,
                                    message_field: descriptor.FieldDescriptor):
    """Serialize the provided extension and add it to the message.

  Args:
    extension: The FHIR extension to serialize.
    msg: The message to add the serialized extension to.
    message_field: The field on the message to set.

  Raises:
    InvalidFhirError: In the event that the field to be set is not a singular
    message type, or if the provided extension is not singular (has nested
    extensions).
  """
    if message_field.type != descriptor.FieldDescriptor.TYPE_MESSAGE:
        raise fhir_errors.InvalidFhirError(
            f'{msg.DESCRIPTOR.full_name} is not a FHIR extension type.')

    extension_field = extension.DESCRIPTOR.fields_by_name['extension']
    if proto_utils.field_content_length(extension, extension_field) > 0:
        raise fhir_errors.InvalidFhirError(
            'No child extensions should be set on '
            f'{extension.DESCRIPTOR.full_name}.')

    value_field = _get_populated_extension_value_field(extension)

    # If a choice type, need to assign the extension value to the correct field.
    if annotation_utils.is_choice_type_field(message_field):
        choice_message = proto_utils.get_value_at_field(msg, message_field)
        choice_descriptor = choice_message.DESCRIPTOR

        for choice_field in choice_descriptor.fields:
            if (value_field.message_type.full_name ==
                    choice_field.message_type.full_name):
                _add_extension_value_to_message(extension, choice_message,
                                                choice_field)
                return

        raise ValueError(
            f'No field on Choice Type {choice_descriptor.full_name} '
            f'for extension {extension.DESCRIPTOR.full_name}.')

    # If the target message is a bound Code type, we need to convert the generic
    # Code field from the extension into the target typed Code.
    if annotation_utils.has_fhir_valueset_url(message_field.message_type):
        typed_code = proto_utils.set_in_parent_or_add(msg, message_field)
        codes.copy_code(cast(Any, extension).value.code, typed_code)
        return

    # If the target message is bound to a Coding type, we must convert the generic
    # Coding field from the extension into the target typed Coding.
    if fhir_types.is_type_or_profile_of_coding(message_field.message_type):
        typed_coding = proto_utils.set_in_parent_or_add(msg, message_field)
        codes.copy_coding(cast(Any, extension).value.coding, typed_coding)
        return

    # Value types must match
    if not proto_utils.are_same_message_type(value_field.message_type,
                                             message_field.message_type):
        raise ValueError(
            'Missing expected value of type '
            f'{message_field.message_type.full_name} in extension '
            f'{extension.DESCRIPTOR.full_name}.')

    value = proto_utils.get_value_at_field(
        cast(Any, extension).value, value_field)
    if proto_utils.field_is_repeated(message_field):
        proto_utils.append_value_at_field(msg, message_field, value)
    else:
        proto_utils.set_value_at_field(msg, message_field, value)