diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 800c12f..65b3c8a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -12,7 +12,7 @@ on: - cron: '0 0 1,15 * *' env: - NET_VERSION: '7.x' + NET_VERSION: '9.x' jobs: analyze: @@ -44,7 +44,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -57,7 +57,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ?? Command-line programs to run using the OS shell. # ?? See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -70,4 +70,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 55f738d..44119ae 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -40,7 +40,7 @@ jobs: # Run Linter against code base # ################################ - name: Lint Code Base - uses: github/super-linter@v4 + uses: super-linter/super-linter@v7.2.0 env: LINTER_RULES_PATH: '.' EDITORCONFIG_FILE_NAME: '.editorconfig' diff --git a/samples/TinyHelpers.AspNetCore.Sample/TinyHelpers.AspNetCore.Sample.csproj b/samples/TinyHelpers.AspNetCore.Sample/TinyHelpers.AspNetCore.Sample.csproj index 693ba9c..18cbc87 100644 --- a/samples/TinyHelpers.AspNetCore.Sample/TinyHelpers.AspNetCore.Sample.csproj +++ b/samples/TinyHelpers.AspNetCore.Sample/TinyHelpers.AspNetCore.Sample.csproj @@ -8,7 +8,7 @@ - + diff --git a/samples/TinyHelpers.AspNetCore8.Sample/TinyHelpers.AspNetCore8.Sample.csproj b/samples/TinyHelpers.AspNetCore8.Sample/TinyHelpers.AspNetCore8.Sample.csproj index 3c033f9..e3e73de 100644 --- a/samples/TinyHelpers.AspNetCore8.Sample/TinyHelpers.AspNetCore8.Sample.csproj +++ b/samples/TinyHelpers.AspNetCore8.Sample/TinyHelpers.AspNetCore8.Sample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiOperationOptions.cs b/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiOperationOptions.cs new file mode 100644 index 0000000..1a7ef42 --- /dev/null +++ b/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiOperationOptions.cs @@ -0,0 +1,12 @@ +using Microsoft.OpenApi.Models; + +namespace TinyHelpers.AspNetCore.Swagger; + +public class OpenApiOperationOptions +{ + internal OpenApiOperationOptions() + { + } + + public IList Parameters { get; } = []; +} diff --git a/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiParametersOperationFilter.cs b/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiParametersOperationFilter.cs index 1077339..9efdf03 100644 --- a/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiParametersOperationFilter.cs +++ b/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiParametersOperationFilter.cs @@ -1,5 +1,4 @@ -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; namespace TinyHelpers.AspNetCore.Swagger; @@ -8,7 +7,7 @@ internal class OpenApiParametersOperationFilter(OpenApiOperationOptions options) { public void Apply(OpenApiOperation operation, OperationFilterContext context) { - if (options?.Parameters.Count > 0) + if (options.Parameters.Count > 0) { operation.Parameters ??= []; @@ -18,74 +17,4 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) } } } -} - -public class OpenApiOperationOptions -{ - internal OpenApiOperationOptions() - { - } - - public IList Parameters { get; init; } = []; -} - -public static class OpenApiSchemaHelper -{ - public static OpenApiSchema CreateStringSchema(string? defaultValue = null) - { - var schema = new OpenApiSchema - { - Type = "string", - Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null - }; - - return schema; - } - - public static OpenApiSchema CreateSchema(string type, string? format = null) - { - var schema = new OpenApiSchema - { - Type = type, - Format = format - }; - - return schema; - } - - public static OpenApiSchema CreateSchema(string type, string? format, TValue? defaultValue = null) where TValue : struct - { - var schema = new OpenApiSchema - { - Type = type, - Format = format, - Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null - }; - - return schema; - } - - public static OpenApiSchema CreateSchema(IEnumerable values, string? defaultValue = null) - { - var schema = new OpenApiSchema - { - Type = "string", - Enum = values.Select(v => new OpenApiString(v)).Cast().ToList(), - Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null - }; - - return schema; - } - - public static OpenApiSchema CreateSchema(TEnum? defaultValue = null) where TEnum : struct, Enum - { - var schema = new OpenApiSchema - { - Type = "string", - Enum = Enum.GetValues().Select(e => new OpenApiString(e.ToString())).Cast().ToList(), - Default = defaultValue.HasValue ? new OpenApiString(defaultValue.ToString()) : null - }; - - return schema; - } } \ No newline at end of file diff --git a/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiSchemaHelper.cs b/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiSchemaHelper.cs new file mode 100644 index 0000000..2e08a8c --- /dev/null +++ b/src/TinyHelpers.AspNetCore.Swashbuckle/OpenApiSchemaHelper.cs @@ -0,0 +1,65 @@ +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; + +namespace TinyHelpers.AspNetCore.Swagger; + +public static class OpenApiSchemaHelper +{ + public static OpenApiSchema CreateStringSchema(string? defaultValue = null) + { + var schema = new OpenApiSchema + { + Type = "string", + Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null + }; + + return schema; + } + + public static OpenApiSchema CreateSchema(string type, string? format = null) + { + var schema = new OpenApiSchema + { + Type = type, + Format = format + }; + + return schema; + } + + public static OpenApiSchema CreateSchema(string type, string? format, TValue? defaultValue = null) where TValue : struct + { + var schema = new OpenApiSchema + { + Type = type, + Format = format, + Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null + }; + + return schema; + } + + public static OpenApiSchema CreateSchema(IEnumerable values, string? defaultValue = null) + { + var schema = new OpenApiSchema + { + Type = "string", + Enum = values.Select(v => new OpenApiString(v)).Cast().ToList(), + Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null + }; + + return schema; + } + + public static OpenApiSchema CreateSchema(TEnum? defaultValue = null) where TEnum : struct, Enum + { + var schema = new OpenApiSchema + { + Type = "string", + Enum = Enum.GetValues().Select(e => new OpenApiString(e.ToString())).Cast().ToList(), + Default = defaultValue.HasValue ? new OpenApiString(defaultValue.ToString()) : null + }; + + return schema; + } +} \ No newline at end of file diff --git a/src/TinyHelpers.AspNetCore.Swashbuckle/TinyHelpers.AspNetCore.Swashbuckle.csproj b/src/TinyHelpers.AspNetCore.Swashbuckle/TinyHelpers.AspNetCore.Swashbuckle.csproj index 9785afa..7acf96f 100644 --- a/src/TinyHelpers.AspNetCore.Swashbuckle/TinyHelpers.AspNetCore.Swashbuckle.csproj +++ b/src/TinyHelpers.AspNetCore.Swashbuckle/TinyHelpers.AspNetCore.Swashbuckle.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/TinyHelpers.AspNetCore/OpenApi/OpenApiOperationOptions.cs b/src/TinyHelpers.AspNetCore/OpenApi/OpenApiOperationOptions.cs new file mode 100644 index 0000000..49dc3ff --- /dev/null +++ b/src/TinyHelpers.AspNetCore/OpenApi/OpenApiOperationOptions.cs @@ -0,0 +1,16 @@ +#if NET9_0_OR_GREATER + +using Microsoft.OpenApi.Models; + +namespace TinyHelpers.AspNetCore.OpenApi; + +public class OpenApiOperationOptions +{ + internal OpenApiOperationOptions() + { + } + + public IList Parameters { get; } = []; +} + +#endif \ No newline at end of file diff --git a/src/TinyHelpers.AspNetCore/OpenApi/OpenApiParametersOperationTransformer.cs b/src/TinyHelpers.AspNetCore/OpenApi/OpenApiParametersOperationTransformer.cs index 7c91b66..512b35b 100644 --- a/src/TinyHelpers.AspNetCore/OpenApi/OpenApiParametersOperationTransformer.cs +++ b/src/TinyHelpers.AspNetCore/OpenApi/OpenApiParametersOperationTransformer.cs @@ -1,7 +1,6 @@ #if NET9_0_OR_GREATER using Microsoft.AspNetCore.OpenApi; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; namespace TinyHelpers.AspNetCore.OpenApi; @@ -10,7 +9,7 @@ internal class OpenApiParametersOperationFilter(OpenApiOperationOptions options) { public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) { - if (options?.Parameters.Count > 0) + if (options.Parameters.Count > 0) { operation.Parameters ??= []; @@ -24,74 +23,4 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform } } -public class OpenApiOperationOptions -{ - internal OpenApiOperationOptions() - { - } - - public IList Parameters { get; init; } = []; -} - -public static class OpenApiSchemaHelper -{ - public static OpenApiSchema CreateStringSchema(string? defaultValue = null) - { - var schema = new OpenApiSchema - { - Type = "string", - Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null - }; - - return schema; - } - - public static OpenApiSchema CreateSchema(string type, string? format = null) - { - var schema = new OpenApiSchema - { - Type = type, - Format = format - }; - - return schema; - } - - public static OpenApiSchema CreateSchema(string type, string? format, TValue? defaultValue = null) where TValue : struct - { - var schema = new OpenApiSchema - { - Type = type, - Format = format, - Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null - }; - - return schema; - } - - public static OpenApiSchema CreateSchema(IEnumerable values, string? defaultValue = null) - { - var schema = new OpenApiSchema - { - Type = "string", - Enum = values.Select(v => new OpenApiString(v)).Cast().ToList(), - Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null - }; - - return schema; - } - - public static OpenApiSchema CreateSchema(TEnum? defaultValue = null) where TEnum : struct, Enum - { - var schema = new OpenApiSchema - { - Type = "string", - Enum = Enum.GetValues().Select(e => new OpenApiString(e.ToString())).Cast().ToList(), - Default = defaultValue.HasValue ? new OpenApiString(defaultValue.ToString()) : null - }; - - return schema; - } -} - #endif \ No newline at end of file diff --git a/src/TinyHelpers.AspNetCore/OpenApi/OpenApiSchemaHelper.cs b/src/TinyHelpers.AspNetCore/OpenApi/OpenApiSchemaHelper.cs new file mode 100644 index 0000000..ca0073b --- /dev/null +++ b/src/TinyHelpers.AspNetCore/OpenApi/OpenApiSchemaHelper.cs @@ -0,0 +1,69 @@ +#if NET9_0_OR_GREATER + +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; + +namespace TinyHelpers.AspNetCore.OpenApi; + +public static class OpenApiSchemaHelper +{ + public static OpenApiSchema CreateStringSchema(string? defaultValue = null) + { + var schema = new OpenApiSchema + { + Type = "string", + Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null + }; + + return schema; + } + + public static OpenApiSchema CreateSchema(string type, string? format = null) + { + var schema = new OpenApiSchema + { + Type = type, + Format = format + }; + + return schema; + } + + public static OpenApiSchema CreateSchema(string type, string? format, TValue? defaultValue = null) where TValue : struct + { + var schema = new OpenApiSchema + { + Type = type, + Format = format, + Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null + }; + + return schema; + } + + public static OpenApiSchema CreateSchema(IEnumerable values, string? defaultValue = null) + { + var schema = new OpenApiSchema + { + Type = "string", + Enum = values.Select(v => new OpenApiString(v)).Cast().ToList(), + Default = defaultValue is not null ? new OpenApiString(defaultValue.ToString()) : null + }; + + return schema; + } + + public static OpenApiSchema CreateSchema(TEnum? defaultValue = null) where TEnum : struct, Enum + { + var schema = new OpenApiSchema + { + Type = "string", + Enum = Enum.GetValues().Select(e => new OpenApiString(e.ToString())).Cast().ToList(), + Default = defaultValue.HasValue ? new OpenApiString(defaultValue.ToString()) : null + }; + + return schema; + } +} + +#endif \ No newline at end of file diff --git a/src/TinyHelpers.Dapper/TypeHandlers/TimeSpanTypeHandler.cs b/src/TinyHelpers.Dapper/TypeHandlers/TimeSpanTypeHandler.cs new file mode 100644 index 0000000..dadef5e --- /dev/null +++ b/src/TinyHelpers.Dapper/TypeHandlers/TimeSpanTypeHandler.cs @@ -0,0 +1,30 @@ +using System.Data; +using System.Globalization; +using Dapper; + +namespace TinyHelpers.Dapper.TypeHandlers; + +public class TimeSpanTypeHandler : SqlMapper.TypeHandler +{ + public override TimeSpan Parse(object value) + { + return value switch + { + null => TimeSpan.Zero, + string stringValue => TimeSpan.Parse(stringValue, CultureInfo.InvariantCulture), + long longValue => TimeSpan.FromTicks(longValue), + int intValue => TimeSpan.FromSeconds(intValue), + double doubleValue => TimeSpan.FromSeconds(doubleValue), + _ => throw new ArgumentException($"Unable to convert {value.GetType()} to {nameof(TimeSpan)}"), + }; + } + + public override void SetValue(IDbDataParameter parameter, TimeSpan value) + { + parameter.Value = value.Ticks; + parameter.DbType = DbType.Int64; + } + + public static void Configure() + => SqlMapper.AddTypeHandler(new TimeSpanTypeHandler()); +} diff --git a/src/TinyHelpers/Threading/AsyncLock.cs b/src/TinyHelpers/Threading/AsyncLock.cs index ee1829c..f219ec1 100644 --- a/src/TinyHelpers/Threading/AsyncLock.cs +++ b/src/TinyHelpers/Threading/AsyncLock.cs @@ -8,7 +8,7 @@ public class AsyncLock : IDisposable private readonly SemaphoreSlim semaphoreSlim = new(1, 1); /// - /// Asyncronously waits for the lock to become available. + /// Asynchronously waits for the lock to become available. /// /// A token that can be used to request cancellation of the asynchronous operation. /// An awaitable task. @@ -18,6 +18,38 @@ public async Task LockAsync(CancellationToken cancellationToken = def return this; } + /// + /// Asynchronously waits for the lock to become available, with a specific timeout in milliseconds. + /// + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// A token that can be used to request cancellation of the asynchronous operation. + /// An awaitable task that returns a indicating whether the lock was acquired or not and the lock itself. + /// + /// is a negative number other than -1 milliseconds, which represents + /// an infinite timeout. + /// + public async Task LockAsync(int timeoutInMilliseconds, CancellationToken cancellationToken = default) + { + var isOwned = await semaphoreSlim.WaitAsync(timeoutInMilliseconds, cancellationToken).ConfigureAwait(false); + return new LockResult(isOwned, isOwned ? this : null); + } + + /// + /// Asynchronously waits for the lock to become available, using a to specify a timeout. + /// + /// A that represents the maximum time to wait + /// A token that can be used to request cancellation of the asynchronous operation. + /// An awaitable task that returns a indicating whether the lock was acquired or not and the lock itself. + /// + /// is a negative number other than -1 milliseconds, which represents + /// an infinite timeout or timeout is greater than . + /// + public async Task LockAsync(TimeSpan timeout, CancellationToken cancellationToken = default) + { + var isOwned = await semaphoreSlim.WaitAsync(timeout, cancellationToken).ConfigureAwait(false); + return new LockResult(isOwned, isOwned ? this : null); + } + /// /// Releases the lock. /// diff --git a/src/TinyHelpers/Threading/LockResult.cs b/src/TinyHelpers/Threading/LockResult.cs new file mode 100644 index 0000000..3a3305f --- /dev/null +++ b/src/TinyHelpers/Threading/LockResult.cs @@ -0,0 +1,30 @@ +namespace TinyHelpers.Threading; + +/// +/// Represents the result of an asynchronous lock operation. +/// +public readonly struct LockResult +{ + /// + /// Gets the object if succesfully acquired. + /// + public AsyncLock? AsyncLock { get; } + + /// + /// Gets a boolean indicating if the lock was acquired or not. + /// + public bool IsOwned { get; } + + internal LockResult(bool isOwned, AsyncLock? asyncLock) + { + (IsOwned, AsyncLock) = (isOwned, asyncLock); + } + + /// + /// Deconstruct the object. + /// + /// The boolean indicating if the lock was acquired or not. + /// The object. + public void Deconstruct(out bool isOwned, out AsyncLock? asyncLock) + => (isOwned, asyncLock) = (IsOwned, AsyncLock); +}