-
-
Notifications
You must be signed in to change notification settings - Fork 252
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add validation attributes for files and directories that must n…
…ot exist. (#230)
- Loading branch information
1 parent
1370b3a
commit be23040
Showing
6 changed files
with
361 additions
and
0 deletions.
There are no files selected for viewing
24 changes: 24 additions & 0 deletions
24
src/CommandLineUtils/Attributes/DirectoryNotExistsAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) Nate McMaster. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using McMaster.Extensions.CommandLineUtils.Abstractions; | ||
using McMaster.Extensions.CommandLineUtils.Validation; | ||
|
||
namespace McMaster.Extensions.CommandLineUtils | ||
{ | ||
/// <summary> | ||
/// Specifies that the data must not be an already existing directory, not a file. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Property)] | ||
public sealed class DirectoryNotExistsAttribute : FilePathNotExistsAttributeBase | ||
{ | ||
/// <summary> | ||
/// Initializes an instance of <see cref="DirectoryNotExistsAttribute"/>. | ||
/// </summary> | ||
public DirectoryNotExistsAttribute() | ||
: base(FilePathType.Directory) | ||
{ | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) Nate McMaster. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using McMaster.Extensions.CommandLineUtils.Abstractions; | ||
using McMaster.Extensions.CommandLineUtils.Validation; | ||
|
||
namespace McMaster.Extensions.CommandLineUtils | ||
{ | ||
/// <summary> | ||
/// Specifies that the data must not be an already existing file, not a directory. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Property)] | ||
public sealed class FileNotExistsAttribute : FilePathNotExistsAttributeBase | ||
{ | ||
/// <summary> | ||
/// Initializes an instance of <see cref="FileNotExistsAttribute"/>. | ||
/// </summary> | ||
public FileNotExistsAttribute() | ||
: base(FilePathType.File) | ||
{ | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/CommandLineUtils/Attributes/FileOrDirectoryNotExistsAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) Nate McMaster. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using McMaster.Extensions.CommandLineUtils.Abstractions; | ||
using McMaster.Extensions.CommandLineUtils.Validation; | ||
|
||
namespace McMaster.Extensions.CommandLineUtils | ||
{ | ||
/// <summary> | ||
/// Specifies that the data must not be an already existing file or directory. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Property)] | ||
public sealed class FileOrDirectoryNotExistsAttribute : FilePathNotExistsAttributeBase | ||
{ | ||
/// <summary> | ||
/// Initializes an instance of <see cref="FileOrDirectoryNotExistsAttribute"/>. | ||
/// </summary> | ||
public FileOrDirectoryNotExistsAttribute() | ||
: base(FilePathType.Any) | ||
{ | ||
} | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
src/CommandLineUtils/Attributes/FilePathNotExistsAttributeBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Copyright (c) Nate McMaster. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.ComponentModel.DataAnnotations; | ||
using System.IO; | ||
using McMaster.Extensions.CommandLineUtils.Abstractions; | ||
|
||
namespace McMaster.Extensions.CommandLineUtils.Validation | ||
{ | ||
/// <summary> | ||
/// Base type for attributes that check for files or directories not existing. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Property)] | ||
public abstract class FilePathNotExistsAttributeBase : ValidationAttribute | ||
{ | ||
private readonly FilePathType _filePathType; | ||
|
||
/// <summary> | ||
/// Initializes an instance of <see cref="FilePathNotExistsAttributeBase"/>. | ||
/// </summary> | ||
/// <param name="filePathType">Acceptable file path types</param> | ||
internal FilePathNotExistsAttributeBase(FilePathType filePathType) | ||
: base(GetDefaultErrorMessage(filePathType)) | ||
{ | ||
_filePathType = filePathType; | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override ValidationResult IsValid(object value, ValidationContext validationContext) | ||
{ | ||
if (!(value is string path) || path.Length == 0 || path.IndexOfAny(Path.GetInvalidPathChars()) >= 0) | ||
{ | ||
return new ValidationResult(FormatErrorMessage(value as string)); | ||
} | ||
|
||
if (!Path.IsPathRooted(path) | ||
&& validationContext.GetService(typeof(CommandLineContext)) is CommandLineContext context) | ||
{ | ||
path = Path.Combine(context.WorkingDirectory, path); | ||
} | ||
|
||
if ((_filePathType == FilePathType.File) && !File.Exists(path)) | ||
{ | ||
return ValidationResult.Success; | ||
} | ||
|
||
if ((_filePathType == FilePathType.Directory) && !Directory.Exists(path)) | ||
{ | ||
return ValidationResult.Success; | ||
} | ||
|
||
if ((_filePathType == FilePathType.Any) && (!File.Exists(path) && !Directory.Exists(path))) | ||
{ | ||
return ValidationResult.Success; | ||
} | ||
|
||
return new ValidationResult(FormatErrorMessage(value as string)); | ||
} | ||
|
||
private static string GetDefaultErrorMessage(FilePathType filePathType) | ||
{ | ||
if (filePathType == FilePathType.File) | ||
{ | ||
return "The file '{0}' already exists."; | ||
} | ||
|
||
if (filePathType == FilePathType.Directory) | ||
{ | ||
return "The directory '{0}' already exists."; | ||
} | ||
|
||
return "The file path '{0}' already exists."; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
test/CommandLineUtils.Tests/FilePathNotExistsAttributeTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
// Copyright (c) Nate McMaster. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.ComponentModel.DataAnnotations; | ||
using System.IO; | ||
using McMaster.Extensions.CommandLineUtils.Internal; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace McMaster.Extensions.CommandLineUtils.Tests | ||
{ | ||
public class FilePathNotExistsAttributeTests | ||
{ | ||
private readonly ITestOutputHelper _output; | ||
|
||
public FilePathNotExistsAttributeTests(ITestOutputHelper output) | ||
{ | ||
_output = output; | ||
} | ||
|
||
private class App | ||
{ | ||
[Argument(0)] | ||
[FileOrDirectoryNotExists] | ||
public string File { get; } | ||
|
||
private void OnExecute() { } | ||
} | ||
|
||
[Theory] | ||
[InlineData("exists.txt")] | ||
public void ValidatesFilesMustNotExist(string filePath) | ||
{ | ||
var exists = Path.Combine(AppContext.BaseDirectory, filePath); | ||
if (!File.Exists(exists)) | ||
{ | ||
File.WriteAllText(exists, ""); | ||
} | ||
|
||
var app = new CommandLineApplication( | ||
new TestConsole(_output), | ||
AppContext.BaseDirectory, false); | ||
|
||
app.Argument("Files", "Files") | ||
.Accepts().NonExistingFileOrDirectory(); | ||
|
||
var result = app | ||
.Parse(filePath) | ||
.SelectedCommand | ||
.GetValidationResult(); | ||
|
||
Assert.NotEqual(ValidationResult.Success, result); | ||
Assert.Equal($"The file path '{filePath}' already exists.", result.ErrorMessage); | ||
|
||
var console = new TestConsole(_output); | ||
Assert.NotEqual(0, CommandLineApplication.Execute<App>(console, filePath)); | ||
} | ||
|
||
public static TheoryData<string> BadFilePaths | ||
=> new TheoryData<string> | ||
{ | ||
"notfound.txt", | ||
"\0", | ||
null, | ||
string.Empty, | ||
}; | ||
|
||
[Fact] | ||
public void ValidatesFilesRelativeToAppContext() | ||
{ | ||
var exists = Path.Combine(AppContext.BaseDirectory, "exists.txt"); | ||
if (!File.Exists(exists)) | ||
{ | ||
File.WriteAllText(exists, ""); | ||
} | ||
|
||
var appInBaseDir = new CommandLineApplication( | ||
new TestConsole(_output), | ||
AppContext.BaseDirectory, | ||
false); | ||
var notFoundDir = Path.Combine(AppContext.BaseDirectory, "notfound"); | ||
var appNotInBaseDir = new CommandLineApplication( | ||
new TestConsole(_output), | ||
notFoundDir, | ||
false); | ||
|
||
appInBaseDir.Argument("Files", "Files") | ||
.Accepts(v => v.NonExistingFileOrDirectory()); | ||
appNotInBaseDir.Argument("Files", "Files") | ||
.Accepts(v => v.NonExistingFileOrDirectory()); | ||
|
||
var fails = appInBaseDir | ||
.Parse("exists.txt") | ||
.SelectedCommand | ||
.GetValidationResult(); | ||
|
||
var success = appNotInBaseDir | ||
.Parse("exists.txt") | ||
.SelectedCommand | ||
.GetValidationResult(); | ||
|
||
Assert.NotEqual(ValidationResult.Success, fails); | ||
Assert.Equal("The file path 'exists.txt' already exists.", fails.ErrorMessage); | ||
|
||
Assert.Equal(ValidationResult.Success, success); | ||
|
||
var console = new TestConsole(_output); | ||
var context = new DefaultCommandLineContext(console, appInBaseDir.WorkingDirectory, new[] { "exists.txt" }); | ||
Assert.NotEqual(0, CommandLineApplication.Execute<App>(context)); | ||
|
||
context = new DefaultCommandLineContext(console, appNotInBaseDir.WorkingDirectory, new[] { "exists.txt" }); | ||
Assert.Equal(0, CommandLineApplication.Execute<App>(context)); | ||
} | ||
|
||
[Theory] | ||
[InlineData("./dir")] | ||
[InlineData("./")] | ||
[InlineData("../")] | ||
public void ValidatesDirectories(string dirPath) | ||
{ | ||
Directory.CreateDirectory(Path.Combine(AppContext.BaseDirectory, dirPath)); | ||
|
||
var context = new DefaultCommandLineContext( | ||
new TestConsole(_output), | ||
AppContext.BaseDirectory, | ||
new[] { dirPath }); | ||
|
||
Assert.NotEqual(0, CommandLineApplication.Execute<App>(context)); | ||
} | ||
|
||
private class OnlyDir | ||
{ | ||
[Argument(0)] | ||
[DirectoryNotExists] | ||
public string Dir { get; } | ||
|
||
private void OnExecute() { } | ||
} | ||
|
||
private class OnlyFile | ||
{ | ||
[Argument(0)] | ||
[FileNotExists] | ||
public string Path { get; } | ||
|
||
private void OnExecute() { } | ||
} | ||
|
||
[Theory] | ||
[InlineData("./dir")] | ||
[InlineData("./")] | ||
[InlineData("../")] | ||
public void ValidatesOnlyDirectories(string dirPath) | ||
{ | ||
Directory.CreateDirectory(Path.Combine(AppContext.BaseDirectory, dirPath)); | ||
|
||
var context = new DefaultCommandLineContext( | ||
new TestConsole(_output), | ||
AppContext.BaseDirectory, | ||
new[] { dirPath }); | ||
|
||
Assert.Equal(0, CommandLineApplication.Execute<OnlyFile>(context)); | ||
Assert.NotEqual(0, CommandLineApplication.Execute<OnlyDir>(context)); | ||
} | ||
|
||
[Fact] | ||
public void ValidatesOnlyFiles() | ||
{ | ||
var filePath = "exists.txt"; | ||
var fullPath = Path.Combine(AppContext.BaseDirectory, filePath); | ||
if (!File.Exists(fullPath)) | ||
{ | ||
File.WriteAllText(fullPath, ""); | ||
} | ||
|
||
var context = new DefaultCommandLineContext( | ||
new TestConsole(_output), | ||
AppContext.BaseDirectory, | ||
new[] { filePath }); | ||
|
||
Assert.NotEqual(0, CommandLineApplication.Execute<OnlyFile>(context)); | ||
Assert.Equal(0, CommandLineApplication.Execute<OnlyDir>(context)); | ||
} | ||
} | ||
} |