def testGetValueAtFieldIndex_invalidIndex_raisesException(self): """Test get_value_at_field_index with an invalid index.""" patient = patient_pb2.Patient(active=datatypes_pb2.Boolean(value=True)) with self.assertRaises(ValueError) as ve: proto_utils.get_value_at_field_index(patient, "active", 1) self.assertIsInstance(ve.exception, ValueError)
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)
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}`.')
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))
def testGetValueAtFieldIndex_withSingularField_returnsValue(self): """Test get_value_at_field_index with a singular field.""" patient = patient_pb2.Patient(active=datatypes_pb2.Boolean(value=True)) result = proto_utils.get_value_at_field_index(patient, "active", 0) self.assertTrue(result.value)
def testGetValueAtFieldIndex_withRepeatedField_returnsValueAtIndex(self): """Test get_value_at_field_index with a repeated field.""" patient = self._create_patient_with_names(["A", "B", "C"]) result = proto_utils.get_value_at_field_index(patient, "name", 1) self.assertEqual(result.text.value, "B")
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)