Skip to content

Commit

Permalink
Merge pull request #25 from calebjenkins/develop
Browse files Browse the repository at this point in the history
version bump - added IntExtensions and expanded IFileIO
  • Loading branch information
calebjenkins authored Nov 11, 2024
2 parents 65108d2 + aa98c20 commit f45ae19
Show file tree
Hide file tree
Showing 14 changed files with 368 additions and 158 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.cs]

# CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
dotnet_diagnostic.CS8632.severity = none
1 change: 1 addition & 0 deletions Calebs.Extensions.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtensionTests", "src\Exten
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4316AA9F-ECF9-489D-B86B-E643AD24C6F1}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
LICENSE = LICENSE
README.md = README.md
Expand Down
172 changes: 89 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,99 +1,105 @@
[![.github/workflows/dev-ci.yml](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/dev-ci.yml/badge.svg?branch=develop)](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/dev-ci.yml)
[![.github/workflows/main-publish.yml](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/main-publish.yml/badge.svg?branch=main)](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/main-publish.yml)
[![NuGet](https://img.shields.io/nuget/dt/calebs.extensions.svg)](https://www.nuget.org/packages/calebs.extensions)
[![NuGet](https://img.shields.io/nuget/vpre/calebs.extensions.svg)](https://www.nuget.org/packages/calebs.extensions)
[![.github/workflows/ci.yml](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/ci.yml/badge.svg)](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/ci.yml)

![Extensions Logo](https://raw.githubusercontent.com/calebjenkins/Calebs.Extensions/develop/Assets/logo.png)
# Calebs.Extensions
Useful extension methods and attributes for working with enums, strings and lists. The majority of these extensions were born out of working with various models while building micro services.


### Installing Calebs.Extensions

You should install [Extensions with NuGet](https://www.nuget.org/packages/Calebs.Extensions):

Install-Package Calebs.Extensions

Or via the .NET Core command line interface:

dotnet add package Calebs.Extensions

Either command, from Package Manager Console or .NET Core CLI, will download and install Calebs.Extensions and all required dependencies.

### .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.

# 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.

[![.github/workflows/dev-ci.yml](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/dev-ci.yml/badge.svg?branch=develop)](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/dev-ci.yml)
[![.github/workflows/main-publish.yml](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/main-publish.yml/badge.svg?branch=main)](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/main-publish.yml)
[![NuGet](https://img.shields.io/nuget/dt/calebs.extensions.svg)](https://www.nuget.org/packages/calebs.extensions)
[![NuGet](https://img.shields.io/nuget/vpre/calebs.extensions.svg)](https://www.nuget.org/packages/calebs.extensions)
[![.github/workflows/ci.yml](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/ci.yml/badge.svg)](https://github.com/calebjenkins/Calebs.Extensions/actions/workflows/ci.yml)

![Extensions Logo](https://raw.githubusercontent.com/calebjenkins/Calebs.Extensions/develop/Assets/logo.png)
# Calebs.Extensions
Useful extension methods and attributes for working with enums, strings and lists. The majority of these extensions were born out of working with various models while building micro services.


### Installing Calebs.Extensions

You should install [Extensions with NuGet](https://www.nuget.org/packages/Calebs.Extensions):

Install-Package Calebs.Extensions

Or via the .NET Core command line interface:

dotnet add package Calebs.Extensions

Either command, from Package Manager Console or .NET Core CLI, will download and install Calebs.Extensions and all required dependencies.

### .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.

# 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 []
- GetFileInfo(path)
- GetFileAttributes(path)
- DirectoryExists(path)
- GetDirectoryName(path)
- ReadAllText (path)
- FileExists(path)
- WriteAllLines(path, lines)
- DeleteFile(path)
- CreateDirectory (path)
- DeleteDirectory (path)

# Extension Methods

## EnumExtensions
- ToList<D> -
- ToList(Type)
- Description (enum)
- Description<ToDesc>(Enum)
- Parse<T>
- DeleteDirectory (path)

# Extension Methods

## EnumExtensions
- ToList<D> -
- ToList(Type)
- Description (enum)
- Description<ToDesc>(Enum)
- Parse<T>
- Parse <T, D>


## StringExtensions
- IsNotNullOrEmpty
- IsNullOrEmpty

## IntExtensions
- RandomText // For integers between 1 and 1000 -- returns a random string with that length


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

## ObjectExtensions
- ToSafeString()

## ListExtensions
- ToDelimitedList
- ToUpper
- string?.ValueOrEmpty()

## ObjectExtensions
- ToSafeString()

## ListExtensions
- ToDelimitedList
- ToUpper
- AddRange - `IList<T>.AddRange(IList<T>)`
- AddUnlessBlank IList<string>.AddUnlessBlank(string)

## JsonExtensions
For both of these extension methods I'm using the `Newtonsoft.Json` library. I'm planning on migrating to `System.Text.Json` as soon as it is viable. Right now, Newtonsoft is easeir to serialize enums to thier ToString() value rather than index, and to deserialize the same. For example, by default - an enum is serialized to the index of the value. So an enum with (High, Med, Low) values would otherwise be serialiezed to a 2, instead of to "Med". Serializing to "Med" is my prefered behavior. I will continue to evailuate `System.Text.Json` against these unit tests and most likely migrate at some point.
- ToJson<T>
- FromJson

## Versioning
This package follow semantic versioning as much as possible.

# Contributions
Please submit PR's to the `develop` branch.
Merges to `deveoper` automtically run all unit tests and publish a nuget package with the postfix `-ci-build_number`
Merges to `main` publish to nuget as a major release.

# Change Log
- 1.1.0 - added IList.AddRange extension method
- AddUnlessBlank IList<string>.AddUnlessBlank(string)

## JsonExtensions
For both of these extension methods I'm using the `Newtonsoft.Json` library. I'm planning on migrating to `System.Text.Json` as soon as it is viable. Right now, Newtonsoft is easeir to serialize enums to thier ToString() value rather than index, and to deserialize the same. For example, by default - an enum is serialized to the index of the value. So an enum with (High, Med, Low) values would otherwise be serialiezed to a 2, instead of to "Med". Serializing to "Med" is my prefered behavior. I will continue to evailuate `System.Text.Json` against these unit tests and most likely migrate at some point.
- ToJson<T>
- FromJson

## Versioning
This package follow semantic versioning as much as possible.

# Contributions
Please submit PR's to the `develop` branch.
Merges to `deveoper` automtically run all unit tests and publish a nuget package with the postfix `-ci-build_number`
Merges to `main` publish to nuget as a major release.

# Change Log
- 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
- 1.3.1 - suppressed some test warnings and updated the GH workflows
- 1.4.0 - added `CreatedDiretory` and `DeleteDirectory` to `IFileIO`
- 1.4.0 - added `CreatedDiretory` and `DeleteDirectory` to `IFileIO`
- 1.5.0 - Added package logo and `ToSafeString` for `ObjectExtensions`
- 1.6.0 - Added AddUnlessBlank to list extensions
- 1.7.0 - Added `IntExtensions` and expanded `IFileIO`
10 changes: 5 additions & 5 deletions src/ExtensionTests/ExtensionTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-preview-24080-01" />
<PackageReference Include="FluentAssertions" Version="7.0.0-alpha.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
21 changes: 18 additions & 3 deletions src/ExtensionTests/FileIOTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ public class FileIOTests
public void DirectoryExists_ShouldBeTrue()
{
var exists = _files.DirectoryExists("./");
exists.Should().BeTrue();
exists.Should().BeTrue();
}

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

Expand All @@ -37,6 +38,20 @@ public void GetFilesCount_FilterToOneFile()
results.Count().Should().Be(1);
}

[Fact]
public void GetFileInfo()
{
var results = _files.GetFileInfo("./Calebs.Extensions.dll");
results.CreationTime.Ticks.Should().BeLessThan(DateTime.Now.Ticks);
}

[Fact]
public void GetAttributes()
{
var results = _files.GetFileAttributes("./Calebs.Extensions.dll");
results.Description().Count().Should().BeGreaterThan(0);
}

[Fact]
public void GetDirectoryName()
{
Expand Down Expand Up @@ -64,7 +79,7 @@ public void FilesExists_DoesExist()
[Fact]
public void FilesExists_DoesNotExist()
{
var result = _files.FileExists("./blah");
var result = _files.FileExists("./blah.txt");
result.Should().BeFalse();
}

Expand Down
46 changes: 46 additions & 0 deletions src/ExtensionTests/IntExtensionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

namespace ExtensionTests;

using Calebs.Extensions;

public class IntExtensionTests
{
[Theory]
[InlineData(1)]
[InlineData(5)]
[InlineData(10)]
[InlineData(32)]
public void RandomTextUnder32(int value)
{
value.RandomText().Length.Should().Be(value);
}

[Theory]
[InlineData(33)]
[InlineData(100)]
[InlineData(1000)]
public void RandomTextOver32_Below1000_LongRunning(int value)
{
value.RandomText().Length.Should().Be(value);
}

[Theory]
[InlineData (0)]
[InlineData(-1)]
[InlineData(-10)]
[InlineData (1001)]
[InlineData(5000)]
public void RandomTextBelowZero_or_Over1000_Should_Thrown_Exception(int value)
{
try
{
var _ = value.RandomText();
}
catch (Exception ex)
{
ex.Should().BeOfType<ArgumentOutOfRangeException>();
}
}


}
7 changes: 2 additions & 5 deletions src/ExtensionTests/StringExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ public class StringExtensionTests
[Fact]
public void ShouldReturnEmptyStringOnNull()
{
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
string? value = null;
#pragma warning restore CS8632 // 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);
Expand All @@ -19,11 +17,10 @@ public void ShouldReturnEmptyStringOnNull()
[Fact]
public void ShouldReturnValueIfNotNull()
{
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

string? value = "hello";
#pragma warning restore CS8632 // 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);
}
}
}
10 changes: 5 additions & 5 deletions src/ExtensionTests7/ExtensionTests7.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="FluentAssertions" Version="7.0.0-alpha.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-preview-24080-01" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
Loading

0 comments on commit f45ae19

Please sign in to comment.