def _print_choice_field(self, field_name: str, field: descriptor.FieldDescriptor, choice_container: message.Message) -> None: """Prints a FHIR choice field. This field is expected to have one valid oneof set. Args: field_name: The name of the field. field: The FieldDescriptor whose contents to print. choice_container: The value present at field, which should be a oneof with a single value set. """ if len(choice_container.DESCRIPTOR.oneofs) != 1: raise ValueError(f'Invalid value for choice field {field_name}: ' f'{choice_container}.') oneof_group = choice_container.DESCRIPTOR.oneofs[0] set_oneof_name = choice_container.WhichOneof(oneof_group.name) if set_oneof_name is None: raise ValueError('Oneof not set on choice type: ' f'{choice_container.DESCRIPTOR.full_name}.') value_field = choice_container.DESCRIPTOR.fields_by_name[ set_oneof_name] oneof_field_name = value_field.json_name oneof_field_name = oneof_field_name[0].upper() + oneof_field_name[1:] value = proto_utils.get_value_at_field(choice_container, value_field) if annotation_utils.is_primitive_type(value_field.message_type): self._print_primitive_field(field_name + oneof_field_name, value_field, value) else: self._print_message_field(field_name + oneof_field_name, value_field, value)
def reference_to_string(reference: message.Message) -> str: """Returns a reference URI for a typed reference message.""" _validate_reference(reference) # Early-exit if URI or fragment is set if proto_utils.field_is_set(reference, 'uri'): uri = proto_utils.get_value_at_field(reference, 'uri') return uri.value elif proto_utils.field_is_set(reference, 'fragment'): fragment = proto_utils.get_value_at_field(reference, 'fragment') return f'#{fragment.value}' set_oneof = reference.WhichOneof('reference') if set_oneof is None: raise ValueError( f'Reference is not set on: {reference.DESCRIPTOR.name}.') # Convert to CamelCase prefix = path_utils.snake_case_to_camel_case(set_oneof, upper=True) # Cull trailing 'Id' if prefix.endswith('Id'): prefix = prefix[:-2] reference_id = proto_utils.get_value_at_field(reference, set_oneof) reference_string = f'{prefix}/{reference_id.value}' if proto_utils.field_is_set(reference_id, 'history'): reference_string += f'/_history/{reference_id.history.value}' return reference_string
def _add_value_to_extension(msg: message.Message, extension: message.Message, is_choice_type: bool): """Adds the fields from msg to a generic Extension. Attempts are first made to set the "value" field of the generic Extension based on the type of field set on message. If this fails, checks are made against the generic Code and Coding types, and finally we fall back to adding the message's fields as sub-extensions. Args: msg: The message whose values to add to extension. extension: The generic Extension to populate. is_choice_type: Whether or not the provided message represents a "choice" type. """ if is_choice_type: oneofs = msg.DESCRIPTOR.oneofs if not oneofs: raise fhir_errors.InvalidFhirError( f'Choice type is missing a oneof: {msg.DESCRIPTOR.full_name}') value_field_name = msg.WhichOneof(oneofs[0].name) if value_field_name is None: raise ValueError('Choice type has no value set: ' f'{msg.DESCRIPTOR.full_name}') value_field = msg.DESCRIPTOR.fields_by_name[value_field_name] _verify_field_is_proto_message_type(value_field) _add_value_to_extension( proto_utils.get_value_at_field(msg, value_field), extension, False) else: # Try to set the message directly as a datatype value on the extension. # E.g., put the message of type Boolean into the value.boolean field value_field_mapping = _get_value_field_mapping_for_extension(extension) value_field = value_field_mapping.get(msg.DESCRIPTOR.full_name) if value_field is not None: proto_utils.set_value_at_field( cast(Any, extension).value, value_field, msg) elif annotation_utils.has_fhir_valueset_url(msg): codes.copy_code(msg, cast(Any, extension).value.code) elif fhir_types.is_type_or_profile_of_coding(msg): codes.copy_coding(msg, cast(Any, extension).value.coding) else: # Fall back to adding individual fields as sub-extensions _add_fields_to_extension(msg, extension)
def _print_reference(self, reference: message.Message): """Standardizes and prints the provided reference. Note that "standardization" in this case refers to un-typing the typed- reference prior to printing. Args: reference: The reference to print. """ set_oneof = reference.WhichOneof('reference') if set_oneof is None or set_oneof == 'uri': # Reference is already standard self._print_message(reference) else: new_reference = copy.copy(reference) # Setting the new URI field will overwrite the oneof new_uri = proto_utils.get_value_at_field(new_reference, 'uri') proto_utils.set_value_at_field( new_uri, 'value', references.reference_to_string(reference)) self._print_message(new_reference)
def _validate_fhir_constraints( msg: message.Message, base_name: str, primitive_handler_: primitive_handler.PrimitiveHandler): """Iterates over fields of the provided message and validates constraints. Args: msg: The message to validate. base_name: The root message name for recursive validation of nested message fields. primitive_handler_: Responsible for returning PrimitiveWrappers. Raises: fhir_errors.InvalidFhirError: In the event that a field is found to be violating FHIR constraints or a required oneof is not set. """ if annotation_utils.is_primitive_type(msg): # Validation is implicitly done on the primitive type during wrapping _ = primitive_handler_.primitive_wrapper_from_primitive(msg) return if proto_utils.is_message_type(msg, any_pb2.Any): # We do not validate "Any" constrained resources. # TODO: Potentially unpack the correct type and validate? return # Enumerate and validate fields of the message for field in msg.DESCRIPTOR.fields: field_name = f'{base_name}.{field.json_name}' _validate_field(msg, field, field_name, primitive_handler_) # Also verify that oneof fields are set. Note that optional choice-types # should have the containing message unset - if the containing message is set, # it should have a value set as well for oneof in msg.DESCRIPTOR.oneofs: if (msg.WhichOneof(oneof.name) is None and not oneof.GetOptions().HasExtension( annotations_pb2.fhir_oneof_is_optional)): raise fhir_errors.InvalidFhirError( f'Empty oneof: `{oneof.full_name}`.')
def get_contained_resource( contained_resource: message.Message) -> message.Message: """Returns the resource instance contained within `contained_resource`. Args: contained_resource: The containing `ContainedResource` instance. Returns: The resource contained by `contained_resource`. Raises: TypeError: In the event that `contained_resource` is not of type `ContainedResource`. ValueError: In the event that the oneof on `contained_resource` is not set. """ # TODO: Use an annotation here. if contained_resource.DESCRIPTOR.name != 'ContainedResource': raise TypeError('Expected `ContainedResource` but got: ' f'{type(contained_resource)}.') oneof_field = contained_resource.WhichOneof('oneof_resource') if oneof_field is None: raise ValueError('`ContainedResource` oneof not set.') return proto_utils.get_value_at_field(contained_resource, oneof_field)
def _print_reference(self, reference: message.Message) -> None: """Standardizes and prints the provided reference. Note that "standardization" in the case of PURE FHIR JSON refers to un-typing the typed-reference prior to printing. Args: reference: The reference to print. """ set_oneof = reference.WhichOneof('reference') if (self.json_format == _FhirJsonFormat.PURE and set_oneof is not None and set_oneof != 'uri'): # In pure FHIR mode, we have to serialize structured references # into FHIR uri strings. standardized_reference = copy.copy(reference) # Setting the new URI field will overwrite the original oneof new_uri = proto_utils.get_value_at_field(standardized_reference, 'uri') proto_utils.set_value_at_field( new_uri, 'value', references.reference_to_string(reference)) self._print_message(standardized_reference) else: self._print_message(reference)