コード例 #1
0
 def compare(self):
     # 1. If original service is None, then a new service is added.
     if self.service_original is None:
         FindingContainer.addFinding(
             category=FindingCategory.SERVICE_ADDITION,
             location=f"{self.service_update.proto_file_name} Line: {self.service_update.source_code_line}",
             message=f"A new service {self.service_update.name} is added.",
             actionable=False,
         )
         return
     # 2. If updated service is None, then the original service is removed.
     if self.service_update is None:
         FindingContainer.addFinding(
             category=FindingCategory.SERVICE_REMOVAL,
             location=f"{self.service_original.proto_file_name} Line: {self.service_original.source_code_line}",
             message=f"A service {self.service_original.name} is removed",
             actionable=True,
         )
         return
     self.messages_map_original = self.service_original.messages_map
     self.messages_map_update = self.service_update.messages_map
     # 3. Check the methods list
     self._compareRpcMethods(
         self.service_original,
         self.service_update,
         self.messages_map_original,
         self.messages_map_update,
     )
コード例 #2
0
    def _compare(self, message_original, message_update):
        # 1. If original message is None, then a new message is added.
        if self.message_original is None:
            msg = 'A new message {} is added.'.format(self.message_update.name)
            FindingContainer.addFinding(FindingCategory.MESSAGE_ADDITION, "",
                                        msg, False)
            return
        # 2. If updated message is None, then the original message is removed.
        if self.message_update is None:
            msg = 'A message {} is removed'.format(self.message_original.name)
            FindingContainer.addFinding(FindingCategory.MESSAGE_REMOVAL, "",
                                        msg, True)
            return

        # 3. Check breaking changes in each fields. Note: Fields are identified by number, not by name.
        # Descriptor.fields_by_number (dict int -> FieldDescriptor) indexed by number.
        if message_original.fields_by_number or message_update.fields_by_number:
            self._compareNestedFields(message_original.fields_by_number,
                                      message_update.fields_by_number)

        # 4. Check breaking changes in nested message.
        # Descriptor.nested_types_by_name (dict str -> Descriptor) indexed by name.
        # Recursively call _compare for nested message type comparison.
        if (message_original.nested_types_by_name
                or message_update.nested_types_by_name):
            self._compareNestedMessages(message_original.nested_types_by_name,
                                        message_update.nested_types_by_name)
コード例 #3
0
    def _compare(self, message_original, message_update):
        # 1. If original message is None, then a new message is added.
        if message_original is None:
            FindingContainer.addFinding(
                category=FindingCategory.MESSAGE_ADDITION,
                location=
                f"{message_update.proto_file_name} Line: {message_update.source_code_line}",
                message=f"A new message {message_update.name} is added.",
                actionable=False,
            )
            return
        # 2. If updated message is None, then the original message is removed.
        if message_update is None:
            FindingContainer.addFinding(
                category=FindingCategory.MESSAGE_REMOVAL,
                location=
                f"{message_original.proto_file_name} Line: {message_original.source_code_line}",
                message=f"A message {message_original.name} is removed",
                actionable=True,
            )
            return

        self.global_resources_original = self.message_original.file_resources
        self.global_resources_update = self.message_update.file_resources
        # 3. Check breaking changes in each fields. Note: Fields are
        # identified by number, not by name. Descriptor.fields_by_number
        # (dict int -> FieldDescriptor) indexed by number.
        if message_original.fields or message_update.fields:
            self._compare_nested_fields(
                message_original.fields,
                message_update.fields,
            )

        # 4. Check breaking changes in nested message.
        # Descriptor.nested_types_by_name (dict str -> Descriptor)
        # indexed by name. Recursively call _compare for nested
        # message type comparison.
        if message_original.nested_messages or message_update.nested_messages:
            self._compare_nested_messages(
                message_original.nested_messages,
                message_update.nested_messages,
            )
        # 5. Check breaking changes in nested enum.
        if message_original.nested_enums or message_update.nested_enums:
            self._compare_nested_enums(
                message_original.nested_enums,
                message_update.nested_enums,
            )

        # 6. Check `google.api.resource` annotation.
        self._compare_resources(message_original.resource,
                                message_update.resource)
コード例 #4
0
 def _compare_method_signatures(self, method_original, method_update):
     signatures_original = method_original.method_signatures.value
     signatures_update = method_update.method_signatures.value
     if len(signatures_original) > len(signatures_update):
         FindingContainer.addFinding(
             category=FindingCategory.METHOD_SIGNATURE_CHANGE,
             location=f"{method_original.proto_file_name} Line: {method_original.method_signatures.source_code_line}",
             message="An existing method_signature is removed.",
             actionable=True,
         )
     for old_sig, new_sig in zip(signatures_original, signatures_update):
         if old_sig != new_sig:
             FindingContainer.addFinding(
                 category=FindingCategory.METHOD_SIGNATURE_CHANGE,
                 location=f"{method_update.proto_file_name} Line: {method_update.method_signatures.source_code_line}",
                 message=f"An existing method_signature is changed from '{old_sig}' to '{new_sig}'.",
                 actionable=True,
             )
コード例 #5
0
    def compare(self):
        # 1. If original EnumDescriptor is None, then a new
        # EnumDescriptor is added.
        if self.enum_original is None:
            FindingContainer.addFinding(
                category=FindingCategory.ENUM_ADDITION,
                location=
                f"{self.enum_update.proto_file_name} Line: {self.enum_update.source_code_line}",
                message=f"A new Enum {self.enum_update.name} is added.",
                actionable=False,
            )

        # 2. If updated EnumDescriptor is None, then the original
        # EnumDescriptor is removed.
        elif self.enum_update is None:
            FindingContainer.addFinding(
                category=FindingCategory.ENUM_REMOVAL,
                location=
                f"{self.enum_original.proto_file_name} Line: {self.enum_original.source_code_line}",
                message=f"An Enum {self.enum_original.name} is removed",
                actionable=True,
            )

        # 3. If the EnumDescriptors have the same name, check the values
        # of them stay the same. Enum values are identified by number,
        # not by name.
        else:
            enum_values_dict_original = self.enum_original.values
            enum_values_dict_update = self.enum_update.values
            enum_values_keys_set_original = set(
                enum_values_dict_original.keys())
            enum_values_keys_set_update = set(enum_values_dict_update.keys())
            # Compare Enum values that only exist in original version
            for number in enum_values_keys_set_original - enum_values_keys_set_update:
                EnumValueComparator(enum_values_dict_original[number],
                                    None).compare()
            # Compare Enum values that only exist in update version
            for number in enum_values_keys_set_update - enum_values_keys_set_original:
                EnumValueComparator(None,
                                    enum_values_dict_update[number]).compare()
            # Compare Enum values that exist both in original and update versions
            for number in enum_values_keys_set_original & enum_values_keys_set_update:
                EnumValueComparator(enum_values_dict_original[number],
                                    enum_values_dict_update[number]).compare()
コード例 #6
0
    def compare(self):
        # 1. If original service is None, then a new service is added.
        if self.service_original is None:
            msg = 'A new service {} is added.'.format(self.service_update.name)
            FindingContainer.addFinding(FindingCategory.SERVICE_ADDITION, "",
                                        msg, False)
            return
        # 2. If updated service is None, then the original service is removed.
        if self.service_update is None:
            msg = 'A service {} is removed'.format(self.service_original.name)
            FindingContainer.addFinding(FindingCategory.SERVICE_REMOVAL, "",
                                        msg, True)
            return

        # 3. TODO(xiaozhenliu): method_signature annotation
        # 4. TODO(xiaozhenliu): LRO operation_info annotation
        # 5. TODO(xiaozhenliu): google.api.http annotation
        # 6. Check the methods list
        self._compareRpcMethods(self.service_original, self.service_update)
コード例 #7
0
 def compare(self):
     # 1. If original EnumValue is None, then a new EnumValue is added.
     if self.enum_value_original is None:
         FindingContainer.addFinding(
             category=FindingCategory.ENUM_VALUE_ADDITION,
             location=f"{self.enum_value_update.proto_file_name} Line: {self.enum_value_update.source_code_line}",
             message=f"A new EnumValue {self.enum_value_update.name} is added.",
             actionable=False,
         )
     # 2. If updated EnumValue is None, then the original EnumValue is removed.
     elif self.enum_value_update is None:
         FindingContainer.addFinding(
             category=FindingCategory.ENUM_VALUE_REMOVAL,
             location=f"{self.enum_value_original.proto_file_name} Line: {self.enum_value_original.source_code_line}",
             message=f"An EnumValue {self.enum_value_original.name} is removed",
             actionable=True,
         )
     # 3. If both EnumValueDescriptors are existing, check if the name is changed.
     elif self.enum_value_original.name != self.enum_value_update.name:
         FindingContainer.addFinding(
             category=FindingCategory.ENUM_VALUE_NAME_CHANGE,
             location=f"{self.enum_value_update.proto_file_name} Line: {self.enum_value_update.source_code_line}",
             message=f"Name of the EnumValue is changed, the original is {self.enum_value_original.name}, but the updated is {self.enum_value_update.name}",
             actionable=True,
         )
コード例 #8
0
 def _is_parent_type(self, child_type, parent_type, original_is_child):
     if original_is_child:
         parent_resources = (
             self.global_resources_original.get_parent_resources_by_child_type(
                 child_type
             )
         )
     else:
         parent_resources = (
             self.global_resources_update.get_parent_resources_by_child_type(
                 child_type
             )
         )
     if parent_type not in [parent.type for parent in parent_resources]:
         # Resulting referenced resource patterns cannot be resolved identical.
         FindingContainer.addFinding(
             FindingCategory.RESOURCE_REFERENCE_CHANGE,
             "",
             f"The child_type '{child_type}' and type '{parent_type}' of "
             f"resource reference option in field '{self.field_original.name}' "
             "cannot be resolved to the identical resource.",
             True,
         )
コード例 #9
0
 def _compare_resource_reference(self, field_original, field_update):
     resource_ref_original = self.field_original.resource_reference
     resource_ref_update = self.field_update.resource_reference
     # No resource_reference annotations found for the field in both versions.
     if not resource_ref_original and not resource_ref_update:
         return
     # A `google.api.resource_reference` annotation is added.
     if not resource_ref_original and resource_ref_update:
         FindingContainer.addFinding(
             FindingCategory.RESOURCE_REFERENCE_ADDITION,
             "",
             f"A resource reference option is added to the field {field_original.name}",
             False,
         )
         return
     # Resource annotation is removed, check if it is added as a message resource.
     if resource_ref_original and not resource_ref_update:
         if not self._resource_ref_in_local(resource_ref_original):
             FindingContainer.addFinding(
                 FindingCategory.RESOURCE_REFERENCE_REMOVAL,
                 "",
                 f"A resource reference option of field '{field_original.name}' is removed.",
                 True,
             )
         return
     # Resource annotation is both existing in the field for original and update versions.
     # They both use `type` or `child_type`.
     if field_original.child_type == field_update.child_type:
         original_type = (
             resource_ref_original.type or resource_ref_original.child_type
         )
         update_type = resource_ref_update.type or resource_ref_update.child_type
         if original_type != update_type:
             FindingContainer.addFinding(
                 FindingCategory.RESOURCE_REFERENCE_CHANGE,
                 "",
                 f"The type of resource reference option in field '{field_original.name}' is changed from '{original_type}' to '{update_type}'.",
                 True,
             )
         return
     # The `type` is changed to `child_type` or `child_type` is changed to `type`, but
     # resulting referenced resource patterns can be resolved to be identical,
     # in that case it is not considered breaking.
     # Register the message-level resource into the global resource database,
     # so that we can query the parent resources for child_type.
     self._register_local_resource()
     if field_original.child_type:
         self._is_parent_type(
             resource_ref_original.child_type, resource_ref_update.type, True
         )
     if field_update.child_type:
         self._is_parent_type(
             resource_ref_update.child_type, resource_ref_original.type, False
         )
コード例 #10
0
 def _compare_lro_annotations(self, method_original, method_update):
     lro_original = method_original.lro_annotation
     lro_update = method_update.lro_annotation
     if not lro_original and not lro_update:
         return
     # LRO operation_info annotation addition.
     if not lro_original and lro_update:
         FindingContainer.addFinding(
             category=FindingCategory.LRO_ANNOTATION_ADDITION,
             location=f"{method_update.proto_file_name} Line: {method_update.lro_annotation.source_code_line}",
             message="A LRO operation_info annotation is added.",
             actionable=False,
         )
         return
     # LRO operation_info annotation removal.
     if lro_original and not lro_update:
         FindingContainer.addFinding(
             category=FindingCategory.LRO_ANNOTATION_REMOVAL,
             location=f"{method_original.proto_file_name} Line: {method_original.lro_annotation.source_code_line}",
             message="A LRO operation_info annotation is removed.",
             actionable=False,
         )
         return
     # The response_type value of LRO operation_info is changed.
     if lro_original.value["response_type"] != lro_update.value["response_type"]:
         FindingContainer.addFinding(
             category=FindingCategory.LRO_RESPONSE_CHANGE,
             location=f"{method_update.proto_file_name} Line: {lro_update.source_code_line}",
             message=f"The response_type of LRO operation_info annotation is changed from {lro_original.value['response_type']} to {lro_update.value['response_type']}",
             actionable=True,
         )
     # The metadata_type value of LRO operation_info is changed.
     if lro_original.value["metadata_type"] != lro_update.value["metadata_type"]:
         FindingContainer.addFinding(
             category=FindingCategory.LRO_METADATA_CHANGE,
             location=f"{method_update.proto_file_name} Line: {lro_update.source_code_line}",
             message=f"The metadata_type of LRO operation_info annotation is changed from {lro_original.value['metadata_type']} to {lro_update.value['metadata_type']}",
             actionable=True,
         )
コード例 #11
0
    def compare(self):
        # 1. If original EnumDescriptor is None, then a new EnumDescriptor is added.
        if self.enum_original is None:
            msg = 'A new Enum {} is added.'.format(self.enum_update.name)
            FindingContainer.addFinding(FindingCategory.ENUM_ADDITION, "", msg,
                                        False)

        # 2. If updated EnumDescriptor is None, then the original EnumDescriptor is removed.
        elif self.enum_update is None:
            msg = 'An Enum {} is removed'.format(self.enum_original.name)
            FindingContainer.addFinding(FindingCategory.ENUM_REMOVAL, "", msg,
                                        True)

        # 3. If both EnumDescriptors are existing, check if the name is changed.
        elif self.enum_original.name != self.enum_update.name:
            msg = 'Name of the Enum is changed, the original is {}, but the updated is {}'.format(
                self.enum_original.name, self.enum_update.name)
            FindingContainer.addFinding(FindingCategory.ENUM_NAME_CHANGE, "",
                                        msg, True)

        # 4. If the EnumDescriptors have the same name, check the values of them stay the same.
        # Enum values are identified by number, not by name.
        else:
            enum_values_dict_original = {
                x.number: x
                for x in self.enum_original.values
            }
            enum_values_dict_update = {
                x.number: x
                for x in self.enum_update.values
            }
            # Compare Enum values that only exist in original version
            for number in list(
                    set(enum_values_dict_original.keys()) -
                    set(enum_values_dict_update.keys())):
                EnumValueComparator(enum_values_dict_original[number],
                                    None).compare()
            # Compare Enum values that only exist in update version
            for number in list(
                    set(enum_values_dict_update.keys()) -
                    set(enum_values_dict_original.keys())):
                EnumValueComparator(None,
                                    enum_values_dict_update[number]).compare()
            # Compare Enum values that exist both in original and update versions
            for number in list(
                    set(enum_values_dict_update.keys())
                    & set(enum_values_dict_original.keys())):
                EnumValueComparator(enum_values_dict_original[number],
                                    enum_values_dict_update[number]).compare()
コード例 #12
0
    def _compare_resources(self, fs_original, fs_update):
        resources_original = fs_original.resources_database
        resources_update = fs_update.resources_database
        resources_types_original = set(resources_original.types.keys())
        resources_types_update = set(resources_update.types.keys())
        # 1. Patterns of file-level resource definitions have changed.
        # TODO(xiaozhenliu): add source code information for file-level resource definition.
        for resource_type in resources_types_original & resources_types_update:
            patterns_original = resources_original.types[resource_type].pattern
            patterns_update = resources_update.types[resource_type].pattern
            # An existing pattern is removed.
            if len(patterns_original) > len(patterns_update):
                FindingContainer.addFinding(
                    category=FindingCategory.RESOURCE_DEFINITION_CHANGE,
                    location="",
                    message=
                    f"An existing pattern value of the resource definition '{resource_type}' is removed.",
                    actionable=True,
                )
            # An existing pattern value is changed.
            # A new pattern value appended to the pattern list is not consider breaking change.
            for old_pattern, new_pattern in zip(patterns_original,
                                                patterns_update):
                if old_pattern != new_pattern:
                    FindingContainer.addFinding(
                        category=FindingCategory.RESOURCE_DEFINITION_CHANGE,
                        location="",
                        message=
                        f"Pattern value of the resource definition '{resource_type}' is updated from '{old_pattern}' to '{new_pattern}'.",
                        actionable=True,
                    )

        # 2. File-level resource definitions addition.
        for resource_type in resources_types_update - resources_types_original:
            FindingContainer.addFinding(
                category=FindingCategory.RESOURCE_DEFINITION_ADDITION,
                location="",
                message=
                f"A file-level resource definition '{resource_type}' has been added.",
                actionable=False,
            )
コード例 #13
0
 def compare(self):
     # 1. If original EnumValue is None, then a new EnumValue is added.
     if self.enum_value_original is None:
         msg = 'A new EnumValue {} is added.'.format(
             self.enum_value_update.name)
         FindingContainer.addFinding(FindingCategory.ENUM_VALUE_ADDITION,
                                     "", msg, False)
     # 2. If updated EnumValue is None, then the original EnumValue is removed.
     elif self.enum_value_update is None:
         msg = ('An EnumValue {} is removed'.format(
             self.enum_value_original.name))
         FindingContainer.addFinding(FindingCategory.ENUM_VALUE_REMOVAL, "",
                                     msg, True)
     # 3. If both EnumValueDescriptors are existing, check if the name is changed.
     elif self.enum_value_original.name != self.enum_value_update.name:
         msg = (
             'Name of the EnumValue is changed, the original is {}, but the updated is {}'
             .format(self.enum_value_original.name,
                     self.enum_value_update.name))
         FindingContainer.addFinding(FindingCategory.ENUM_VALUE_NAME_CHANGE,
                                     "", msg, True)
コード例 #14
0
    def _compare_http_annotation(self, method_original, method_update):
        """Compare the fields `http_method, http_uri, http_body` of google.api.http annotation."""
        http_annotation_original = method_original.http_annotation.value
        http_annotation_update = method_update.http_annotation.value

        if not http_annotation_original or not http_annotation_update:
            # (Aip127) APIs must provide HTTP definitions for each RPC that they define,
            # except for bi-directional streaming RPCs, so the http_annotation addition/removal indicates
            # streaming state changes of the RPC, which is a breaking change.
            if http_annotation_original and not http_annotation_update:
                FindingContainer.addFinding(
                    category=FindingCategory.HTTP_ANNOTATION_REMOVAL,
                    location=f"{method_original.proto_file_name} Line: {method_original.http_annotation.source_code_line}",
                    message="A google.api.http annotation is removed.",
                    actionable=True,
                )
            if not http_annotation_original and http_annotation_update:
                FindingContainer.addFinding(
                    category=FindingCategory.HTTP_ANNOTATION_ADDITION,
                    location=f"{method_update.proto_file_name} Line: {method_update.http_annotation.source_code_line}",
                    message="A google.api.http annotation is added.",
                    actionable=False,
                )
            return
        for annotation in (
            ("http_method", "None", "An existing http method is changed."),
            ("http_uri", "None", "An existing http method URI is changed."),
            ("http_body", "None", "An existing http method body is changed."),
        ):
            # TODO (xiaozhenliu): this should allow version updates. For example,
            # from `v1/example:foo` to `v1beta1/example:foo` is not a breaking change.
            if http_annotation_original.get(
                annotation[0], annotation[1]
            ) != http_annotation_update.get(annotation[0], annotation[1]):
                FindingContainer.addFinding(
                    category=FindingCategory.HTTP_ANNOTATION_CHANGE,
                    location=f"{method_update.proto_file_name} Line: {method_update.http_annotation.source_code_line}",
                    message=annotation[2],
                    actionable=True,
                )
コード例 #15
0
    def compare(self):
        # 1. If original FieldDescriptor is None, then a
        # new FieldDescriptor is added.
        if self.field_original is None:
            FindingContainer.addFinding(
                category=FindingCategory.FIELD_ADDITION,
                location=f"{self.field_update.proto_file_name} Line: {self.field_update.source_code_line}",
                message=f"A new Field {self.field_update.name} is added.",
                actionable=False,
            )
            return

        # 2. If updated FieldDescriptor is None, then
        # the original FieldDescriptor is removed.
        if self.field_update is None:
            FindingContainer.addFinding(
                category=FindingCategory.FIELD_REMOVAL,
                location=f"{self.field_original.proto_file_name} Line: {self.field_original.source_code_line}",
                message=f"A Field {self.field_original.name} is removed",
                actionable=True,
            )
            return

        self.global_resources_original = self.field_original.file_resources
        self.global_resources_update = self.field_update.file_resources
        self.local_resource_original = self.field_original.message_resource
        self.local_resource_update = self.field_update.message_resource

        # 3. If both FieldDescriptors are existing, check
        # if the name is changed.
        if self.field_original.name != self.field_update.name:
            FindingContainer.addFinding(
                category=FindingCategory.FIELD_NAME_CHANGE,
                location=f"{self.field_update.proto_file_name} Line: {self.field_update.source_code_line}",
                message=f"Name of the Field is changed, the original is {self.field_original.name}, but the updated is {self.field_update.name}",
                actionable=True,
            )
            return

        # 4. If the FieldDescriptors have the same name, check if the
        # repeated state of them stay the same.
        # TODO(xiaozhenliu): add location information for field.label
        if self.field_original.label != self.field_update.label:
            FindingContainer.addFinding(
                category=FindingCategory.FIELD_REPEATED_CHANGE,
                location="",
                message=f"Repeated state of the Field is changed, the original is {self.field_original.label}, but the updated is {self.field_update.label}",
                actionable=True,
            )

        # 5. If the FieldDescriptors have the same repeated state,
        # check if the type of them stay the same.
        # TODO(xiaozhenliu): add location information for field.type
        if self.field_original.proto_type != self.field_update.proto_type:
            FindingContainer.addFinding(
                category=FindingCategory.FIELD_TYPE_CHANGE,
                location="",
                message=f"Type of the field is changed, the original is {self.field_original.proto_type}, but the updated is {self.field_update.proto_type}",
                actionable=True,
            )
        # 6. Check the oneof_index of the field.
        if self.field_original.oneof != self.field_update.oneof:
            location = f"{self.field_update.proto_file_name} Line: {self.field_update.source_code_line}"
            if self.field_original.oneof:
                msg = f"The existing field {self.field_original.name} is moved out of One-of."
                FindingContainer.addFinding(
                    category=FindingCategory.FIELD_ONEOF_REMOVAL,
                    location=location,
                    message=msg,
                    actionable=True,
                )
            else:
                msg = f"The existing field {self.field_original.name} is moved into One-of."
                FindingContainer.addFinding(
                    category=FindingCategory.FIELD_ONEOF_ADDITION,
                    location=location,
                    message=msg,
                    actionable=True,
                )

        # 6. Check `google.api.resource_reference` annotation.
        # TODO(xiaozhenliu): add location information for field.resource_reference.
        self._compare_resource_reference(self.field_original, self.field_update)
コード例 #16
0
    def _compareRpcMethods(
        self,
        service_original,
        service_update,
        messages_map_original,
        messages_map_update,
    ):
        methods_original = service_original.methods
        methods_update = service_update.methods
        methods_original_keys = set(methods_original.keys())
        methods_update_keys = set(methods_update.keys())
        # 3.1 An RPC method is removed.
        for name in methods_original_keys - methods_update_keys:
            removed_method = methods_original[name]
            FindingContainer.addFinding(
                category=FindingCategory.METHOD_REMOVAL,
                location=f"{removed_method.proto_file_name} Line: {removed_method.source_code_line}",
                message=f"An rpc method {name} is removed",
                actionable=True,
            )
        # 3.2 An RPC method is added.
        for name in methods_update_keys - methods_original_keys:
            added_method = methods_update[name]
            FindingContainer.addFinding(
                category=FindingCategory.METHOD_ADDTION,
                location=f"{added_method.proto_file_name} Line: {added_method.source_code_line}",
                message=f"An rpc method {name} is added",
                actionable=False,
            )
        for name in methods_update_keys & methods_original_keys:
            method_original = methods_original[name]
            method_update = methods_update[name]
            # 3.3 The request type of an RPC method is changed.
            input_type_original = method_original.input.value
            input_type_update = method_update.input.value
            if input_type_original != input_type_update:
                FindingContainer.addFinding(
                    category=FindingCategory.METHOD_INPUT_TYPE_CHANGE,
                    location=f"{method_update.proto_file_name} Line: {method_update.input.source_code_line}",
                    message=f"Input type of method {name} is changed from {input_type_original} to {input_type_update}",
                    actionable=True,
                )
            # 3.4 The response type of an RPC method is changed.
            response_type_original = method_original.output.value
            response_type_update = method_update.output.value
            if response_type_original != response_type_update:
                FindingContainer.addFinding(
                    category=FindingCategory.METHOD_RESPONSE_TYPE_CHANGE,
                    location=f"{method_update.proto_file_name} Line: {method_update.output.source_code_line}",
                    message=f"Output type of method {name} is changed from {response_type_original} to {response_type_update}",
                    actionable=True,
                )
            # 3.5 The request streaming state of an RPC method is changed.
            if (
                method_original.client_streaming.value
                != method_update.client_streaming.value
            ):
                FindingContainer.addFinding(
                    category=FindingCategory.METHOD_CLIENT_STREAMING_CHANGE,
                    location=f"{method_update.proto_file_name} Line: {method_update.client_streaming.source_code_line}",
                    message=f"The request streaming type of method {name} is changed",
                    actionable=True,
                )
            # 3.6 The response streaming state of an RPC method is changed.
            if (
                method_original.server_streaming.value
                != method_update.server_streaming.value
            ):
                FindingContainer.addFinding(
                    category=FindingCategory.METHOD_SERVER_STREAMING_CHANGE,
                    location=f"{method_update.proto_file_name} Line: {method_update.server_streaming.source_code_line}",
                    message=f"The response streaming type of method {name} is changed",
                    actionable=True,
                )
            # 3.7 The paginated response of an RPC method is changed.
            if method_original.paged_result_field != method_update.paged_result_field:
                FindingContainer.addFinding(
                    category=FindingCategory.METHOD_PAGINATED_RESPONSE_CHANGE,
                    location=f"{method_update.proto_file_name} Line: {method_update.source_code_line}",
                    message=f"The paginated response of method {name} is changed",
                    actionable=True,
                )
            # TODO(xiaozhenliu): add source code information for annotations.
            # The customized annotation options share the same field number (1000)
            # in MethodDescriptorProto.options.
            # 3.8 The method_signature annotation is changed.
            self._compare_method_signatures(method_original, method_update)

            # 3.9 The LRO operation_info annotation is changed.
            self._compare_lro_annotations(method_original, method_update)

            # 3.10 The google.api.http annotation is changed.
            self._compare_http_annotation(method_original, method_update)
コード例 #17
0
 def _compareRpcMethods(self, service_original, service_update):
     methods_original = {x.name: x for x in service_original.method}
     methods_update = {x.name: x for x in service_update.method}
     # 6.1 An RPC method is removed.
     for name in list(
             set(methods_original.keys()) - set(methods_update.keys())):
         msg = 'An rpc method {} is removed'.format(name)
         FindingContainer.addFinding(FindingCategory.METHOD_REMOVAL, "",
                                     msg, True)
     # 6.2 An RPC method is added.
     for name in list(
             set(methods_original.keys()) - set(methods_update.keys())):
         msg = 'An rpc method {} is added'.format(name)
         FindingContainer.addFinding(FindingCategory.METHOD_ADDTION, "",
                                     msg, False)
     for name in list(
             set(methods_original.keys()) & set(methods_update.keys())):
         method_original = methods_original[name]
         method_update = methods_update[name]
         # 6.3 The request type of an RPC method is changed.
         if method_original.input_type.name != method_update.input_type.name:
             msg = 'Input type of method {} is changed from {} to {}'.format(
                 name, method_original.input_type.name,
                 method_update.input_type.name)
             FindingContainer.addFinding(
                 FindingCategory.METHOD_INPUT_TYPE_CHANGE, "", msg, True)
         # 6.4 The request type of an RPC method is changed.
         if method_original.output_type.name != method_update.output_type.name:
             msg = 'Output type of method {} is changed from {} to {}'.format(
                 name, method_original.output_type.name,
                 method_update.output_type.name)
             FindingContainer.addFinding(
                 FindingCategory.METHOD_RESPONSE_TYPE_CHANGE, "", msg, True)
         # 6.5 The request streaming state of an RPC method is changed.
         if method_original.client_streaming != method_update.client_streaming:
             msg = 'The request streaming type of method {} is changed'.format(
                 name)
             FindingContainer.addFinding(
                 FindingCategory.METHOD_CLIENT_STREAMING_CHANGE, "", msg,
                 True)
         # 6.6 The response streaming state of an RPC method is changed.
         if method_original.server_streaming != method_update.server_streaming:
             msg = 'The response streaming type of method {} is changed'.format(
                 name)
             FindingContainer.addFinding(
                 FindingCategory.METHOD_SERVER_STREAMING_CHANGE, "", msg,
                 True)
         # 6.7 The paginated response of an RPC method is changed.
         if self._isPaginatedResponse(
                 method_original) != self._isPaginatedResponse(
                     method_update):
             msg = 'The paginated response of method {} is changed'.format(
                 name)
             FindingContainer.addFinding(
                 FindingCategory.METHOD_PAGINATED_RESPONSE_CHANGE, "", msg,
                 True)
コード例 #18
0
    def compare(self):
        # 1. If original FieldDescriptor is None, then a new FieldDescriptor is added.
        if self.field_original is None:
            msg = 'A new Field {} is added.'.format(self.field_update.name)
            FindingContainer.addFinding(FindingCategory.FIELD_ADDITION, "",
                                        msg, False)
            return

        # 2. If updated FieldDescriptor is None, then the original FieldDescriptor is removed.
        if self.field_update is None:
            msg = 'A Field {} is removed'.format(self.field_original.name)
            FindingContainer.addFinding(FindingCategory.FIELD_REMOVAL, "", msg,
                                        True)
            return

        # 3. If both FieldDescriptors are existing, check if the name is changed.
        if self.field_original.name != self.field_update.name:
            msg = 'Name of the Field is changed, the original is {}, but the updated is {}'.format(
                self.field_original.name, self.field_update.name)
            FindingContainer.addFinding(FindingCategory.FIELD_NAME_CHANGE, "",
                                        msg, True)
            return

        # 4. If the EnumDescriptors have the same name, check if the repeated state of them stay the same.
        if self.field_original.label != self.field_update.label:
            option_original = FieldDescriptorProto().Label.Name(
                self.field_original.label)
            option_update = FieldDescriptorProto().Label.Name(
                self.field_update.label)
            msg = 'Repeated state of the Field is changed, the original is {}, but the updated is {}'.format(
                option_original, option_update)
            FindingContainer.addFinding(FindingCategory.FIELD_REPEATED_CHANGE,
                                        "", msg, True)

        # 5. If the EnumDescriptors have the same repeated state, check if the type of them stay the same.
        if self.field_original.type != self.field_update.type:
            type_original = FieldDescriptorProto().Type.Name(
                self.field_original.type)
            type_update = FieldDescriptorProto().Type.Name(
                self.field_update.type)
            msg = 'Type of the Field is changed, the original is {}, but the updated is {}'.format(
                type_original, type_update)
            FindingContainer.addFinding(FindingCategory.FIELD_TYPE_CHANGE, "",
                                        msg, True)

        # 6. Check the existing field is moved out of one-of or moved into one-of .
        if self.field_original.containing_oneof != self.field_update.containing_oneof:
            if self.field_original.containing_oneof != None:
                msg = 'The Field {} is moved out of one-of'.format(
                    self.field_original.name)
                FindingContainer.addFinding(
                    FindingCategory.FIELD_ONEOF_REMOVAL, "", msg, True)
            else:
                msg = 'The Field {} is moved into one-of'.format(
                    self.field_update.name)
                FindingContainer.addFinding(
                    FindingCategory.FIELD_ONEOF_ADDITION, "", msg, True)
コード例 #19
0
    def _compare_resources(
        self,
        resource_original,
        resource_update,
    ):
        if not resource_original and not resource_update:
            return
        # 1. A new resource definition is added.
        if not resource_original and resource_update:
            FindingContainer.addFinding(
                category=FindingCategory.RESOURCE_DEFINITION_ADDITION,
                location="",
                message=
                f"A message-level resource definition {resource_update.type} has been added.",
                actionable=False,
            )
            return
        # 2. Message-level resource definitions removal may not be breaking change since
        # the resource could be moved to file-level resource definition.
        # 3. Note that the type change of an existing resource definition is like one resource
        # is removed and another one is added.
        if (resource_original and not resource_update) or (
                resource_original.type != resource_update.type):
            if not self.global_resources_update:
                FindingContainer.addFinding(
                    category=FindingCategory.RESOURCE_DEFINITION_REMOVAL,
                    location="",
                    message=
                    f"A message-level resource definition {resource_original.type} has been removed.",
                    actionable=True,
                )
                return
            # Check if the removed resource is in the global file-level resource database.
            if resource_original.type not in self.global_resources_update.types:
                FindingContainer.addFinding(
                    category=FindingCategory.RESOURCE_DEFINITION_REMOVAL,
                    location="",
                    message=
                    f"A message-level resource definition {resource_original.type} has been removed.",
                    actionable=True,
                )
            else:
                # Check the patterns of existing file-level resource are compatible with
                # the patterns of the removed message-level resource.
                global_resource_pattern = self.global_resources_update.types[
                    resource_original.type].pattern

                # If there is pattern removal, or pattern value change. Then the global file-level resource
                # can not replace the original message-level resource.
                if self._compatible_patterns(resource_original.pattern,
                                             global_resource_pattern):
                    FindingContainer.addFinding(
                        category=FindingCategory.RESOURCE_DEFINITION_REMOVAL,
                        location="",
                        message=
                        f"A message-level resource definition {resource_original.type} has been removed.",
                        actionable=True,
                    )
            return
        # Resource is existing in both original and update versions.
        # 3. Patterns of message-level resource definitions have changed.
        if self._compatible_patterns(resource_original.pattern,
                                     resource_update.pattern):
            FindingContainer.addFinding(
                category=FindingCategory.RESOURCE_DEFINITION_CHANGE,
                location="",
                message=
                f"The pattern of message-level resource definition has changed from {resource_original.pattern} to {resource_update.pattern}.",
                actionable=True,
            )