Skip to content

Commit

Permalink
Merge pull request #12 from calebjenkins/develop
Browse files Browse the repository at this point in the history
Adding IFileIO - Release
  • Loading branch information
calebjenkins committed Sep 2, 2023
2 parents 5f59b00 + a631136 commit d678e13
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 15 deletions.
42 changes: 30 additions & 12 deletions README.md
Expand Up @@ -21,7 +21,31 @@ Either command, from Package Manager Console or .NET Core CLI, will download and
### .NET 7
These extensions target .NET 6 and .NET 7. With .NET 7 the `Calebs.Extensions.Validators` include `EnumStringValidator<T>`. The ability for Attributes to support <T> was added with .NET 7.

# Extensions
# Helpers - not really extensions

## EnumStringValidator
Used for string properties in models that are supposed to conform to an enum value. The philosophy of my micro-services have been to be liberal in what you accept and conservative in what you send.

`Let's propose a scenario:` - you are recieving a message (model) that represents an `account` with a field `accountType`. Now, in this scenarios `accountType` could be `Standard, Silver or Gold` values. The easy way to restrict this is with en enum. The problem is - that if the incoming message doesn't exactly have one of those values (say `"AccountStatus":"Gold-Status"` is passed in instead of `"AccountStatus":"Gold"`), and if you are leveraging `Microsoft Web API` with model binding - then it is likely that model binding will fail and you will return a `400 - Bad Request` by default. This is the correct response, but you might want to log what was actually sent, or add additional context like an error message stating what field or fields were incorrect and what values are supported for that field. This makes for a much more developer friendly API.

So instead of having your model directly bind to an enum - and throw a Bad Request exception - you can accept a `string` in that field, and use the `Calebs.Extensions.Validators.EnumStringValidatorAttribute` to perform model validation and then decide how to handle the errors.

## SystemIO

### IFileIO
A collection of thin shims for common File IO opperations. Helpful when you want to mock out the File IO opperations for testing.
The interface `IFileIO` - every method is so slim that they each have a default implementaion. The default implementation `FileIO` jump implements the Interface, but doesn't need to implement any of the methods.
To use this helper - register `IFileIO` is your `DI` with `FileIO` as the implmentation. For unit tests use something like `nSubstitute` to mock out and intercept interactions through `IFileIO` methods.

- GetFiles(path, filter) // returns a string []
- DirectoryExists(path)
- GetDirectoryName(path)
- ReadAllText (path)
- FileExists(path)
- WriteAllLines(path, lines)
- DeleteFile(path)

# Extension Methods

## EnumExtensions
- ToList<D> -
Expand All @@ -31,18 +55,11 @@ These extensions target .NET 6 and .NET 7. With .NET 7 the `Calebs.Extensions.Va
- Parse<T>
- Parse <T, D>


## EnumStringValidator
Used for string properties in models that are supposed to conform to an enum value. The philosophy of my micro-services have been to be liberal in what you accept and conservative in what you send.

`Let's propose a scenario:` - you are recieving a message (model) that represents an `account` with a field `accountType`. Now, in this scenarios `accountType` could be `Standard, Silver or Gold` values. The easy way to restrict this is with en enum. The problem is - that if the incoming message doesn't exactly have one of those values (say `"AccountStatus":"Gold-Status"` is passed in instead of `"AccountStatus":"Gold"`), and if you are leveraging `Microsoft Web API` with model binding - then it is likely that model binding will fail and you will return a `400 - Bad Request` by default. This is the correct response, but you might want to log what was actually sent, or add additional context like an error message stating what field or fields were incorrect and what values are supported for that field. This makes for a much more developer friendly API.

So instead of having your model directly bind to an enum - and throw a Bad Request exception - you can accept a `string` in that field, and use the `Calebs.Extensions.Validators.EnumStringValidatorAttribute` to perform model validation and then decide how to handle the errors.

## StringExtensions
- IsNotNullOrEmpty
- IsNullOrEmpty
- Compare
- Compare
- string?.ValueOrEmpty()

## ListExtensions
- ToDelimitedList
Expand All @@ -63,5 +80,6 @@ Merges to `deveoper` automtically run all unit tests and publish a nuget package
Merges to `main` publish to nuget as a major release.

# Change Log
-1.1.0 - added IList.AddRange extension method
-
- 1.1.0 - added IList.AddRange extension method
- 1.2.0 - never published - only preview
- 1.3.0 - added IFileIO - an interface + implementation for making common filesystem opperations easier to test
6 changes: 6 additions & 0 deletions src/ExtensionTests/ExtensionTests.csproj
Expand Up @@ -23,4 +23,10 @@
<ProjectReference Include="..\Extensions\Extensions.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="resources\example.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
117 changes: 117 additions & 0 deletions src/ExtensionTests/FileIOTests.cs
@@ -0,0 +1,117 @@


using System.IO;
using System.Linq;

namespace ExtensionTests;

public class FileIOTests
{
IFileIO _files = new FileIO();

[Fact]
public void DirectoryExists_ShouldBeTrue()
{
var exists = _files.DirectoryExists("./");
exists.Should().BeTrue();
}

[Fact]
public void DirectoryDoesNotExists_ShouldBeFalse()
{
var exists = _files.DirectoryExists("./blah/");
exists.Should().BeFalse();
}

[Fact]
public void GetFilesCount_NoFilter()
{
var results = _files.GetFiles("./", "");
results.Count().Should().BeGreaterThan(10);
}

[Fact]
public void GetFilesCount_FilterToOneFile()
{
var results = _files.GetFiles("./", "Calebs.Extensions.dll");
results.Count().Should().Be(1);
}

[Fact]
public void GetDirectoryName()
{
var results = _files.GetDirectoryName("./Calebs.Extensions.dll");
results.Should().Be(".");
}

[Fact]
public void ReadAllText_Reads()
{
var exists = _files.FileExists("./resources/example.txt");
exists.Should().BeTrue();

var result = _files.ReadAllText("./resources/example.txt");
result.Should().StartWith("test");
}

[Fact]
public void FilesExists_DoesExist()
{
var result = _files.FileExists("./Calebs.Extensions.dll");
result.Should().BeTrue();
}

[Fact]
public void FilesExists_DoesNotExist()
{
var result = _files.FileExists("./blah");
result.Should().BeFalse();
}


//Need WriteAllLines
//Need DeleteFile

[Fact]
public void WriteAllLines_withDeleteFile()
{
// string settingsPath = Path.Combine(_hostingEnvironment.ContentRootPath, "AppData");
var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create);
folderPath.Length.Should().BeGreaterThan(1);

// Source File
var confirmSource = _files.FileExists("./resources/example.txt");
confirmSource.Should().BeTrue();

var source = _files.ReadAllText("./resources/example.txt");
var sourceList = new List<string>() { source };

// Target File - Confirm not already there
var target = Path.Combine(folderPath, "example.txt");
var targetExist = _files.FileExists(target);

if (targetExist)
{
_files.DeleteFile(target);
targetExist = _files.FileExists(target);
}

targetExist.Should().BeFalse();

// Do the thing - Write File
_files.WriteAllLines(target, sourceList);

// Confirm created and content matches
var resultFile = _files.FileExists(target);
resultFile.Should().BeTrue();

var result = _files.ReadAllText(target).Trim();

result.Should().Be(source);

// Clean Up
_files.DeleteFile(target);
var confirmDelete = _files.FileExists(target);
confirmDelete.Should().BeFalse();
}
}
2 changes: 0 additions & 2 deletions src/ExtensionTests/ListExtensionTests.cs
Expand Up @@ -30,7 +30,5 @@ public void ShouldCombineLists()

l2.Count.Should().Be(2);
}


}

26 changes: 26 additions & 0 deletions src/ExtensionTests/StringExtensionTests.cs
@@ -0,0 +1,26 @@

namespace ExtensionTests;

using Calebs.Extensions;

public class StringExtensionTests
{
[Fact]
public void ShouldReturnEmptyStringOnNull()
{
string? value = null;

Check warning on line 11 in src/ExtensionTests/StringExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 11 in src/ExtensionTests/StringExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 11 in src/ExtensionTests/StringExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 11 in src/ExtensionTests/StringExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
string v2 = value.ValueOrEmpty();

v2.Should().Be(String.Empty);
}

[Fact]
public void ShouldReturnValueIfNotNull()
{
string? value = "hello";

Check warning on line 20 in src/ExtensionTests/StringExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 20 in src/ExtensionTests/StringExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 20 in src/ExtensionTests/StringExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 20 in src/ExtensionTests/StringExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
string v2 = value.ValueOrEmpty();

v2.Should().Be(value);
}
}

Expand Up @@ -7,5 +7,6 @@
global using Xunit;
global using FluentAssertions;
global using Calebs.Extensions;
global using Calebs.Extensions.SystemIO;
global using ExtensionTests;
// global using ExtensionTests7;
3 changes: 3 additions & 0 deletions src/ExtensionTests/resources/example.txt
@@ -0,0 +1,3 @@
test
second line
third line multi words
2 changes: 1 addition & 1 deletion src/Extensions/Extensions.csproj
Expand Up @@ -10,7 +10,7 @@
<Configurations>Debug;Release;NET7</Configurations>
<RootNamespace>Calebs.Extensions</RootNamespace>
<AssemblyName>Calebs.Extensions</AssemblyName>
<Version>1.1.0</Version>
<Version>1.3.0</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Caleb Jenkins</Authors>
<Company>Caleb Jenkins</Company>
Expand Down
12 changes: 12 additions & 0 deletions src/Extensions/StringExtensions.cs
Expand Up @@ -22,5 +22,17 @@ public static bool Compare(this string string1, string string2)
returnValue = string1.Equals(string2, StringComparison.CurrentCultureIgnoreCase);
}
return returnValue;
}

#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public static string ValueOrEmpty(this string? value)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
if (value.IsNullOrEmpty())
{
return string.Empty;

}
return value;
}
}
6 changes: 6 additions & 0 deletions src/Extensions/SystemIO/FileIO.cs
@@ -0,0 +1,6 @@
namespace Calebs.Extensions.SystemIO;

public class FileIO : IFileIO
{

}
20 changes: 20 additions & 0 deletions src/Extensions/SystemIO/IFileIO.cs
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;


namespace Calebs.Extensions.SystemIO;

/// <summary>
/// A File IO interface and implementation of common IO uses - designed for edge testability
/// </summary>
public interface IFileIO
{
string[] GetFiles(string path, string filter = "") => Directory.GetFiles(path, filter);
bool DirectoryExists(string path) => Directory.Exists(path);
string GetDirectoryName(string path) => Path.GetDirectoryName(path);
string ReadAllText (string path) => File.ReadAllText(path);
bool FileExists(string path) => File.Exists(path);
void WriteAllLines(string path, IEnumerable<string> lines) => File.WriteAllLines(path, lines);
void DeleteFile(string path) => File.Delete(path);
}

0 comments on commit d678e13

Please sign in to comment.