Skip to content

Commit

Permalink
Introduce VFSException class
Browse files Browse the repository at this point in the history
  • Loading branch information
phmatray committed Feb 4, 2023
1 parent f610291 commit 687756d
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 91 deletions.
17 changes: 17 additions & 0 deletions docs/api/VFSException.VFSException(string).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#### [Atypical.VirtualFileSystem.Core](VirtualFileSystem.md 'VirtualFileSystem')
### [Atypical.VirtualFileSystem.Core.Exceptions](VirtualFileSystem.md#Atypical.VirtualFileSystem.Core.Exceptions 'Atypical.VirtualFileSystem.Core.Exceptions').[VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException')

## VFSException(string) Constructor

Initializes a new instance of the [VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException') class with a message that describes the error.

```csharp
public VFSException(string message);
```
#### Parameters

<a name='Atypical.VirtualFileSystem.Core.Exceptions.VFSException.VFSException(string).message'></a>

`message` [System.String](https://docs.microsoft.com/en-us/dotnet/api/System.String 'System.String')

The error message that explains the reason for the exception.
25 changes: 25 additions & 0 deletions docs/api/VFSException.VFSException(string,Exception).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#### [Atypical.VirtualFileSystem.Core](VirtualFileSystem.md 'VirtualFileSystem')
### [Atypical.VirtualFileSystem.Core.Exceptions](VirtualFileSystem.md#Atypical.VirtualFileSystem.Core.Exceptions 'Atypical.VirtualFileSystem.Core.Exceptions').[VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException')

## VFSException(string, Exception) Constructor

Initializes a new instance of the [VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException') class with a message and an inner exception that is the cause
of this exception.

```csharp
public VFSException(string message, System.Exception innerException);
```
#### Parameters

<a name='Atypical.VirtualFileSystem.Core.Exceptions.VFSException.VFSException(string,System.Exception).message'></a>

`message` [System.String](https://docs.microsoft.com/en-us/dotnet/api/System.String 'System.String')

The error message that explains the reason for the exception.

<a name='Atypical.VirtualFileSystem.Core.Exceptions.VFSException.VFSException(string,System.Exception).innerException'></a>

`innerException` [System.Exception](https://docs.microsoft.com/en-us/dotnet/api/System.Exception 'System.Exception')

The exception that is the cause of the current exception, or a null reference if no inner
exception is specified.
17 changes: 17 additions & 0 deletions docs/api/VFSException.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#### [Atypical.VirtualFileSystem.Core](VirtualFileSystem.md 'VirtualFileSystem')
### [Atypical.VirtualFileSystem.Core.Exceptions](VirtualFileSystem.md#Atypical.VirtualFileSystem.Core.Exceptions 'Atypical.VirtualFileSystem.Core.Exceptions')

## VFSException Class

Exception thrown by the VFS.

```csharp
public class VFSException : System.Exception
```

Inheritance [System.Object](https://docs.microsoft.com/en-us/dotnet/api/System.Object 'System.Object') &#129106; [System.Exception](https://docs.microsoft.com/en-us/dotnet/api/System.Exception 'System.Exception') &#129106; VFSException
| Constructors | |
| :--- | :--- |
| [VFSException(string, Exception)](VFSException.VFSException(string,Exception).md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException.VFSException(string, System.Exception)') | Initializes a new instance of the [VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException') class with a message and an inner exception that is the cause<br/>of this exception. |
| [VFSException(string)](VFSException.VFSException(string).md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException.VFSException(string)') | Initializes a new instance of the [VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException') class with a message that describes the error. |
8 changes: 8 additions & 0 deletions docs/api/VirtualFileSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,14 @@
For example, the path of the node with the path "./temp/file.txt" is "./temp/file.txt".
The path of the node with the path "./temp/" is "./temp/".

<a name='Atypical.VirtualFileSystem.Core.Exceptions'></a>

## Atypical.VirtualFileSystem.Core.Exceptions Namespace
- **[VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException')** `Class` Exception thrown by the VFS.
- **[VFSException(string, Exception)](VFSException.VFSException(string,Exception).md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException.VFSException(string, System.Exception)')** `Constructor` Initializes a new instance of the [VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException') class with a message and an inner exception that is the cause
of this exception.
- **[VFSException(string)](VFSException.VFSException(string).md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException.VFSException(string)')** `Constructor` Initializes a new instance of the [VFSException](VFSException.md 'Atypical.VirtualFileSystem.Core.Exceptions.VFSException') class with a message that describes the error.

<a name='Atypical.VirtualFileSystem.Core.Models'></a>

## Atypical.VirtualFileSystem.Core.Models Namespace
Expand Down
4 changes: 4 additions & 0 deletions docs/links
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ T:Atypical.VirtualFileSystem.Core.Models.FileNode|FileNode.md|FileNode
M:Atypical.VirtualFileSystem.Core.Models.RootNode.#ctor|RootNode.RootNode().md|RootNode()
M:Atypical.VirtualFileSystem.Core.Models.RootNode.ToString|RootNode.ToString().md|ToString()
T:Atypical.VirtualFileSystem.Core.Models.RootNode|RootNode.md|RootNode
M:Atypical.VirtualFileSystem.Core.Exceptions.VFSException.#ctor(System.String)|VFSException.VFSException(string).md|VFSException(string)
M:Atypical.VirtualFileSystem.Core.Exceptions.VFSException.#ctor(System.String,System.Exception)|VFSException.VFSException(string,Exception).md|VFSException(string, Exception)
N:Atypical.VirtualFileSystem.Core.Exceptions|VirtualFileSystem.md#Atypical.VirtualFileSystem.Core.Exceptions|Atypical.VirtualFileSystem.Core.Exceptions
T:Atypical.VirtualFileSystem.Core.Exceptions.VFSException|VFSException.md|VFSException
N:Atypical.VirtualFileSystem.Core.Contracts|VirtualFileSystem.md#Atypical.VirtualFileSystem.Core.Contracts|Atypical.VirtualFileSystem.Core.Contracts
T:Atypical.VirtualFileSystem.Core.Contracts.IDirectoryNode|IDirectoryNode.md|IDirectoryNode
P:Atypical.VirtualFileSystem.Core.Contracts.IFileNode.Content|IFileNode.Content.md|Content
Expand Down
51 changes: 43 additions & 8 deletions src/Atypical.VirtualFileSystem.Core/Abstractions/VFSPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public abstract record VFSPath
/// </summary>
public static readonly Regex VFSPathRegex = new(VFSPathRegexPattern, RegexOptions.Compiled);



/// <summary>
/// Creates a new instance of <see cref="VFSPath" />.
/// </summary>
Expand All @@ -30,7 +32,9 @@ public abstract record VFSPath
/// <exception cref="ArgumentException">Thrown when the path is invalid.</exception>
public VFSPath(string path)
{
ArgumentNullException.ThrowIfNull(path);
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (path is null)
ThrowArgumentHasInvalidFormat(string.Empty);

var vfsPath = CleanVFSPathInput(path);

Expand All @@ -41,11 +45,11 @@ public VFSPath(string path)
return;
}

if (!VFSPathRegex.IsMatch(vfsPath))
throw new ArgumentException($"The path '{path}' is invalid.", nameof(path));

if (vfsPath.Contains($".{DIRECTORY_SEPARATOR}") || vfsPath.Contains($"{DIRECTORY_SEPARATOR}."))
throw new ArgumentException($"The path '{path}' contains relative path segments.", nameof(path));
ThrowArgumentHasRelativePathSegment(vfsPath);

if (!VFSPathRegex.IsMatch(vfsPath))
ThrowArgumentHasInvalidFormat(vfsPath);

Value = vfsPath;

Expand Down Expand Up @@ -143,6 +147,9 @@ private string CleanVFSPathInput(string path)
// if is root path, return it
if (cleanPath is ROOT_PATH or "")
return cleanPath;

// replace backslashes with forward slashes
cleanPath = cleanPath.Replace('\\', '/');

// clean up the path - remove leading and trailing slashes
cleanPath = cleanPath.TrimStart('/');
Expand All @@ -167,8 +174,7 @@ private string CleanVFSPathInput(string path)
public VFSPath GetAbsoluteParentPath(int depthFromRoot)
{
if (depthFromRoot < 0)
throw new ArgumentOutOfRangeException(nameof(depthFromRoot),
"The depth from root must be greater than or equal to 0.");
ThrowDepthFromRootMustBeGreaterThanOrEqualToZero(depthFromRoot);

if (IsRoot)
return this;
Expand All @@ -184,4 +190,33 @@ public VFSPath GetAbsoluteParentPath(int depthFromRoot)
/// </summary>
/// <returns>A hash code for the current object.</returns>
public override int GetHashCode() => Value.GetHashCode();
}

[DoesNotReturn]
private static void ThrowArgumentHasRelativePathSegment(string vfsPath)
{
var message = $"The path '{vfsPath}' contains a relative path segment.";
throw new VFSException(message, new ArgumentException(message));
}

[DoesNotReturn]
private static void ThrowArgumentHasInvalidFormat(string vfsPath)
{
var message = vfsPath.Length > 0
? $"The path '{vfsPath}' is invalid."
: "An empty path is invalid.";

throw new VFSException(message, new ArgumentException(message));
}

[DoesNotReturn]
private static void ThrowDepthFromRootMustBeGreaterThanOrEqualToZero(int depthFromRoot)
{
var message = $"""
The depth from root must be greater than or equal to 0.
Actual value: {depthFromRoot}.
""";

throw new VFSException(message);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions_005Cinvaliddirectory/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions_005Cinvalidpath/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
36 changes: 36 additions & 0 deletions src/Atypical.VirtualFileSystem.Core/Exceptions/VFSException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2022, Atypical Consulting SRL
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

namespace Atypical.VirtualFileSystem.Core.Exceptions;

/// <summary>
/// Exception thrown by the VFS.
/// </summary>
public class VFSException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="VFSException"/> class with a message that describes the error.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
public VFSException(string message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="VFSException"/> class with a message and an inner exception that is the cause
/// of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">
/// The exception that is the cause of the current exception, or a null reference if no inner
/// exception is specified.
/// </param>
public VFSException(string message, Exception innerException)
: base(message, innerException)
{
}
}
1 change: 1 addition & 0 deletions src/Atypical.VirtualFileSystem.Core/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
global using System.Text.RegularExpressions;
global using Atypical.VirtualFileSystem.Core.Abstractions;
global using Atypical.VirtualFileSystem.Core.Contracts;
global using Atypical.VirtualFileSystem.Core.Exceptions;
global using Atypical.VirtualFileSystem.Core.Models;
global using Atypical.VirtualFileSystem.Core.ValueObjects;
global using static Atypical.VirtualFileSystem.Core.VFSConstants;
45 changes: 40 additions & 5 deletions src/Atypical.VirtualFileSystem.Core/VFS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private void AddToIndex(IVirtualFileSystemNode node)
var added = Index.TryAdd(node.Path.Value, node);

if (!added)
throw new InvalidOperationException($"The node '{node.Path}' already exists in the index.");
ThrowVirtualNodeAlreadyExists(node);

if (node.Path.Parent is not null && !Index.ContainsKey(node.Path.Parent.Value))
CreateDirectory(node.Path.Parent);
Expand Down Expand Up @@ -135,7 +135,7 @@ public bool TryGetDirectory(string path, out IDirectoryNode? directory)
public IVirtualFileSystem CreateDirectory(VFSDirectoryPath directoryPath)
{
if (directoryPath.IsRoot)
throw new ArgumentException("Cannot create root directory.", nameof(directoryPath));
ThrowCannotCreateRootDirectory();

var directory = new DirectoryNode(directoryPath);
AddToIndex(directory);
Expand All @@ -150,12 +150,12 @@ public IVirtualFileSystem CreateDirectory(string path)
public IVirtualFileSystem DeleteDirectory(VFSDirectoryPath directoryPath)
{
if (directoryPath.IsRoot)
throw new ArgumentException("Cannot delete root directory.", nameof(directoryPath));
ThrowCannotDeleteRootDirectory();

// try to get the directory
var found = TryGetDirectory(directoryPath, out _);
if (!found)
throw new KeyNotFoundException($"The directory '{directoryPath}' does not exist.");
ThrowVirtualDirectoryNotFound(directoryPath);

// find the path and its children in the index
var paths = Index.Keys
Expand Down Expand Up @@ -231,7 +231,7 @@ public IVirtualFileSystem DeleteFile(VFSFilePath filePath)
// try to get the file
var found = TryGetFile(filePath, out _);
if (!found)
throw new KeyNotFoundException($"The file '{filePath}' does not exist.");
ThrowVirtualFileNotFound(filePath);

// remove the file from the index
Index.Remove(filePath.Value);
Expand All @@ -252,4 +252,39 @@ public IEnumerable<IFileNode> FindFiles(Regex regexPattern)
=> FindFiles().Where(f => regexPattern.IsMatch(f.Path.Value));

#endregion

[DoesNotReturn]
private static void ThrowVirtualNodeAlreadyExists(IVirtualFileSystemNode node)
{
var message = $"The node '{node.Path}' already exists in the index.";
throw new VFSException(message);
}

[DoesNotReturn]
private static void ThrowVirtualFileNotFound(VFSFilePath filePath)
{
var message = $"The file '{filePath}' does not exist in the index.";
throw new VFSException(message);
}

[DoesNotReturn]
private static void ThrowVirtualDirectoryNotFound(VFSDirectoryPath directoryPath)
{
var message = $"The directory '{directoryPath}' does not exist in the index.";
throw new VFSException(message);
}

[DoesNotReturn]
private static void ThrowCannotDeleteRootDirectory()
{
const string message = "Cannot delete the root directory.";
throw new VFSException(message);
}

[DoesNotReturn]
private static void ThrowCannotCreateRootDirectory()
{
const string message = "Cannot create the root directory.";
throw new VFSException(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public VFSDirectoryPath(string path)
// cannot ends with a file extension
var lastSegment = Value.Split('/').Last();
if (lastSegment.Contains('.'))
throw new ArgumentException("The path must not contain a file extension.", nameof(path));
ThrowArgumentHasFileExtension(path);
}

/// <summary>
Expand All @@ -41,4 +41,11 @@ public VFSDirectoryPath(string path)
/// <param name="path">The path to convert.</param>
/// <returns>The string representation of the path.</returns>
public static implicit operator string(VFSDirectoryPath path) => path.Value;

[DoesNotReturn]
private static void ThrowArgumentHasFileExtension(string path)
{
var message = $"The directory path '{path}' contains a file extension.";
throw new VFSException(message, new ArgumentException(message));
}
}
1 change: 1 addition & 0 deletions tests/Atypical.VirtualFileSystem.UnitTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Global using directives

global using Atypical.VirtualFileSystem.Core;
global using Atypical.VirtualFileSystem.Core.Exceptions;
global using Atypical.VirtualFileSystem.Core.Models;
global using Atypical.VirtualFileSystem.Core.ValueObjects;
global using FluentAssertions;
Expand Down
Loading

0 comments on commit 687756d

Please sign in to comment.