Repository: microsoft/onefuzz
File: src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs (C#)

Code before:
                await LogFailedNotification(report, e, notificationId);
                return OneFuzzResultVoid.Error(ErrorCode.NOTIFICATION_FAILURE,
                    $"Failed to process ado notification : exception: {e}");
            }
        }
        return OneFuzzResultVoid.Ok;
    }

    private static bool IsTransient(Exception e) {
        var errorCodes = new List<string>()
        {
            //TF401349: An unexpected error has occurred, please verify your request and try again.
            "TF401349",
            //TF26071: This work item has been changed by someone else since you opened it. You will need to refresh it and discard your changes.
            "TF26071",
        };

        var errorStr = e.ToString();
        return errorCodes.Any(errorStr.Contains);
    }

    private static OneFuzzResultVoid ValidatePathNaming(string path, string? pathType) {
        // Validate path based on https://learn.microsoft.com/en-us/azure/devops/organizations/settings/about-areas-iterations?view=azure-devops#naming-restrictions

        var pathParts = path.Split('\\');
        var maxLength = 255;
        var maxDepth = 13;
        var invalidChars = new char[] { '/', '.', '~', '[', ']', ':', '$', '?', '*', '<', '>', '|', '+', '=', ';', ',' };

        // Ensure that none of the path parts are too long
        var erroneous = pathParts.FirstOrDefault(part => part.Length > maxLength);
        if (erroneous != null) {
            return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
                $"{pathType} Path \"{path}\" is invalid. \"{erroneous}\" is too long. It must be less than {maxLength} characters.",
            });
        }

        // Ensure that none of the path parts contain invalid characters
        erroneous = pathParts.FirstOrDefault(part => invalidChars.Any(part.Contains));
        if (erroneous != null) {
            return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
                $"{pathType} Path \"{path}\" is invalid. \"{erroneous}\" contains an invalid character ({string.Join(" ", invalidChars)}).",
                "Make sure that the path is separated by backslashes (\\) and not forward slashes (/).",
            });
        }

        // Ensure that there aren't too many path parts
        if (pathParts.Length > maxDepth) {
            return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
                $"{pathType} Path \"{path}\" is invalid. It must be less than {maxDepth} levels deep.",
            });

Review comment (suggestion):
This could be confused with the max path length which is 4000. Maybe a better name would be `maxNodeLength`.

Code after:
                await LogFailedNotification(report, e, notificationId);
                return OneFuzzResultVoid.Error(ErrorCode.NOTIFICATION_FAILURE,
                    $"Failed to process ado notification : exception: {e}");
            }
        }
        return OneFuzzResultVoid.Ok;
    }

    private static bool IsTransient(Exception e) {
        var errorCodes = new List<string>()
        {
            //TF401349: An unexpected error has occurred, please verify your request and try again.
            "TF401349",
            //TF26071: This work item has been changed by someone else since you opened it. You will need to refresh it and discard your changes.
            "TF26071",
        };

        var errorStr = e.ToString();
        return errorCodes.Any(errorStr.Contains);
    }

    public static OneFuzzResultVoid ValidateTreePath(IEnumerable<string> path, WorkItemClassificationNode? root) {
        if (root is null) {
            return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PROJECT, new string[] {
                $"Path \"{string.Join('\\', path)}\" is invalid. The specified ADO project doesn't exist.",
                "Double check the 'project' field in your ADO config.",
            });
        }

        string treeNodeTypeName;
        switch (root.StructureType) {
            case TreeNodeStructureType.Area:
                treeNodeTypeName = "Area";
                break;
            case TreeNodeStructureType.Iteration:
                treeNodeTypeName = "Iteration";
                break;
            default:
                return OneFuzzResultVoid.Error(ErrorCode.ADO_VALIDATION_INVALID_PATH, new string[] {
                    $"Path root \"{root.Name}\" is an unsupported type. Expected Area or Iteration but got {root.StructureType}.",
                });
        }

        // Validate path based on
        // https://learn.microsoft.com/en-us/azure/devops/organizations/settings/about-areas-iterations?view=azure-devops#naming-restrictions
        var maxNodeLength = 255;
        var maxDepth = 13;
        // Invalid characters from the link above plus the escape sequences (since they have backslashes and produce confusingly formatted errors if not caught here)
        var invalidChars = new char[] { '/', ':', '*', '?', '"', '<', '>', '|', ';', '#', '$', '*', '{', '}', ',', '+', '=', '[', ']' };

        // Ensure that none of the path parts are too long