├── .editorconfig ├── .filenesting.json ├── .github ├── FUNDING.yml └── workflows │ └── dotnet-core.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── TypeCache.sln ├── TypeCash.png ├── src ├── TypeCache.GraphQL │ ├── Attributes │ │ ├── GraphQLDeprecationReasonAttribute.cs │ │ ├── GraphQLDescriptionAttribute.cs │ │ ├── GraphQLIgnoreAttribute.cs │ │ ├── GraphQLInputNameAttribute.cs │ │ ├── GraphQLMutationAttribute.cs │ │ ├── GraphQLNameAttribute.cs │ │ ├── GraphQLQueryAttribute.cs │ │ ├── GraphQLSubscriptionAttribute.cs │ │ └── GraphQLTypeAttribute.cs │ ├── Converters │ │ ├── GraphQLExecutionErrorJsonConverter.cs │ │ └── GraphQLExecutionResultJsonConverter.cs │ ├── Data │ │ ├── Connection.cs │ │ ├── Edge.cs │ │ └── PageInfo.cs │ ├── Extensions │ │ ├── ApplicationBuilderExtensions.cs │ │ ├── AttributeExtensions.cs │ │ ├── EnumExtensions.cs │ │ ├── GraphQLExtensions.cs │ │ ├── QueryArgumentsExtensions.cs │ │ ├── ResolveFieldContextExtensions.cs │ │ ├── SchemaExtensions.cs │ │ ├── ServiceCollectionExtensions.cs │ │ └── TypeScriptExtensions.cs │ ├── GlobalUsings.cs │ ├── Listeners │ │ └── DefaultDocumentExecutionListener.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Resolvers │ │ ├── BatchCollectionFieldResolver.cs │ │ ├── BatchItemFieldResolver.cs │ │ ├── CustomFieldResolver.cs │ │ ├── CustomSourceStreamResolver.cs │ │ ├── DatabaseSchemaFieldResolver.cs │ │ ├── FieldResolver.cs │ │ ├── ItemFieldResolver.cs │ │ ├── MethodFieldResolver.cs │ │ ├── MethodSourceStreamResolver.cs │ │ ├── ObservableFromAsyncEnumerable.cs │ │ ├── PropertyFieldResolver.cs │ │ ├── SourceStreamResolver.cs │ │ ├── SqlApiCallFieldResolver.cs │ │ ├── SqlApiDeleteFieldResolver.cs │ │ ├── SqlApiInsertFieldResolver.cs │ │ ├── SqlApiSelectFieldResolver.cs │ │ ├── SqlApiUpdateFieldResolver.cs │ │ └── StringCase.cs │ ├── SqlApi │ │ ├── DataResponse.cs │ │ ├── OutputResponse.cs │ │ ├── Parameter.cs │ │ └── SelectResponse.cs │ ├── TypeCache.GraphQL.csproj │ ├── Types │ │ ├── ConfigureSchema.cs │ │ ├── EnumGraphType.cs │ │ ├── HashIdGraphType.cs │ │ ├── InputGraphType.cs │ │ ├── InterfaceGraphType.cs │ │ └── OutputGraphType.cs │ └── Web │ │ ├── GraphQLMiddleware.cs │ │ ├── GraphQLRequest.cs │ │ └── GraphQLSerializer.cs ├── TypeCache.Web │ ├── Attributes │ │ ├── RequireClaimAttribute.cs │ │ └── RequireHeaderAttribute.cs │ ├── Extensions │ │ ├── ApplicationBuilderExtensions.cs │ │ ├── AuthorizationHandlerContextExtensions.cs │ │ ├── AuthorizationOptionsExtensions.cs │ │ ├── ClaimsPrincipalExtensions.cs │ │ ├── EndpointRouteBuilderExtensions.API.cs │ │ ├── EndpointRouteBuilderExtensions.SQL.cs │ │ ├── EndpointRouteBuilderExtensions.cs │ │ ├── HttpContextExtensions.cs │ │ ├── HttpRequestExtensions.cs │ │ └── ServiceCollectionExtensions.cs │ ├── Filters │ │ ├── SqlApiProcedureEndpointFilter.cs │ │ ├── SqlApiSchemaEndpointFilter.cs │ │ └── SqlApiTableEndpointFilter.cs │ ├── GlobalUsings.cs │ ├── Handlers │ │ ├── ClaimAuthorizationHandler.cs │ │ ├── HeaderAuthorizationHandler.cs │ │ ├── SelectParameter.cs │ │ ├── SqlApiHandler.cs │ │ └── SqlHandler.cs │ ├── Mediation │ │ ├── HttpClientRequest.cs │ │ ├── HttpClientRule.cs │ │ └── HttpClientValidationRule.cs │ ├── Middleware │ │ └── SqlApiErrorHandlerMiddleware.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Requirements │ │ ├── ClaimAuthorizationRequirement.cs │ │ └── HeaderAuthorizationRequirement.cs │ └── TypeCache.Web.csproj └── TypeCache │ ├── Attributes │ ├── MapAttribute.cs │ ├── MapIgnoreAttribute.cs │ ├── ScopedAttribute.cs │ ├── ServiceLifetimeAttribute.cs │ ├── SingletonAttribute.cs │ ├── SqlApiAction.cs │ ├── SqlApiAttribute.cs │ └── TransientAttribute.cs │ ├── Converters │ ├── BigIntegerJsonConverter.cs │ ├── DataRowJsonConverter.cs │ ├── DataSetJsonConverter.cs │ ├── DataTableJsonConverter.cs │ ├── DataViewJsonConverter.cs │ ├── DictionaryJsonConverter.cs │ ├── FieldJsonConverter.cs │ ├── PropertyJsonConverter.cs │ ├── ReadOnlyDictionaryJsonConverter.cs │ ├── StringValuesJsonConverter.cs │ └── ValueJsonConverter.cs │ ├── Data │ ├── ColumnSchema.cs │ ├── ComparisonOperator.cs │ ├── DataSource.cs │ ├── DataSourceType.cs │ ├── DatabaseObjectType.cs │ ├── Extensions │ │ ├── DbCommandExtensions.cs │ │ ├── DbConnectionExtensions.cs │ │ ├── DbDataReaderExtensions.cs │ │ ├── DbProviderFactoryExtensions.cs │ │ ├── EnumExtensions.cs │ │ ├── SqlCommandExtensions.cs │ │ ├── SqlExtensions.cs │ │ └── StringBuilderExtensions.cs │ ├── IDataSource.cs │ ├── LogicalOperator.cs │ ├── Mediation │ │ ├── SqlDataSetRule.cs │ │ ├── SqlDataTableRule.cs │ │ ├── SqlExecuteRule.cs │ │ ├── SqlResultJsonRule.cs │ │ ├── SqlResultSetJsonRule.cs │ │ ├── SqlResultsRule.cs │ │ └── SqlScalarRule.cs │ ├── ObjectSchema.cs │ ├── ParameterSchema.cs │ ├── SchemaCollection.cs │ ├── SchemaColumn.cs │ ├── SelectQuery.cs │ ├── Sort.cs │ └── SqlCommand.cs │ ├── Extensions │ ├── ActionExtensions.cs │ ├── ArrayExtensions.cs │ ├── AssemblyExtensions.cs │ ├── AsyncEnumeratorExtensions.cs │ ├── BooleanExtensions.cs │ ├── CharExtensions.cs │ ├── ComparableExtensions.cs │ ├── ComparerExtensions.cs │ ├── CsvExtensions.cs │ ├── DateTimeExtensions.cs │ ├── DictionaryExtensions.cs │ ├── EnumExtensions.CollectionType.cs │ ├── EnumExtensions.ScalarType.cs │ ├── EnumExtensions.cs │ ├── EnumerableExtensions.cs │ ├── EnumeratorExtensions.cs │ ├── EqualityComparerExtensions.cs │ ├── EquatableExtensions.cs │ ├── ExpressionExtensions.ArrayExpressionBuilder.cs │ ├── ExpressionExtensions.BinaryOperator.cs │ ├── ExpressionExtensions.LabelTarget.cs │ ├── ExpressionExtensions.UnaryOperator.cs │ ├── ExpressionExtensions.cs │ ├── FuncExtensions.cs │ ├── GlobalExtensions.cs │ ├── IndexExtensions.cs │ ├── JsonExtensions.cs │ ├── ListExtensions.cs │ ├── LoggerExtensions.cs │ ├── MapExtensions.cs │ ├── NumericExtensions.Double.cs │ ├── NumericExtensions.FloatingPoint.cs │ ├── NumericExtensions.FloatingPointIeee754.cs │ ├── NumericExtensions.Int32.cs │ ├── NumericExtensions.Number.cs │ ├── NumericExtensions.NumberBase.cs │ ├── NumericExtensions.cs │ ├── ObjectExtensions.cs │ ├── ParallelExtensions.cs │ ├── RangeExtensions.cs │ ├── ReadOnlySpanExtensions.cs │ ├── ReflectionExtensions.CollectionType.cs │ ├── ReflectionExtensions.ConstructorInfo.cs │ ├── ReflectionExtensions.Delegate.cs │ ├── ReflectionExtensions.FieldInfo.cs │ ├── ReflectionExtensions.Handles.cs │ ├── ReflectionExtensions.MemberInfo.cs │ ├── ReflectionExtensions.MethodBase.cs │ ├── ReflectionExtensions.MethodInfo.cs │ ├── ReflectionExtensions.ObjectType.cs │ ├── ReflectionExtensions.ParameterInfo.cs │ ├── ReflectionExtensions.PropertyInfo.cs │ ├── ReflectionExtensions.ScalarType.cs │ ├── ReflectionExtensions.Type.cs │ ├── ReflectionExtensions.cs │ ├── RuneExtensions.cs │ ├── ServiceCollectionExtensions.cs │ ├── SetExtensions.cs │ ├── SpanExtensions.cs │ ├── StringBuilderExtensions.cs │ ├── StringExtensions.cs │ └── TaskExtensions.cs │ ├── GlobalUsings.cs │ ├── Mediation │ ├── CustomAfterRule.cs │ ├── CustomRule.cs │ ├── CustomValidationRule.cs │ ├── IAfterRule.cs │ ├── IMediator.cs │ ├── IRequest.cs │ ├── IRule.cs │ ├── IValidationRule.cs │ ├── Mediator.cs │ └── RuleFactory.cs │ ├── README.md │ ├── TypeCache.csproj │ └── Utilities │ ├── Array.cs │ ├── CustomObservable.cs │ ├── CustomStringWriter.cs │ ├── Enum.cs │ ├── EnumComparer.cs │ ├── Enumerable.cs │ ├── EventHandler.cs │ ├── HashMaker.cs │ ├── IHashMaker.cs │ ├── LambdaFactory.cs │ ├── LazyDictionary.cs │ ├── ObservableAdapter.cs │ ├── ObserverAdapter.cs │ ├── Sequence.cs │ ├── Singleton.cs │ ├── TypeStore.cs │ ├── ValueBox.cs │ └── ValueConverter.cs └── tests ├── TypeCache.GraphQL.TestApp ├── Models │ └── Detail.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Tables │ ├── Person.cs │ ├── Product.cs │ └── WorkOrder.cs ├── TypeCache.GraphQL.TestApp.csproj └── appsettings.json └── TypeCache.Tests ├── AssemblyInfo.cs ├── Converters └── JsonConverterTests.cs ├── Data └── Extensions │ ├── DbDataReaderExtensions.cs │ └── SqlExtensions.cs ├── Extensions ├── ActionExtensions.cs ├── ArrayExtensions.cs ├── BooleanExtensions.cs ├── CharExtensions.cs ├── ComparableExtensions.cs ├── ComparerExtensions.cs ├── DateTimeExtensions.cs ├── DictionaryExtensions.cs ├── EnumExtensions.cs ├── EnumerableExtensions.cs ├── EnumeratorExtensions.cs ├── EqualityComparerExtensions.cs ├── EquatableExtensions.cs ├── FuncExtensions.cs ├── GlobalExtensions.cs ├── IndexExtensions.cs ├── JsonExtensions.cs ├── ListExtensions.cs ├── MapExtensions.cs ├── MathExtensions.cs ├── NumericExtensions.cs ├── ObjectExtensions.cs ├── RangeExtensions.cs ├── ReadOnlySpanExtensions.cs ├── ReflectionExtensions.cs ├── RuneExtensions.cs ├── StringExtensions.cs └── ValueExtensions.cs ├── TypeCache.Tests.csproj └── Utilities ├── EnumTests.cs ├── EventOfTests.cs └── HashMakerTests.cs /.filenesting.json: -------------------------------------------------------------------------------- 1 | { 2 | "help":"https://go.microsoft.com/fwlink/?linkid=866610" 3 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: sam987883 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.com/paypalme/sam987883'] 13 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET9 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET9 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 9.0.x 20 | - name: Install dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --configuration Release --no-restore 24 | - name: Test 25 | run: dotnet test --no-restore --verbosity normal 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Samuel Abraham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeCache 2 | TypeCache is a fast alternative to System.Reflection. 3 | -------------------------------------------------------------------------------- /TypeCash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam987883/TypeCache/db8844e4a13fdd50420a20791cd60f27314f863e/TypeCash.png -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLDeprecationReasonAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.GraphQL.Attributes; 6 | 7 | /// 8 | /// GraphQL
9 | /// Sets the deprecation reason of the object type, object property, enum type, enum field, endpoint or endpoint parameter. 10 | ///
11 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Struct)] 12 | public sealed class GraphQLDeprecationReasonAttribute(string deprecationReason) : Attribute 13 | { 14 | public string DeprecationReason { get; } = deprecationReason; 15 | } 16 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLDescriptionAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.GraphQL.Attributes; 6 | 7 | /// 8 | /// GraphQL
9 | /// Sets the description of the object type, object property, enum type, enum field, endpoint or endpoint parameter. 10 | ///
11 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Struct)] 12 | public sealed class GraphQLDescriptionAttribute(string description) : Attribute 13 | { 14 | public string Description { get; } = description; 15 | } 16 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLIgnoreAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.GraphQL.Attributes; 4 | 5 | /// 6 | /// GraphQL
7 | /// Ignore a parameter, enum field or property. 8 | ///
9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)] 10 | public sealed class GraphQLIgnoreAttribute : Attribute 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLInputNameAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.GraphQL.Attributes; 6 | 7 | /// 8 | /// GraphQL
9 | /// Sets the name of the input type. 10 | ///
11 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] 12 | public sealed class GraphQLInputNameAttribute(string name) : Attribute 13 | { 14 | public string Name { get; } = name; 15 | } 16 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLMutationAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.GraphQL.Attributes; 4 | 5 | /// 6 | /// GraphQL
7 | /// Marks a method to be used as a root level Mutation endpoint. 8 | ///
9 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 10 | public sealed class GraphQLMutationAttribute : Attribute 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLNameAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.GraphQL.Attributes; 6 | 7 | /// 8 | /// GraphQL
9 | /// Sets the name of the object type, object property, enum type, enum field, endpoint or endpoint parameter. 10 | ///
11 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Struct)] 12 | public sealed class GraphQLNameAttribute(string name) : Attribute 13 | { 14 | public string Name { get; } = name; 15 | } 16 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLQueryAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.GraphQL.Attributes; 4 | 5 | /// 6 | /// GraphQL
7 | /// Marks a method to be used as a root level Query endpoint. 8 | ///
9 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 10 | public sealed class GraphQLQueryAttribute : Attribute 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLSubscriptionAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.GraphQL.Attributes; 4 | 5 | /// 6 | /// GraphQL
7 | /// Marks a method to be used as a Subscriber endpoint. 8 | ///
9 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 10 | public sealed class GraphQLSubscriptionAttribute : Attribute 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Attributes/GraphQLTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL; 4 | using GraphQL.Types; 5 | 6 | namespace TypeCache.GraphQL.Attributes; 7 | 8 | public abstract class GraphQLTypeAttribute : Attribute 9 | { 10 | } 11 | 12 | /// 13 | /// GraphQL 14 | /// If the parameter a type of or , then it will not show up in the endpoint-
15 | /// Instead it will be injected with the instance of or . 16 | ///
17 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = false)] 18 | public sealed class GraphQLTypeAttribute : GraphQLTypeAttribute 19 | where T : IGraphType 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Converters/GraphQLExecutionErrorJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using GraphQL; 6 | using TypeCache.Extensions; 7 | 8 | namespace TypeCache.GraphQL.Converters; 9 | 10 | public sealed class GraphQLExecutionErrorJsonConverter : JsonConverter 11 | { 12 | /// 13 | public override ExecutionError Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 14 | => throw new NotImplementedException(); 15 | 16 | public override void Write(Utf8JsonWriter writer, ExecutionError? value, JsonSerializerOptions options) 17 | { 18 | if (value is null) 19 | { 20 | writer.WriteNullValue(); 21 | return; 22 | } 23 | 24 | writer.WriteStartObject(); 25 | writer.WriteString("message", value.Message); 26 | 27 | if (value.Locations is not null) 28 | { 29 | writer.WritePropertyName("locations"); 30 | JsonSerializer.Serialize(writer, value.Locations, options); 31 | } 32 | 33 | if (value.Path?.Any() is true) 34 | { 35 | writer.WritePropertyName("path"); 36 | JsonSerializer.Serialize(writer, value.Path, options); 37 | } 38 | 39 | if (value.Extensions?.Any() is true) 40 | { 41 | writer.WritePropertyName("extensions"); 42 | JsonSerializer.Serialize(writer, value.Extensions, options); 43 | } 44 | 45 | writer.WriteEndObject(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Data/Edge.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Resolvers; 4 | using GraphQL.Types; 5 | using TypeCache.Extensions; 6 | using TypeCache.GraphQL.Attributes; 7 | using TypeCache.GraphQL.Extensions; 8 | 9 | namespace TypeCache.GraphQL.Data; 10 | 11 | [GraphQLDescription("An edge in a connection that associates a cursor ID with data.")] 12 | public readonly record struct Edge( 13 | [GraphQLDescription("An identification number for use in pagination.")] int Cursor 14 | , [GraphQLDescription("The data item associated with the cursor value.")] T Node) 15 | where T : notnull 16 | { 17 | public static ObjectGraphType CreateGraphType(string name, IGraphType dataGraphType) 18 | { 19 | var graphType = new ObjectGraphType 20 | { 21 | Name = Invariant($"{name}{nameof(Edge)}"), 22 | Description = typeof(Edge).GraphQLDescription() 23 | }; 24 | 25 | graphType.AddField(new() 26 | { 27 | Name = nameof(Edge.Cursor), 28 | Type = ScalarType.Int32.ToGraphType(), 29 | Resolver = new FuncFieldResolver, int>(context => context.Source.Cursor) 30 | }); 31 | graphType.AddField(new() 32 | { 33 | Name = nameof(Edge.Node), 34 | ResolvedType = new NonNullGraphType(dataGraphType), 35 | Resolver = new FuncFieldResolver, T>(context => context.Source.Node) 36 | }); 37 | 38 | return graphType; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Data/PageInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.GraphQL.Attributes; 4 | 5 | namespace TypeCache.GraphQL.Data; 6 | 7 | [GraphQLDescription("Information about pagination in a connection.")] 8 | public readonly record struct PageInfo( 9 | [GraphQLDescription("The first cursor value of the current page.")] int StartCursor 10 | , [GraphQLDescription("The last cursor value of the current page.")] int EndCursor 11 | , [GraphQLDescription("Whether a page exists before the current page.")] bool HasPreviousPage 12 | , [GraphQLDescription("Whether a page exists after the current page.")] bool HasNextPage) 13 | { 14 | public PageInfo(uint offset, uint fetch, int totalCount) 15 | : this((int)offset + 1, (int)(offset + fetch), offset > 0, (offset + fetch) < totalCount) 16 | { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Extensions/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL; 4 | using GraphQL.DI; 5 | using GraphQL.Types; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Http; 8 | using TypeCache.GraphQL.Types; 9 | using TypeCache.GraphQL.Web; 10 | 11 | namespace TypeCache.GraphQL.Extensions; 12 | 13 | public static class ApplicationBuilderExtensions 14 | { 15 | /// 16 | /// => @.UseMiddleware<>(, ()); 17 | /// 18 | /// The route to use for this instance. 19 | public static IApplicationBuilder UseGraphQLSchema(this IApplicationBuilder @this, PathString route, IConfigureSchema configureSchema) 20 | => @this.UseMiddleware(route, configureSchema); 21 | 22 | /// 23 | /// => @.UseMiddleware<>(, ()); 24 | /// 25 | /// The route to use for this instance. 26 | public static IApplicationBuilder UseGraphQLSchema(this IApplicationBuilder @this, PathString route, Action configureSchema) 27 | => @this.UseMiddleware(route, new ConfigureSchema(configureSchema)); 28 | } 29 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Extensions/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using global::GraphQL.Types; 4 | using TypeCache.Extensions; 5 | 6 | namespace TypeCache.GraphQL.Extensions; 7 | 8 | public static class EnumExtensions 9 | { 10 | public static Type? ToGraphType(this ScalarType @this) 11 | => @this switch 12 | { 13 | ScalarType.Boolean => typeof(BooleanGraphType), 14 | ScalarType.SByte => typeof(SByteGraphType), 15 | ScalarType.Int16 => typeof(ShortGraphType), 16 | ScalarType.Int32 or ScalarType.Index => typeof(IntGraphType), 17 | ScalarType.Int64 or ScalarType.IntPtr => typeof(LongGraphType), 18 | ScalarType.BigInteger or ScalarType.Int128 or ScalarType.UInt128 => typeof(BigIntGraphType), 19 | ScalarType.Byte => typeof(ByteGraphType), 20 | ScalarType.UInt16 => typeof(UShortGraphType), 21 | ScalarType.UInt32 => typeof(UIntGraphType), 22 | ScalarType.UInt64 or ScalarType.UIntPtr => typeof(ULongGraphType), 23 | ScalarType.Half => typeof(HalfGraphType), 24 | ScalarType.Single or ScalarType.Double => typeof(FloatGraphType), 25 | ScalarType.Decimal => typeof(DecimalGraphType), 26 | ScalarType.DateOnly => typeof(DateOnlyGraphType), 27 | ScalarType.DateTime => typeof(DateTimeGraphType), 28 | ScalarType.DateTimeOffset => typeof(DateTimeOffsetGraphType), 29 | ScalarType.TimeOnly => typeof(TimeOnlyGraphType), 30 | ScalarType.TimeSpan => typeof(TimeSpanSecondsGraphType), 31 | ScalarType.Guid => typeof(GuidGraphType), 32 | ScalarType.String or ScalarType.Char => typeof(StringGraphType), 33 | ScalarType.Uri => typeof(UriGraphType), 34 | _ => null 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Buffers.Text; 3 | global using System.Diagnostics; 4 | global using System.Diagnostics.CodeAnalysis; 5 | global using static System.FormattableString; 6 | global using static System.Runtime.CompilerServices.MethodImplOptions; 7 | global using CancellationToken = System.Threading.CancellationToken; 8 | global using MethodImplAttribute = System.Runtime.CompilerServices.MethodImplAttribute; 9 | global using Task = System.Threading.Tasks.Task; 10 | global using ValueTask = System.Threading.Tasks.ValueTask; 11 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Listeners/DefaultDocumentExecutionListener.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Execution; 4 | 5 | namespace TypeCache.GraphQL.Listeners; 6 | 7 | internal sealed class DefaultDocumentExecutionListener : DocumentExecutionListenerBase 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "TypeCache.GraphQL": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:56443;http://localhost:56444" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/README.md: -------------------------------------------------------------------------------- 1 | ># TypeCache - GraphQL 2 | ###### sam987883@gmail.com 3 | 4 | [**Source Code**](https://github.com/sam987883/TypeCache/tree/master/src/TypeCache.GraphQL) 5 | 6 | [Request Features (or report a bug) (if any)](https://github.com/sam987883/TypeCache/issues) 7 | 8 | --- 9 | ### `TypeCache.GraphQL.Types` - GraphQL Type Objects 10 | 11 | - `GraphQLEnumType` 12 | - `GraphQLHashIdType` 13 | - `GraphQLInputType` 14 | - `GraphQLInterfaceType` 15 | - `GraphQLObjectType` 16 | - `GraphQLScalarType` 17 | - `GraphQLUnionType` 18 | - `GraphQLUriType` 19 | --- 20 | ### `TypeCache.GraphQL.Attributes` - GraphQL Attributes 21 | 22 | - `GraphQLDeprecationReasonAttribute` 23 | - `GraphQLDescriptionAttribute` 24 | - `GraphQLIgnoreAttribute` 25 | - `GraphQLInputNameAttribute` 26 | - `GraphQLMutationAttribute` 27 | - `GraphQLNameAttribute` 28 | - `GraphQLQueryAttribute` 29 | - `GraphQLSubscriptionAttribute` 30 | - `GraphQLTypeAttribute` 31 | --- 32 | ### `GraphQL.Types.ISchema` - GraphQL ISchema Extensions 33 | 34 | - `AddVersion(...)` 35 | - `AddDatabaseSchemaQueries(...)` 36 | - `AddDatabaseSchemaQuery(...)` 37 | - `AddDatabaseEndpoints(...)` 38 | - `AddEndpoints<>(...)` 39 | - `AddQuery(...)` 40 | - `AddQueries<>(...)` 41 | - `AddMutation(...)` 42 | - `AddMutations<>(...)` 43 | - `AddSubscription(...)` 44 | - `AddSubscriptions<>(...)` 45 | - `AddSqlApiEndpoints<>(...)` 46 | - `AddSqlApiCallProcedureEndpoint<>(...)` 47 | - `AddSqlApiDeleteDataEndpoint<>(...)` 48 | - `AddSqlApiDeleteEndpoint<>(...)` 49 | - `AddSqlApiInsertDataEndpoint<>(...)` 50 | - `AddSqlApiInsertEndpoint<>(...)` 51 | - `AddSqlApiSelectEndpoint<>(...)` 52 | - `AddSqlApiUpdateDataEndpoint<>(...)` 53 | - `AddSqlApiUpdateEndpoint<>(...)` 54 | --- 55 | ### `TypeCache.GraphQL.Types.GraphQLObject` - GraphQL GraphQLObject Extensions 56 | 57 | - `AddField(MethodInfo)` 58 | - `AddQueryItem(MethodInfo, Func, Func)` 59 | - `AddQueryCollection(MethodInfo, Func, Func)` 60 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/BatchCollectionFieldResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | using TypeCache.Extensions; 5 | using TypeCache.GraphQL.Extensions; 6 | using IResolveFieldContext = global::GraphQL.IResolveFieldContext; 7 | 8 | namespace TypeCache.GraphQL.Resolvers; 9 | 10 | public sealed class BatchCollectionFieldResolver : FieldResolver 11 | where SOURCE : notnull 12 | where ITEM : notnull 13 | { 14 | private readonly Lock _Lock = new(); 15 | private readonly RuntimeMethodHandle _MethodHandle; 16 | private readonly Func _GetResults; 17 | private bool _HasResults = false; 18 | private Task? _Results = null; 19 | 20 | /// 21 | /// 22 | public BatchCollectionFieldResolver(MethodInfo methodInfo, Func getResults) 23 | { 24 | methodInfo.ThrowIfNull(); 25 | getResults.ThrowIfNull(); 26 | methodInfo.IsStatic.ThrowIfFalse(); 27 | methodInfo.ReturnType.IsAny, Task>, ValueTask>, ITEM[], Task, ValueTask>().ThrowIfFalse(); 28 | 29 | this._MethodHandle = methodInfo.MethodHandle; 30 | this._GetResults = getResults; 31 | } 32 | 33 | /// 34 | protected override async ValueTask ResolveAsync(IResolveFieldContext context) 35 | { 36 | context.Source.ThrowIfNull(); 37 | 38 | if (!this._HasResults) 39 | { 40 | using (this._Lock.EnterScope()) 41 | { 42 | if (!this._HasResults) 43 | { 44 | this._Results = this.GetData(context); 45 | this._HasResults = true; 46 | } 47 | } 48 | } 49 | 50 | return this._GetResults((SOURCE)context.Source, await this._Results!); 51 | } 52 | 53 | private Task GetData(IResolveFieldContext context) 54 | { 55 | var methodInfo = (MethodInfo)this._MethodHandle.ToMethodBase(); 56 | var result = methodInfo.InvokeStaticFunc(context.GetArguments(methodInfo).ToArray()); 57 | return result switch 58 | { 59 | ITEM[] batch => Task.FromResult(batch), 60 | IEnumerable batch => Task.FromResult(batch.AsArray()), 61 | Task task => task, 62 | Task> task => Task.FromResult(task.Result.AsArray()), 63 | ValueTask valueTask => valueTask.AsTask(), 64 | ValueTask> valueTask => Task.FromResult(valueTask.Result.AsArray()), 65 | _ => Task.FromResult([]) 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/BatchItemFieldResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | using TypeCache.Extensions; 5 | using TypeCache.GraphQL.Extensions; 6 | using IResolveFieldContext = global::GraphQL.IResolveFieldContext; 7 | 8 | namespace TypeCache.GraphQL.Resolvers; 9 | 10 | public sealed class BatchItemFieldResolver : FieldResolver 11 | where SOURCE : notnull 12 | where ITEM : notnull 13 | { 14 | private readonly Lock _Lock = new(); 15 | private readonly RuntimeMethodHandle _MethodHandle; 16 | private readonly Func _GetResult; 17 | private bool _HasResults = false; 18 | private Task? _Results = null; 19 | 20 | /// 21 | /// 22 | public BatchItemFieldResolver(MethodInfo methodInfo, Func getResult) 23 | { 24 | methodInfo.ThrowIfNull(); 25 | getResult.ThrowIfNull(); 26 | methodInfo.IsStatic.ThrowIfFalse(); 27 | methodInfo.ReturnType.IsAny, ValueTask>().ThrowIfFalse(); 28 | 29 | this._MethodHandle = methodInfo.MethodHandle; 30 | this._GetResult = getResult; 31 | } 32 | 33 | /// 34 | protected override async ValueTask ResolveAsync(IResolveFieldContext context) 35 | { 36 | context.Source.ThrowIfNull(); 37 | 38 | if (!this._HasResults) 39 | { 40 | using (this._Lock.EnterScope()) 41 | { 42 | if (!this._HasResults) 43 | { 44 | this._Results = this.GetData(context); 45 | this._HasResults = true; 46 | } 47 | } 48 | } 49 | 50 | return this._GetResult((SOURCE)context.Source, await this._Results!); 51 | } 52 | 53 | private Task GetData(IResolveFieldContext context) 54 | { 55 | var methodInfo = (MethodInfo)this._MethodHandle.ToMethodBase(); 56 | var result = methodInfo.InvokeStaticFunc(context.GetArguments(methodInfo).ToArray()); 57 | return result switch 58 | { 59 | ITEM[] batch => Task.FromResult(batch), 60 | Task task => task, 61 | ValueTask valueTask => valueTask.AsTask(), 62 | _ => Task.FromResult([]) 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/CustomFieldResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | using IResolveFieldContext = global::GraphQL.IResolveFieldContext; 5 | 6 | namespace TypeCache.GraphQL.Resolvers; 7 | 8 | public sealed class CustomFieldResolver : FieldResolver 9 | { 10 | private readonly Func> _Resolve; 11 | 12 | public CustomFieldResolver(Func> resolve) 13 | { 14 | resolve.ThrowIfNull(); 15 | 16 | this._Resolve = resolve; 17 | } 18 | 19 | public CustomFieldResolver(Func> resolve) 20 | { 21 | resolve.ThrowIfNull(); 22 | 23 | this._Resolve = context => new ValueTask(resolve(context)); 24 | } 25 | 26 | public CustomFieldResolver(Func resolve) 27 | { 28 | resolve.ThrowIfNull(); 29 | 30 | this._Resolve = context => ValueTask.FromResult(resolve(context)); 31 | } 32 | 33 | protected override ValueTask ResolveAsync(IResolveFieldContext context) 34 | => this._Resolve(context); 35 | } 36 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/CustomSourceStreamResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | using TypeCache.Utilities; 5 | using IResolveFieldContext = global::GraphQL.IResolveFieldContext; 6 | using ISourceStreamResolver = global::GraphQL.Resolvers.ISourceStreamResolver; 7 | using ResolveFieldContext = global::GraphQL.ResolveFieldContext; 8 | 9 | namespace TypeCache.GraphQL.Resolvers; 10 | 11 | public sealed class CustomSourceStreamResolver(Func>> resolve) : SourceStreamResolver 12 | { 13 | public static ISourceStreamResolver Create(Func>> resolve) 14 | { 15 | resolve.ThrowIfNull(); 16 | 17 | return new CustomSourceStreamResolver(resolve); 18 | } 19 | 20 | public static ISourceStreamResolver Create(Func>> resolve) 21 | => typeof(T).IsValueType 22 | ? Create(async context => new CustomObservable(await resolve(context))) 23 | : Create(async context => (IObservable)await resolve(context)); 24 | 25 | public static ISourceStreamResolver Create(Func> resolve) 26 | => typeof(T).IsValueType 27 | ? Create(context => ValueTask.FromResult>(new CustomObservable(resolve(context)))) 28 | : Create(context => ValueTask.FromResult>((IObservable)resolve(context))); 29 | 30 | public static ISourceStreamResolver Create(Func> resolve) 31 | => Create(context => 32 | { 33 | var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(context.CancellationToken); 34 | ((ResolveFieldContext)context).CancellationToken = tokenSource.Token; 35 | var enumerable = resolve(context); 36 | return new ObservableAdapter(enumerable!, tokenSource); 37 | }); 38 | 39 | [MethodImpl(AggressiveInlining), DebuggerHidden] 40 | protected override ValueTask> ResolveAsync(IResolveFieldContext context) 41 | => resolve.Invoke(context); 42 | } 43 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/DatabaseSchemaFieldResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using global::GraphQL; 4 | using TypeCache.Data; 5 | using TypeCache.Extensions; 6 | 7 | namespace TypeCache.GraphQL.Resolvers; 8 | 9 | public sealed class DatabaseSchemaFieldResolver : FieldResolver 10 | { 11 | protected override async ValueTask ResolveAsync(IResolveFieldContext context) 12 | { 13 | var collection = context.FieldDefinition.GetMetadata(nameof(SchemaCollection)); 14 | var dataSource = context.FieldDefinition.GetMetadata(nameof(IDataSource)); 15 | 16 | string database = context.GetArgument(nameof(database)); 17 | string where = context.GetArgument(nameof(where)); 18 | string[] orderBy = context.GetArgument(nameof(orderBy)); 19 | 20 | var table = await dataSource.GetDatabaseSchemaAsync(collection, database, context.CancellationToken); 21 | return table?.Select(where, orderBy?.ToCSV()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/FieldResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL; 4 | using GraphQL.Resolvers; 5 | 6 | namespace TypeCache.GraphQL.Resolvers; 7 | 8 | public abstract class FieldResolver : IFieldResolver 9 | { 10 | async ValueTask IFieldResolver.ResolveAsync(IResolveFieldContext context) 11 | { 12 | try 13 | { 14 | return await this.ResolveAsync(context); 15 | } 16 | catch (ExecutionError error) 17 | { 18 | context.Errors.Add(error); 19 | return null; 20 | } 21 | catch (AggregateException error) 22 | { 23 | var executionErrors = error.InnerExceptions.Select(exception => 24 | exception as ExecutionError ?? new ExecutionError(exception.Message, exception)); 25 | context.Errors.AddRange(executionErrors); 26 | return null; 27 | } 28 | catch (Exception error) 29 | { 30 | context.Errors.Add(new ExecutionError(error.Message, error)); 31 | return null; 32 | } 33 | } 34 | 35 | protected abstract ValueTask ResolveAsync(IResolveFieldContext context); 36 | } 37 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/ItemFieldResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | using TypeCache.Extensions; 5 | using TypeCache.GraphQL.Extensions; 6 | using IResolveFieldContext = global::GraphQL.IResolveFieldContext; 7 | 8 | namespace TypeCache.GraphQL.Resolvers; 9 | 10 | public sealed class ItemFieldResolver : FieldResolver 11 | where ITEM : notnull 12 | { 13 | private readonly Lock _Lock = new(); 14 | private readonly RuntimeMethodHandle _MethodHandle; 15 | private bool _HasResult = false; 16 | private Task _Result = default!; 17 | 18 | /// 19 | /// 20 | public ItemFieldResolver(MethodInfo methodInfo) 21 | { 22 | methodInfo.ThrowIfNull(); 23 | methodInfo.IsStatic.ThrowIfFalse(); 24 | methodInfo.ReturnType.IsAny, ValueTask>().ThrowIfFalse(); 25 | 26 | this._MethodHandle = methodInfo.MethodHandle; 27 | } 28 | 29 | /// 30 | protected override async ValueTask ResolveAsync(IResolveFieldContext context) 31 | { 32 | context.Source.ThrowIfNull(); 33 | 34 | if (!this._HasResult) 35 | { 36 | using (this._Lock.EnterScope()) 37 | { 38 | if (!this._HasResult) 39 | { 40 | this._Result = this.GetData(context); 41 | this._HasResult = true; 42 | } 43 | } 44 | } 45 | 46 | return await this._Result; 47 | } 48 | 49 | private Task GetData(IResolveFieldContext context) 50 | { 51 | var methodInfo = (MethodInfo)this._MethodHandle.ToMethodBase(); 52 | var result = methodInfo.InvokeStaticFunc(context.GetArguments(methodInfo).ToArray()); 53 | return result switch 54 | { 55 | ITEM item => Task.FromResult(item), 56 | Task task => task, 57 | ValueTask valueTask => valueTask.AsTask(), 58 | _ => Task.FromResult(default(ITEM)!) 59 | }; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/MethodFieldResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using TypeCache.Extensions; 6 | using TypeCache.GraphQL.Extensions; 7 | using IResolveFieldContext = global::GraphQL.IResolveFieldContext; 8 | 9 | namespace TypeCache.GraphQL.Resolvers; 10 | 11 | public sealed class MethodFieldResolver(MethodInfo methodInfo) : FieldResolver 12 | { 13 | protected override async ValueTask ResolveAsync(IResolveFieldContext context) 14 | { 15 | context.RequestServices.ThrowIfNull(); 16 | 17 | var arguments = context.GetArguments(methodInfo).ToArray(); 18 | object? result; 19 | if (!methodInfo.IsStatic) 20 | { 21 | var controller = context.RequestServices.GetRequiredService(methodInfo.DeclaringType!); 22 | result = methodInfo.InvokeFunc(controller, arguments); 23 | } 24 | else 25 | result = methodInfo.InvokeStaticFunc(arguments); 26 | 27 | return result switch 28 | { 29 | ValueTask valueTask => await valueTask, 30 | Task task => await task, 31 | _ => result 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/MethodSourceStreamResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using TypeCache.Extensions; 6 | using TypeCache.GraphQL.Extensions; 7 | 8 | namespace TypeCache.GraphQL.Resolvers; 9 | 10 | public sealed class MethodSourceStreamResolver : SourceStreamResolver 11 | { 12 | private readonly MethodInfo _MethodInfo; 13 | 14 | public MethodSourceStreamResolver(MethodInfo methodInfo) 15 | { 16 | var returnsObservable = methodInfo.ReturnType.Is(typeof(IObservable<>)) || methodInfo.ReturnType.Implements(typeof(IObservable<>)); 17 | if (!returnsObservable) 18 | { 19 | var returnType = methodInfo.ReturnType.GetObjectType(); 20 | if (returnType is ObjectType.Task || returnType is ObjectType.ValueTask) 21 | returnsObservable = methodInfo.ReturnType.GenericTypeArguments.Single().GetObjectType() is ObjectType.Observable; 22 | 23 | returnsObservable.ThrowIfFalse(); 24 | } 25 | 26 | this._MethodInfo = methodInfo; 27 | } 28 | 29 | protected override ValueTask> ResolveAsync(global::GraphQL.IResolveFieldContext context) 30 | { 31 | context.RequestServices.ThrowIfNull(); 32 | 33 | var arguments = context.GetArguments(this._MethodInfo).ToArray(); 34 | object? result; 35 | if (!this._MethodInfo.IsStatic) 36 | { 37 | var controller = context.RequestServices.GetRequiredService(this._MethodInfo.DeclaringType!); 38 | result = this._MethodInfo.InvokeFunc(controller, arguments); 39 | } 40 | else 41 | result = this._MethodInfo.InvokeStaticFunc(arguments); 42 | 43 | return result switch 44 | { 45 | ValueTask> valueTask => valueTask, 46 | Task> task => new(task), 47 | IObservable item => ValueTask.FromResult(item), 48 | _ => throw new UnreachableException(Invariant($"Method {this._MethodInfo.Name()} returned a null for {this._MethodInfo.ReturnType.Name()}.")) 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/SourceStreamResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL; 4 | using GraphQL.Resolvers; 5 | 6 | namespace TypeCache.GraphQL.Resolvers; 7 | 8 | public abstract class SourceStreamResolver : ISourceStreamResolver 9 | { 10 | async ValueTask> ISourceStreamResolver.ResolveAsync(IResolveFieldContext context) 11 | { 12 | try 13 | { 14 | return await this.ResolveAsync(context); 15 | } 16 | catch (AggregateException error) 17 | { 18 | var executionErrors = error.InnerExceptions.Select(exception => new ExecutionError(exception.Message, exception)); 19 | context.Errors.AddRange(executionErrors); 20 | return null!; 21 | } 22 | catch (Exception error) 23 | { 24 | context.Errors.Add(new ExecutionError(error.Message, error)); 25 | return null!; 26 | } 27 | } 28 | 29 | protected abstract ValueTask> ResolveAsync(IResolveFieldContext context); 30 | } 31 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/SqlApiCallFieldResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using TypeCache.Data; 6 | using TypeCache.Data.Extensions; 7 | using TypeCache.Extensions; 8 | using TypeCache.GraphQL.SqlApi; 9 | using TypeCache.Mediation; 10 | 11 | namespace TypeCache.GraphQL.Resolvers; 12 | 13 | public sealed class SqlApiCallFieldResolver : FieldResolver 14 | { 15 | protected override async ValueTask ResolveAsync(IResolveFieldContext context) 16 | { 17 | var mediator = context.RequestServices!.GetRequiredService(); 18 | var objectSchema = context.FieldDefinition.GetMetadata(nameof(ObjectSchema)); 19 | var sqlCommand = objectSchema.DataSource.CreateSqlCommand(objectSchema.Name); 20 | 21 | context.GetArgument("parameters")?.ForEach(parameter => sqlCommand.Parameters[parameter.Name] = parameter.Value); 22 | 23 | var result = (IList)await mediator.Map(sqlCommand.ToSqlModelsRequest(), context.CancellationToken); 24 | return new OutputResponse() 25 | { 26 | TotalCount = sqlCommand.RecordsAffected > 0 ? sqlCommand.RecordsAffected : result.Count, 27 | DataSource = objectSchema.DataSource.Name, 28 | Output = result, 29 | Sql = sqlCommand.SQL, 30 | Table = objectSchema.Name 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Resolvers/StringCase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.GraphQL.Resolvers; 4 | 5 | public enum StringCase 6 | { 7 | Lower = 1, 8 | LowerInvariant, 9 | Upper, 10 | UpperInvariant 11 | } 12 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/SqlApi/OutputResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Resolvers; 4 | using GraphQL.Types; 5 | using TypeCache.GraphQL.Attributes; 6 | using TypeCache.GraphQL.Extensions; 7 | 8 | namespace TypeCache.GraphQL.SqlApi; 9 | 10 | [GraphQLDescription("Response for a DELETE/INSERT/UPDATE SQL API mutation action.")] 11 | public class OutputResponse 12 | { 13 | public string? DataSource { get; set; } 14 | 15 | public IList? Output { get; set; } 16 | 17 | public string? Sql { get; set; } 18 | 19 | public string? Table { get; set; } 20 | 21 | public long? TotalCount { get; set; } 22 | 23 | public static ObjectGraphType CreateGraphType(string name, string description, IGraphType dataGraphType) 24 | { 25 | var graphType = new ObjectGraphType 26 | { 27 | Name = Invariant($"{name}{nameof(OutputResponse)}"), 28 | Description = description 29 | }; 30 | 31 | graphType.AddField(nameof(OutputResponse.DataSource), new FuncFieldResolver, string?>(context => context.Source.DataSource)); 32 | graphType.AddField(nameof(OutputResponse.Output), new ListGraphType(new NonNullGraphType(dataGraphType)), new FuncFieldResolver, IList?>(context => context.Source.Output)); 33 | graphType.AddField(nameof(OutputResponse.Sql), new FuncFieldResolver, string?>(context => context.Source.Sql)); 34 | graphType.AddField(nameof(OutputResponse.Table), new FuncFieldResolver, string?>(context => context.Source.Table)); 35 | graphType.AddField(nameof(OutputResponse.TotalCount), new FuncFieldResolver, long?>(context => context.Source.TotalCount)); 36 | 37 | return graphType; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/SqlApi/Parameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Types; 4 | using TypeCache.GraphQL.Attributes; 5 | using TypeCache.GraphQL.Types; 6 | 7 | namespace TypeCache.GraphQL.SqlApi; 8 | 9 | public class Parameter 10 | { 11 | [GraphQLType>()] 12 | public string Name { get; set; } = string.Empty; 13 | 14 | [GraphQLType>()] 15 | public string Value { get; set; } = string.Empty; 16 | } 17 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/SqlApi/SelectResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Resolvers; 4 | using GraphQL.Types; 5 | using TypeCache.GraphQL.Attributes; 6 | using TypeCache.GraphQL.Data; 7 | using TypeCache.GraphQL.Extensions; 8 | 9 | namespace TypeCache.GraphQL.SqlApi; 10 | 11 | [GraphQLDescription("Response for a SELECT SQL action.")] 12 | public class SelectResponse 13 | where T : notnull 14 | { 15 | public SelectResponse() 16 | { 17 | } 18 | 19 | public SelectResponse(T[] items, int totalCount, uint offset) 20 | { 21 | var start = offset + 1; 22 | this.Data = new(offset, items) 23 | { 24 | PageInfo = new(offset, (uint)items.Length, totalCount), 25 | TotalCount = totalCount 26 | }; 27 | } 28 | 29 | public Connection? Data { get; set; } 30 | 31 | public string? DataSource { get; set; } 32 | 33 | public string? Sql { get; set; } 34 | 35 | public string? Table { get; set; } 36 | 37 | public static ObjectGraphType CreateGraphType(string name, string description, IGraphType dataGraphType) 38 | { 39 | var graphType = new ObjectGraphType 40 | { 41 | Name = Invariant($"{name}{nameof(SelectResponse)}"), 42 | Description = description 43 | }; 44 | 45 | graphType.AddField(nameof(SelectResponse.Data), Connection.CreateGraphType(name, dataGraphType), new FuncFieldResolver, Connection>(context => context.Source.Data)); 46 | graphType.AddField(nameof(SelectResponse.DataSource), new FuncFieldResolver, string>(context => context.Source.DataSource)); 47 | graphType.AddField(nameof(SelectResponse.Sql), new FuncFieldResolver, string>(context => context.Source.Sql)); 48 | graphType.AddField(nameof(SelectResponse.Table), new FuncFieldResolver, string>(context => context.Source.Table)); 49 | 50 | return graphType; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/TypeCache.GraphQL.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | enable 5 | TypeCache.GraphQL 6 | TypeCache.GraphQL 7 | 9.1.2 8 | Samuel Abraham <sam987883@gmail.com> 9 | Samuel Abraham <sam987883@gmail.com> 10 | TypeCache GraphQL 11 | An easier way to add endpoints to GraphQL: 12 | Attribute based GraphQL type generation. 13 | Simplified GraphQL DataLoader support. 14 | Automatic generation of SQL related endpoints. 15 | 16 | Copyright (c) 2021 Samuel Abraham 17 | TypeCache 18 | false 19 | False 20 | MIT 21 | https://www.nuget.org/packages/TypeCache.GraphQL/ 22 | https://github.com/sam987883/TypeCache/ 23 | git 24 | en-US 25 | TypeCash.png 26 | README.md 27 | enable 28 | 29 | 30 | 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | True 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | True 56 | \ 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Types/ConfigureSchema.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.DI; 4 | using GraphQL.Types; 5 | 6 | namespace TypeCache.GraphQL.Types; 7 | 8 | internal sealed class ConfigureSchema(Action configure) : IConfigureSchema 9 | { 10 | public void Configure(ISchema schema, IServiceProvider serviceProvider) 11 | => configure?.Invoke(schema, serviceProvider); 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Types/EnumGraphType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Types; 4 | using TypeCache.Extensions; 5 | using TypeCache.GraphQL.Attributes; 6 | using TypeCache.GraphQL.Extensions; 7 | using TypeCache.Utilities; 8 | 9 | namespace TypeCache.GraphQL.Types; 10 | 11 | /// 12 | public sealed class EnumGraphType : EnumerationGraphType 13 | where T : struct, Enum 14 | { 15 | public EnumGraphType() 16 | { 17 | this.Name = typeof(T).GraphQLName(); 18 | this.Description = typeof(T).GraphQLDescription(); 19 | this.DeprecationReason = typeof(T).GraphQLDeprecationReason(); 20 | 21 | Func? changeEnumCase = Enum.Attributes switch 22 | { 23 | _ when Enum.Attributes.TryFirst(out var attribute) => attribute.ChangeEnumCase, 24 | _ when Enum.Attributes.TryFirst(out var attribute) => attribute.ChangeEnumCase, 25 | _ when Enum.Attributes.TryFirst(out var attribute) => attribute.ChangeEnumCase, 26 | _ => null 27 | }; 28 | 29 | Enum.Values 30 | .Where(_ => !_.Attributes().Any()) 31 | .Select(_ => new EnumValueDefinition(_.Attributes().FirstOrDefault()?.Name ?? changeEnumCase?.Invoke(_.Name()) ?? _.Name(), _) 32 | { 33 | Description = _.Attributes().FirstOrDefault()?.Description, 34 | DeprecationReason = _.Attributes().FirstOrDefault()?.DeprecationReason 35 | }) 36 | .ToArray() 37 | .ForEach(this.Add); 38 | } 39 | 40 | /// 41 | public override object? Serialize(object? value) 42 | => value is not null ? (T)value : null; 43 | 44 | /// 45 | public override bool CanParseValue(object? value) 46 | => value switch 47 | { 48 | null => true, 49 | T token => Enum.IsDefined(token), 50 | string text => Enum.TryParse(text, true, out _), 51 | _ when value.GetType() == typeof(T).GetEnumUnderlyingType() => true, 52 | _ => false 53 | }; 54 | 55 | /// 56 | public override object? ParseValue(object? value) 57 | => value switch 58 | { 59 | null => null, 60 | T token when Enum.IsDefined(token) => token, 61 | T token => null, 62 | string text => Enum.Parse(text, true), 63 | _ => Enum.ToObject(typeof(T), value) 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Types/HashIdGraphType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Types; 4 | using GraphQLParser.AST; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using TypeCache.Extensions; 7 | using TypeCache.Utilities; 8 | 9 | namespace TypeCache.GraphQL.Types; 10 | 11 | /// 12 | /// Requires call to any one of: 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | public sealed class HashIdGraphType : ScalarGraphType 21 | { 22 | private readonly IHashMaker _HashMaker; 23 | 24 | /// 25 | public HashIdGraphType(IHashMaker hashMaker) 26 | { 27 | hashMaker.ThrowIfNull(); 28 | 29 | this._HashMaker = hashMaker; 30 | this.Name = "HashID"; 31 | this.Description = "A hashed ID."; 32 | } 33 | 34 | public override bool CanParseLiteral(GraphQLValue value) 35 | => value is GraphQLNullValue || value is GraphQLStringValue; 36 | 37 | public override bool CanParseValue(object? value) 38 | => value is null || value is Guid || value is string; 39 | 40 | [MethodImpl(AggressiveInlining), DebuggerHidden] 41 | public override object? ParseLiteral(GraphQLValue value) 42 | => this.ParseValue(value); 43 | 44 | public override object? ParseValue(object? value) 45 | => value switch 46 | { 47 | GraphQLNullValue => null, 48 | GraphQLStringValue text => this._HashMaker.Decrypt((string)text.Value), 49 | _ => value 50 | }; 51 | 52 | public override object? Serialize(object? value) 53 | => value switch 54 | { 55 | int id => this._HashMaker.Encrypt(id).ToString(), 56 | long id => this._HashMaker.Encrypt(id).ToString(), 57 | _ => value 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Types/InputGraphType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Types; 4 | using TypeCache.Extensions; 5 | using TypeCache.GraphQL.Extensions; 6 | 7 | namespace TypeCache.GraphQL.Types; 8 | 9 | public sealed class InputGraphType : InputObjectGraphType 10 | where T : notnull 11 | { 12 | public InputGraphType() 13 | { 14 | this.Name = typeof(T).GraphQLInputName(); 15 | this.Description = typeof(T).GraphQLDescription(); 16 | this.DeprecationReason = typeof(T).GraphQLDeprecationReason(); 17 | 18 | typeof(T).GetPublicProperties() 19 | .Where(propertyInfo => propertyInfo.CanRead && propertyInfo.CanWrite && !propertyInfo.GraphQLIgnore()) 20 | .ToArray() 21 | .ForEach(propertyInfo => this.AddField(new() 22 | { 23 | Type = propertyInfo.ToGraphQLType(true), 24 | Name = propertyInfo.GraphQLName(), 25 | Description = propertyInfo.GraphQLDescription(), 26 | DeprecationReason = propertyInfo.GraphQLDeprecationReason() 27 | })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Types/InterfaceGraphType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Types; 4 | using TypeCache.Extensions; 5 | using TypeCache.GraphQL.Extensions; 6 | using TypeCache.GraphQL.Resolvers; 7 | 8 | namespace TypeCache.GraphQL.Types; 9 | 10 | public sealed class InterfaceGraphType : global::GraphQL.Types.InterfaceGraphType 11 | where T : class 12 | { 13 | public InterfaceGraphType() 14 | { 15 | typeof(T).IsInterface.ThrowIfFalse(); 16 | 17 | this.Name = typeof(T).GraphQLName(); 18 | 19 | typeof(T).GetPublicProperties() 20 | .Where(propertyInfo => propertyInfo.CanRead && !propertyInfo.GraphQLIgnore()) 21 | .ToArray() 22 | .ForEach(propertyInfo => this.AddField(propertyInfo.ToFieldType())); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Web/GraphQLRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.GraphQL.Web; 4 | 5 | public sealed class GraphQLRequest 6 | { 7 | public string? OperationName { get; set; } 8 | 9 | public string? Query { get; set; } 10 | 11 | public IDictionary? Variables { get; set; } 12 | 13 | public IDictionary? Extensions { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /src/TypeCache.GraphQL/Web/GraphQLSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using TypeCache.Converters; 6 | using TypeCache.GraphQL.Converters; 7 | 8 | namespace TypeCache.GraphQL.Web; 9 | 10 | public sealed class GraphQLJsonSerializer : global::GraphQL.IGraphQLSerializer 11 | { 12 | private readonly JsonSerializerOptions _JsonOptions; 13 | 14 | public GraphQLJsonSerializer(JsonConverter executionErrorJsonConverter) 15 | { 16 | this._JsonOptions = new() 17 | { 18 | MaxDepth = 40, 19 | PropertyNameCaseInsensitive = true, 20 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase 21 | }; 22 | this._JsonOptions.Converters.Add(new BigIntegerJsonConverter()); 23 | this._JsonOptions.Converters.Add(new DictionaryJsonConverter()); 24 | this._JsonOptions.Converters.Add(new GraphQLExecutionResultJsonConverter()); 25 | this._JsonOptions.Converters.Add(executionErrorJsonConverter); 26 | } 27 | 28 | public bool IsNativelyAsync => true; 29 | 30 | public ValueTask ReadAsync(Stream stream, CancellationToken cancellationToken = default) 31 | => JsonSerializer.DeserializeAsync(stream, this._JsonOptions, cancellationToken); 32 | 33 | public Task WriteAsync(Stream stream, T? value, CancellationToken cancellationToken = default) 34 | => JsonSerializer.SerializeAsync(stream, value, this._JsonOptions, cancellationToken); 35 | 36 | private T? ReadNode(JsonElement jsonElement) 37 | => jsonElement.Deserialize(this._JsonOptions); 38 | 39 | public T? ReadNode(object? value) 40 | => value is not null ? ((JsonElement)value).Deserialize(this._JsonOptions) : default; 41 | } 42 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Attributes/RequireClaimAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Authorization; 4 | using TypeCache.Web.Requirements; 5 | 6 | namespace TypeCache.Web.Attributes; 7 | 8 | /// 9 | /// Require claim values for a controller or endpoint. 10 | /// 11 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] 12 | public sealed class RequireClaimAttribute(string claim, string[] allowedValues) : AuthorizeAttribute(nameof(ClaimAuthorizationRequirement)) 13 | { 14 | public string Claim { get; } = claim; 15 | 16 | public string[] AllowedValues { get; } = allowedValues; 17 | } 18 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Attributes/RequireHeaderAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Authorization; 4 | using TypeCache.Web.Requirements; 5 | 6 | namespace TypeCache.Web.Attributes; 7 | 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] 9 | public sealed class RequireHeaderAttribute(string header, string[] allowedValues) 10 | : AuthorizeAttribute(nameof(HeaderAuthorizationRequirement)) 11 | { 12 | public string Header { get; set; } = header; 13 | 14 | public string[] AllowedValues { get; set; } = allowedValues; 15 | } 16 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Extensions/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | using TypeCache.Web.Middleware; 5 | 6 | namespace TypeCache.Web.Extensions; 7 | 8 | public static class ApplicationBuilderExtensions 9 | { 10 | /// 11 | /// Registers . 12 | /// 13 | /// Must be called before any other UseSqlApi middelware registration. 14 | public static IApplicationBuilder UseSqlApi(this IApplicationBuilder @this) 15 | => @this.UseMiddleware(); 16 | } 17 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Extensions/AuthorizationHandlerContextExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc.Controllers; 6 | using Microsoft.AspNetCore.Mvc.Filters; 7 | 8 | namespace TypeCache.Web.Extensions; 9 | 10 | public static class AuthorizationHandlerContextExtensions 11 | { 12 | public static ControllerActionDescriptor? GetControllerActionDescriptor(this AuthorizationHandlerContext @this) 13 | => @this.Resource switch 14 | { 15 | Endpoint endpoint => endpoint.Metadata.OfType().First(), // Web API 16 | AuthorizationFilterContext authorizationFilterContext => authorizationFilterContext.ActionDescriptor as ControllerActionDescriptor, // MVC 17 | _ => null 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Extensions/AuthorizationOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Authorization; 4 | using TypeCache.Extensions; 5 | using TypeCache.Web.Requirements; 6 | 7 | namespace TypeCache.Web.Extensions; 8 | 9 | public static class AuthorizationOptionsExtensions 10 | { 11 | private static AuthorizationPolicy AddAuthorizationPolicy(this AuthorizationOptions @this, IAuthorizationRequirement requirement, string[]? authenticationSchemas) 12 | { 13 | var builder = new AuthorizationPolicyBuilder(); 14 | if (authenticationSchemas?.Length > 0) 15 | builder.AddAuthenticationSchemes(authenticationSchemas); 16 | 17 | builder.RequireAuthenticatedUser(); 18 | builder.AddRequirements(requirement); 19 | 20 | var policy = builder.Build(); 21 | @this.AddPolicy(requirement.GetType().Name(), policy); 22 | return policy; 23 | } 24 | 25 | [MethodImpl(AggressiveInlining), DebuggerHidden] 26 | public static AuthorizationPolicy AddClaimAuthorizationPolicy(this AuthorizationOptions @this, string[]? authenticationSchemas) 27 | => @this.AddAuthorizationPolicy(new ClaimAuthorizationRequirement(), authenticationSchemas); 28 | 29 | [MethodImpl(AggressiveInlining), DebuggerHidden] 30 | public static AuthorizationPolicy AddHeaderAuthorizationPolicy(this AuthorizationOptions @this, string[]? authenticationSchemas) 31 | => @this.AddAuthorizationPolicy(new HeaderAuthorizationRequirement(), authenticationSchemas); 32 | } 33 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Extensions/ClaimsPrincipalExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Security.Claims; 4 | using TypeCache.Extensions; 5 | 6 | namespace TypeCache.Web.Extensions; 7 | 8 | public static class ClaimsPrincipalExtensions 9 | { 10 | public static bool Any(this ClaimsPrincipal @this, string claimType, string[] values) 11 | { 12 | claimType.ThrowIfBlank(); 13 | 14 | var claim = @this.FindFirst(claimType); // OrdinalIgnoreCase 15 | if (claim is null) 16 | return false; 17 | 18 | return values.Length is 0 || values.Exists(value => value.EqualsIgnoreCase(claim.Value)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Extensions/HttpContextExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using Microsoft.AspNetCore.Http; 5 | using static System.Net.Mime.MediaTypeNames; 6 | 7 | namespace TypeCache.Web.Extensions; 8 | 9 | public static class HttpContextExtensions 10 | { 11 | public static async ValueTask GetJsonRequestAsync(this HttpContext @this, JsonSerializerOptions? options = null) 12 | => await JsonSerializer.DeserializeAsync(@this.Request.Body, options, @this.RequestAborted); 13 | 14 | public static async ValueTask GetRequestAsync(this HttpContext @this) 15 | { 16 | using var reader = new StreamReader(@this.Request.Body); 17 | return await reader.ReadToEndAsync(@this.RequestAborted); 18 | } 19 | 20 | public static async ValueTask WriteJsonResponseAsync(this HttpContext @this, T response, JsonSerializerOptions? options = null) 21 | { 22 | @this.Response.ContentType = Application.Json; 23 | @this.Response.StatusCode = StatusCodes.Status200OK; 24 | await JsonSerializer.SerializeAsync(@this.Response.Body, response, options, @this.RequestAborted); 25 | } 26 | 27 | public static async ValueTask WriteResponseAsync(this HttpContext @this, string response) 28 | { 29 | @this.Response.ContentType = Text.Plain; 30 | @this.Response.StatusCode = StatusCodes.Status200OK; 31 | using var writer = new StreamWriter(@this.Request.Body); 32 | await writer.WriteAsync(response); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Extensions/HttpRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Http; 4 | using TypeCache.Utilities; 5 | 6 | namespace TypeCache.Web.Extensions; 7 | 8 | public static class HttpRequestExtensions 9 | { 10 | [MethodImpl(AggressiveInlining), DebuggerHidden] 11 | public static string? GetQueryString(this HttpRequest @this, string key) 12 | => @this.Query[key]; 13 | 14 | [MethodImpl(AggressiveInlining), DebuggerHidden] 15 | public static string[] GetQueryValues(this HttpRequest @this, string key) 16 | => @this.GetQueryString(key)?.Split(',') ?? Array.Empty; 17 | } 18 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Http.Json; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using TypeCache.Converters; 6 | using TypeCache.Mediation; 7 | using TypeCache.Web.Mediation; 8 | 9 | namespace TypeCache.Web.Extensions; 10 | 11 | public static class ServiceCollectionExtensions 12 | { 13 | public static IServiceCollection AddHttpClientRule(this IServiceCollection @this, string name, Action configure) 14 | { 15 | @this.AddHttpClient(name, configure); 16 | return @this.AddKeyedScoped>(name, 17 | (provider, key) => new HttpClientRule(provider.GetRequiredService().CreateClient((string)key!))); 18 | } 19 | 20 | public static IServiceCollection ConfigureSqlApi(this IServiceCollection @this) 21 | => @this.Configure(options => 22 | { 23 | options.SerializerOptions.Converters.Add(new DataRowJsonConverter()); 24 | options.SerializerOptions.Converters.Add(new DataTableJsonConverter()); 25 | options.SerializerOptions.Converters.Add(new DataSetJsonConverter()); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Filters/SqlApiProcedureEndpointFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using TypeCache.Data; 6 | using TypeCache.Data.Extensions; 7 | using TypeCache.Extensions; 8 | 9 | namespace TypeCache.Web.Filters; 10 | 11 | public sealed class SqlApiProcedureEndpointFilter 12 | : IEndpointFilter 13 | { 14 | public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) 15 | { 16 | var routeValues = context.HttpContext.Request.RouteValues; 17 | 18 | string source = (string)routeValues[nameof(source)]!; 19 | string database = (string)routeValues[nameof(database)]!; 20 | string schema = (string)routeValues[nameof(schema)]!; 21 | string procedure = (string)routeValues[nameof(procedure)]!; 22 | 23 | await using var serviceScope = context.HttpContext.RequestServices.CreateAsyncScope(); 24 | var dataSource = serviceScope.ServiceProvider.GetKeyedService(source); 25 | if (dataSource is null) 26 | return Results.NotFound(Invariant($"{nameof(IDataSource)} was not found: {source}.")); 27 | 28 | if (!dataSource.Databases.ContainsIgnoreCase(database)) 29 | return Results.NotFound(Invariant($"Database {database} was not found in Data Source: {source}.")); 30 | 31 | var objectName = dataSource.Type switch 32 | { 33 | DataSourceType.MySql => Invariant($"{database.EscapeIdentifier(dataSource.Type)}.{procedure.EscapeIdentifier(dataSource.Type)}"), 34 | _ => Invariant($"{database.EscapeIdentifier(dataSource.Type)}.{schema.EscapeIdentifier(dataSource.Type)}.{procedure.EscapeIdentifier(dataSource.Type)}") 35 | }; 36 | var objectSchema = dataSource[objectName]; 37 | if (objectSchema is null) 38 | return Results.NotFound(Invariant($"{nameof(ObjectSchema)} for {objectName} was not found.")); 39 | 40 | if (objectSchema.Type is not DatabaseObjectType.StoredProcedure) 41 | return Results.BadRequest(Invariant($"{objectName} is not a {DatabaseObjectType.StoredProcedure.Name()}.")); 42 | 43 | context.HttpContext.Items.Add(nameof(IDataSource), dataSource); 44 | context.HttpContext.Items.Add(nameof(ObjectSchema), objectSchema); 45 | 46 | return await next(context); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Filters/SqlApiSchemaEndpointFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using TypeCache.Data; 6 | using TypeCache.Extensions; 7 | 8 | namespace TypeCache.Web.Filters; 9 | 10 | public sealed class SqlApiSchemaEndpointFilter 11 | : IEndpointFilter 12 | { 13 | public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) 14 | { 15 | var routeValues = context.HttpContext.Request.RouteValues; 16 | 17 | string source = (string)routeValues[nameof(source)]!; 18 | string database = (string)routeValues[nameof(database)]!; 19 | string collection = (string)routeValues[nameof(collection)]!; 20 | 21 | await using var serviceScope = context.HttpContext.RequestServices.CreateAsyncScope(); 22 | var dataSource = serviceScope.ServiceProvider.GetKeyedService(source); 23 | if (dataSource is null) 24 | return Results.NotFound(Invariant($"{nameof(IDataSource)} was not found: {source}.")); 25 | 26 | if (!dataSource.Databases.ContainsIgnoreCase(database)) 27 | return Results.NotFound(Invariant($"Database {database} was not found in Data Source: {source}.")); 28 | 29 | if (!Enum.TryParse(collection, out _)) 30 | return Results.BadRequest(Invariant($"{collection} is not a {nameof(SchemaCollection)} value.")); 31 | 32 | context.HttpContext.Items.Add(nameof(IDataSource), dataSource); 33 | 34 | return await next(context); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Filters/SqlApiTableEndpointFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using TypeCache.Data; 6 | using TypeCache.Data.Extensions; 7 | using TypeCache.Extensions; 8 | 9 | namespace TypeCache.Web.Filters; 10 | 11 | public sealed class SqlApiTableEndpointFilter 12 | : IEndpointFilter 13 | { 14 | public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) 15 | { 16 | var routeValues = context.HttpContext.Request.RouteValues; 17 | 18 | string source = (string)routeValues[nameof(source)]!; 19 | string database = (string)routeValues[nameof(database)]!; 20 | string schema = (string)routeValues[nameof(schema)]!; 21 | string table = (string)routeValues[nameof(table)]!; 22 | 23 | await using var serviceScope = context.HttpContext.RequestServices.CreateAsyncScope(); 24 | var dataSource = serviceScope.ServiceProvider.GetKeyedService(source); 25 | if (dataSource is null) 26 | return Results.NotFound(Invariant($"{nameof(IDataSource)} was not found: {source}.")); 27 | 28 | if (!dataSource.Databases.ContainsIgnoreCase(database)) 29 | return Results.NotFound(Invariant($"Database {database} was not found in Data Source: {source}.")); 30 | 31 | var objectName = dataSource.Type switch 32 | { 33 | DataSourceType.MySql => Invariant($"{database.EscapeIdentifier(dataSource.Type)}.{table.EscapeIdentifier(dataSource.Type)}"), 34 | _ => Invariant($"{database.EscapeIdentifier(dataSource.Type)}.{schema.EscapeIdentifier(dataSource.Type)}.{table.EscapeIdentifier(dataSource.Type)}") 35 | }; 36 | var objectSchema = dataSource[objectName]; 37 | if (objectSchema is null) 38 | return Results.NotFound(Invariant($"{nameof(ObjectSchema)} for {objectName} was not found.")); 39 | 40 | if (objectSchema.Type is not DatabaseObjectType.Table && objectSchema.Type is not DatabaseObjectType.View) 41 | return Results.BadRequest(Invariant($"{objectName} is not a {DatabaseObjectType.Table.Name()} or {DatabaseObjectType.View.Name()}.")); 42 | 43 | context.HttpContext.Items.Add(nameof(IDataSource), dataSource); 44 | context.HttpContext.Items.Add(nameof(ObjectSchema), objectSchema); 45 | 46 | return await next(context); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/TypeCache.Web/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Buffers.Text; 3 | global using System.Diagnostics; 4 | global using System.Diagnostics.CodeAnalysis; 5 | global using static System.FormattableString; 6 | global using static System.Runtime.CompilerServices.MethodImplOptions; 7 | global using CancellationToken = System.Threading.CancellationToken; 8 | global using MethodImplAttribute = System.Runtime.CompilerServices.MethodImplAttribute; 9 | global using Task = System.Threading.Tasks.Task; 10 | global using ValueTask = System.Threading.Tasks.ValueTask; 11 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Handlers/ClaimAuthorizationHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | using Microsoft.AspNetCore.Authorization; 5 | using TypeCache.Extensions; 6 | using TypeCache.Web.Attributes; 7 | using TypeCache.Web.Extensions; 8 | using TypeCache.Web.Requirements; 9 | 10 | namespace TypeCache.Web.Handlers; 11 | 12 | public class ClaimAuthorizationHandler : AuthorizationHandler 13 | { 14 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimAuthorizationRequirement requirement) 15 | => context.GetControllerActionDescriptor() switch 16 | { 17 | null => Task.CompletedTask, 18 | var controller => Task.Run(() => 19 | { 20 | var attributes = controller.ControllerTypeInfo.GetCustomAttributes() 21 | .Concat(controller.MethodInfo.GetCustomAttributes()); 22 | 23 | var success = attributes.All(_ => context.User.Any(_.Claim, _.AllowedValues)); 24 | if (success) 25 | context.Succeed(requirement); 26 | else 27 | context.Fail(); 28 | }) 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Handlers/HeaderAuthorizationHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Http; 6 | using TypeCache.Extensions; 7 | using TypeCache.Web.Attributes; 8 | using TypeCache.Web.Extensions; 9 | using TypeCache.Web.Requirements; 10 | 11 | namespace TypeCache.Web.Handlers; 12 | 13 | public class HeaderAuthorizationHandler(IHttpContextAccessor httpContextAccessor) : AuthorizationHandler 14 | { 15 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HeaderAuthorizationRequirement requirement) 16 | => context.GetControllerActionDescriptor() switch 17 | { 18 | null => Task.CompletedTask, 19 | var controller => Task.Run(() => 20 | { 21 | var headers = httpContextAccessor.HttpContext!.Request.Headers; 22 | var attributes = controller.ControllerTypeInfo.GetCustomAttributes() 23 | .Concat(controller.MethodInfo.GetCustomAttributes()); 24 | 25 | var success = attributes.All(_ => _.AllowedValues.Length is 0 26 | ? headers.ContainsKey(_.Header) 27 | : headers.TryGetValue(_.Header, out var values) && values.Any(_.AllowedValues.ContainsIgnoreCase!)); 28 | if (success) 29 | context.Succeed(requirement); 30 | else 31 | context.Fail(); 32 | }, httpContextAccessor.HttpContext!.RequestAborted) 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Handlers/SelectParameter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using TypeCache.Data; 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.Web.Handlers; 6 | 7 | public sealed class SelectParameter : SelectQuery 8 | { 9 | public static ValueTask BindAsync(HttpContext context) 10 | => ValueTask.FromResult(new SelectParameter 11 | { 12 | Distinct = context.Request.Query.TryGetValue("distinct", out var distinct), 13 | DistinctOn = distinct.ToString(), 14 | Fetch = context.Request.Query.TryGetValue("fetch", out var fetch) ? uint.Parse(fetch.ToString()) : default, 15 | From = context.Request.Query.TryGetValue("from", out var from) ? from : context.Request.RouteValues["table"]!.ToString(), 16 | GroupBy = context.Request.Query.TryGetValue("groupBy", out var groupBy) ? groupBy.ToString() : null, 17 | Having = context.Request.Query.TryGetValue("having", out var having) ? having.ToString() : null, 18 | Offset = context.Request.Query.TryGetValue("offset", out var offset) ? uint.Parse(offset.ToString()) : default, 19 | OrderBy = context.Request.Query.TryGetValue("orderBy", out var orderBy) ? orderBy.ToString().SplitEx(',') : null, 20 | Select = context.Request.Query.TryGetValue("select", out var select) ? select.ToString().SplitEx(',') : null, 21 | TableHints = context.Request.Query.TryGetValue("hints", out var hints) ? hints.ToString() : null, 22 | Top = context.Request.Query.TryGetValue("top", out var top) ? uint.Parse(top.ToString().TrimEnd('%')) : null, 23 | TopPercent = top.ToString().EndsWith('%') is true, 24 | Where = context.Request.Query.TryGetValue("where", out var where) ? where.ToString() : null 25 | })!; 26 | } 27 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Mediation/HttpClientRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Mediation; 4 | 5 | namespace TypeCache.Web.Mediation; 6 | 7 | public sealed class HttpClientRequest : IRequest 8 | { 9 | public required HttpRequestMessage Message { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Mediation/HttpClientRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.Extensions.Logging; 4 | using TypeCache.Mediation; 5 | 6 | namespace TypeCache.Web.Mediation; 7 | 8 | internal sealed class HttpClientRule(HttpClient httpClient, ILogger? logger = null) 9 | : IRule 10 | { 11 | public async Task Map(HttpClientRequest request, CancellationToken token = default) 12 | { 13 | logger?.LogInformation(Invariant($"{{{nameof(request.Message.Method)}}} {{{nameof(request.Message.RequestUri)}}}"), 14 | [request.Message.Method.Method, request.Message.RequestUri]); 15 | 16 | try 17 | { 18 | var httpResponse = await httpClient.SendAsync(request.Message, HttpCompletionOption.ResponseContentRead, token); 19 | await httpResponse.Content.LoadIntoBufferAsync(); 20 | 21 | var logLevel = httpResponse.IsSuccessStatusCode ? LogLevel.Information : LogLevel.Error; 22 | logger?.Log(logLevel, Invariant($"{{{nameof(request.Message.Method)}}} {{{nameof(request.Message.RequestUri)}}} - {{{nameof(httpResponse.StatusCode)}}}"), 23 | request.Message.Method.Method, request.Message.RequestUri, httpResponse.StatusCode); 24 | 25 | return httpResponse; 26 | } 27 | catch (Exception error) 28 | { 29 | logger?.LogError(error, Invariant($"{{{nameof(request.Message.Method)}}} {{{nameof(request.Message.RequestUri)}}} - {{ErrorMessage}}"), 30 | request.Message.Method.Method, request.Message.RequestUri, error.Message); 31 | 32 | throw; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Mediation/HttpClientValidationRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | using TypeCache.Mediation; 5 | 6 | namespace TypeCache.Web.Mediation; 7 | 8 | internal sealed class HttpClientValidationRule 9 | : IValidationRule 10 | { 11 | public Task Validate(HttpClientRequest request, CancellationToken token) => Task.Run(() => 12 | { 13 | request.ThrowIfNull(); 14 | request.Message.ThrowIfNull(); 15 | request.Message.RequestUri.ThrowIfNull(); 16 | request.Message.RequestUri.IsAbsoluteUri.ThrowIfFalse(); 17 | }, token); 18 | } 19 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Middleware/SqlApiErrorHandlerMiddleware.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using TypeCache.Extensions; 9 | using static System.Net.Mime.MediaTypeNames; 10 | 11 | namespace TypeCache.Web.Middleware; 12 | 13 | public class SqlApiErrorHandlerMiddleware(ILogger? logger = null 14 | , [FromKeyedServices(nameof(SqlApiErrorHandlerMiddleware))] JsonSerializerOptions? jsonSerializerOptions = null) 15 | : IMiddleware 16 | { 17 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 18 | { 19 | try 20 | { 21 | context.Response.ContentType ??= Application.Json; 22 | await next.Invoke(context); 23 | } 24 | catch (AggregateException aggregateException) 25 | { 26 | logger?.LogAggregateException(aggregateException, "{Middleware} Error", [nameof(SqlApiErrorHandlerMiddleware)]); 27 | 28 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 29 | var message = aggregateException.InnerException is not null 30 | ? aggregateException.InnerException.Message 31 | : string.Join(", ", aggregateException.InnerExceptions.Select(_ => _.Message)); 32 | await JsonSerializer.SerializeAsync(context.Response.Body, new ProblemDetails 33 | { 34 | Detail = message, 35 | Status = context.Response.StatusCode, 36 | Title = nameof(SqlApiErrorHandlerMiddleware) 37 | }, jsonSerializerOptions, context.RequestAborted); 38 | } 39 | catch (Exception exception) 40 | { 41 | logger?.LogError(exception, "{Middleware} Error", [nameof(SqlApiErrorHandlerMiddleware)]); 42 | 43 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 44 | await JsonSerializer.SerializeAsync(context.Response.Body, new ProblemDetails 45 | { 46 | Detail = exception.Message, 47 | Status = context.Response.StatusCode, 48 | Title = nameof(SqlApiErrorHandlerMiddleware) 49 | }, jsonSerializerOptions, context.RequestAborted); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:53413/", 7 | "sslPort": 44344 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "TypeCache.Web": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/TypeCache.Web/README.md: -------------------------------------------------------------------------------- 1 | ># TypeCache - Web 2 | ###### sam987883@gmail.com 3 | 4 | [**Source Code**](https://github.com/sam987883/TypeCache/tree/master/src/TypeCache.GraphQL) 5 | 6 | [Request Features (or report a bug) (if any)](https://github.com/sam987883/TypeCache/issues) 7 | 8 | --- 9 | # SQL Web API Extensions 10 | --- 11 | ### `Microsoft.Extensions.DependencyInjection.IServiceCollection` - Configure SQL API: 12 | - `ConfigureSqlApi(...)` 13 | --- 14 | ### `Microsoft.AspNetCore.Routing.IEndpointRouteBuilder` - API Endpoints: 15 | - `MapSqlApi(...)` 16 | - `MapSqlApiCallProcedure(...)` 17 | - `MapSqlApiDelete(...)` 18 | - `MapSqlApiDeleteBatch(...)` 19 | - `MapSqlApiInsert(...)` 20 | - `MapSqlApiInsertBatch(...)` 21 | - `MapSqlApiSchemaGet(...)` 22 | - `MapSqlApiSelectTable(...)` 23 | - `MapSqlApiSelectView(...)` 24 | - `MapSqlApiUpdate(...)` 25 | - `MapSqlApiUpdateBatch(...)` 26 | --- 27 | ### `Microsoft.AspNetCore.Routing.IEndpointRouteBuilder` - Get SQL Endpoints: 28 | - `MapSqlGetSQL(...)` 29 | - `MapSqlApiGetDeleteSQL(...)` 30 | - `MapSqlApiGetDeleteBatchSQL(...)` 31 | - `MapSqlApiGetInsertSQL(...)` 32 | - `MapSqlApiGetInsertBatchSQL(...)` 33 | - `MapSqlApiGetSelectTableSQL(...)` 34 | - `MapSqlApiGetSelectViewSQL(...)` 35 | - `MapSqlApiGetUpdateSQL(...)` 36 | - `MapSqlApiGetUpdateBatchSQL(...)` 37 | --- 38 | ## Helpful Classes (ideas welcome for more!) 39 | 40 | - `TypeCache.Web.Attributes.RequireClaimAttribute` 41 | - `TypeCache.Web.Attributes.RequireHeaderAttribute` 42 | - `TypeCache.Web.Handlers.ClaimAuthorizationHandler` 43 | - `TypeCache.Web.Handlers.HeaderAuthorizationHandler` 44 | - `TypeCache.Web.Middleware.WebMiddleware` 45 | - `TypeCache.Web.Requirements.ClaimAuthorizationRequirement` 46 | - `TypeCache.Web.Requirements.HeaderAuthorizationRequirement` 47 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Requirements/ClaimAuthorizationRequirement.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Authorization; 4 | 5 | namespace TypeCache.Web.Requirements; 6 | 7 | public class ClaimAuthorizationRequirement : IAuthorizationRequirement 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/TypeCache.Web/Requirements/HeaderAuthorizationRequirement.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.AspNetCore.Authorization; 4 | 5 | namespace TypeCache.Web.Requirements; 6 | 7 | public class HeaderAuthorizationRequirement : IAuthorizationRequirement 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/TypeCache.Web/TypeCache.Web.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | TypeCache.Web 7 | TypeCache.Web 8 | 9.1.2 9 | Samuel Abraham <sam987883@gmail.com> 10 | Samuel Abraham <sam987883@gmail.com> 11 | TypeCache Web Library 12 | Web API access to SQL database data operations: allows the front-end to decide how to retrieve and manipulate data. 13 | Copyright (c) 2021 Samuel Abraham 14 | TypeCache 15 | false 16 | False 17 | MIT 18 | https://www.nuget.org/packages/TypeCache.Web/ 19 | https://github.com/sam987883/TypeCache/ 20 | git 21 | en-US 22 | TypeCash.png 23 | README.md 24 | enable 25 | 26 | 27 | 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | True 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | True 49 | \ 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/TypeCache/Attributes/MapAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.Attributes; 6 | 7 | /// 8 | /// Use this to override the property of a type where the current property gets mapped to. 9 | /// 10 | public abstract class MapAttribute : Attribute 11 | { 12 | public MapAttribute(Type type, string property) 13 | { 14 | var propertyInfo = type.GetPropertyInfo(property); 15 | propertyInfo.ThrowIfNull(); 16 | propertyInfo.CanWrite.ThrowIfFalse(); 17 | 18 | this.Property = property; 19 | this.Type = type; 20 | } 21 | 22 | public string Property { get; } 23 | 24 | public Type Type { get; } 25 | } 26 | 27 | /// 28 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)] 29 | public sealed class MapAttribute(string property) : MapAttribute(typeof(T), property) 30 | { 31 | } 32 | -------------------------------------------------------------------------------- /src/TypeCache/Attributes/MapIgnoreAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.Attributes; 6 | 7 | /// 8 | /// Do not map this property. 9 | /// 10 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] 11 | public class MapIgnoreAttribute() : Attribute 12 | { 13 | protected MapIgnoreAttribute(Type type) 14 | : this() 15 | { 16 | type.ThrowIfNull(); 17 | 18 | this.Type = type; 19 | } 20 | 21 | public Type? Type { get; } 22 | } 23 | 24 | /// 25 | /// Do not map this property to type . 26 | /// 27 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)] 28 | public sealed class MapIgnoreAttribute() : MapIgnoreAttribute(typeof(T)) 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/TypeCache/Attributes/ScopedAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace TypeCache.Attributes; 6 | 7 | /// 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 9 | public sealed class ScopedAttribute(object? key = null) : ServiceLifetimeAttribute(ServiceLifetime.Scoped, null, key) 10 | { 11 | } 12 | 13 | /// 14 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 15 | public sealed class ScopedAttribute(object? key = null) : ServiceLifetimeAttribute(ServiceLifetime.Scoped, typeof(T), key) 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/TypeCache/Attributes/ServiceLifetimeAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace TypeCache.Attributes; 6 | 7 | public abstract class ServiceLifetimeAttribute(ServiceLifetime serviceLifetime, Type? serviceType = null, object? key = null) : Attribute 8 | { 9 | public object? Key { get; } = key; 10 | 11 | public ServiceLifetime ServiceLifetime { get; } = serviceLifetime; 12 | 13 | public Type? ServiceType { get; } = serviceType; 14 | } 15 | -------------------------------------------------------------------------------- /src/TypeCache/Attributes/SingletonAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace TypeCache.Attributes; 6 | 7 | /// 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 9 | public sealed class SingletonAttribute(object? key = null) : ServiceLifetimeAttribute(ServiceLifetime.Singleton, null, key) 10 | { 11 | } 12 | 13 | /// 14 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 15 | public sealed class SingletonAttribute(object? key = null) : ServiceLifetimeAttribute(ServiceLifetime.Singleton, typeof(T), key) 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/TypeCache/Attributes/SqlApiAction.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Attributes; 4 | 5 | [Flags] 6 | public enum SqlApiAction 7 | { 8 | None, 9 | Select = 1, 10 | Delete = 2, 11 | DeleteData = 4, 12 | Insert = 8, 13 | InsertData = 16, 14 | Update = 32, 15 | UpdateData = 64, 16 | CRUD = Select | Delete | DeleteData | Insert | InsertData | Update | UpdateData, 17 | Truncate = 128, 18 | All = CRUD | Truncate 19 | } 20 | -------------------------------------------------------------------------------- /src/TypeCache/Attributes/SqlApiAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Attributes; 4 | 5 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 6 | public sealed class SqlApiAttribute : Attribute 7 | { 8 | public SqlApiAction Actions { get; } = SqlApiAction.All; 9 | } 10 | -------------------------------------------------------------------------------- /src/TypeCache/Attributes/TransientAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace TypeCache.Attributes; 6 | 7 | /// 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 9 | public sealed class TransientAttribute(object? key = null) : ServiceLifetimeAttribute(ServiceLifetime.Transient, null, key) 10 | { 11 | } 12 | 13 | /// 14 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 15 | public sealed class TransientAttribute(object? key = null) : ServiceLifetimeAttribute(ServiceLifetime.Transient, typeof(T), key) 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/BigIntegerJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Globalization; 4 | using System.Numerics; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | using TypeCache.Extensions; 8 | 9 | namespace TypeCache.Converters; 10 | 11 | public sealed class BigIntegerJsonConverter : JsonConverter 12 | { 13 | public override BigInteger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 14 | { 15 | reader.TokenType.ThrowIfNotEqual(JsonTokenType.Number); 16 | 17 | using var doc = JsonDocument.ParseValue(ref reader); 18 | return BigInteger.Parse(doc.RootElement.GetRawText(), NumberFormatInfo.InvariantInfo); 19 | } 20 | 21 | public override void Write(Utf8JsonWriter writer, BigInteger value, JsonSerializerOptions options) 22 | { 23 | var text = value.ToString(NumberFormatInfo.InvariantInfo); 24 | using var doc = JsonDocument.Parse(text); 25 | doc.WriteTo(writer); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/DataRowJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using TypeCache.Extensions; 7 | 8 | namespace TypeCache.Converters; 9 | 10 | public sealed class DataRowJsonConverter : JsonConverter 11 | { 12 | /// 13 | public override DataRow? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 14 | => throw new NotImplementedException("Cannot deserialize DataRow."); 15 | 16 | public override void Write(Utf8JsonWriter writer, DataRow row, JsonSerializerOptions options) 17 | { 18 | if (row is null) 19 | { 20 | writer.WriteNullValue(); 21 | return; 22 | } 23 | 24 | writer.WriteStartObject(); 25 | row.Table.Columns.OfType().ForEach(column => 26 | { 27 | var value = row[column]; 28 | if (value is DBNull || value is null) 29 | writer.WriteNull(column.ColumnName); 30 | else 31 | { 32 | writer.WritePropertyName(column.ColumnName); 33 | writer.WriteValue(row[column], options); 34 | } 35 | }); 36 | writer.WriteEndObject(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/DataSetJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using TypeCache.Extensions; 7 | 8 | namespace TypeCache.Converters; 9 | 10 | public sealed class DataSetJsonConverter : JsonConverter 11 | { 12 | public override DataSet? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | { 14 | if (reader.TokenType is JsonTokenType.Null) 15 | return null; 16 | 17 | if (reader.TokenType is JsonTokenType.StartObject) 18 | { 19 | options.Converters.Add(new DataTableJsonConverter()); 20 | var dataSet = new DataSet(); 21 | do 22 | { 23 | if (reader.Read() && reader.TokenType is JsonTokenType.PropertyName) 24 | { 25 | var tableName = reader.GetString(); 26 | if (reader.Read()) 27 | { 28 | var table = JsonSerializer.Deserialize(ref reader, options) ?? new DataTable(tableName); 29 | table.TableName = tableName; 30 | dataSet.Tables.Add(table); 31 | } 32 | else 33 | break; 34 | } 35 | else 36 | break; 37 | } while (reader.Read() && reader.TokenType is not JsonTokenType.EndObject); 38 | 39 | return dataSet; 40 | } 41 | 42 | throw new ArgumentOutOfRangeException(Invariant($"{nameof(reader)}.{nameof(reader.TokenType)}"), Invariant($"Cannot deserialize {nameof(DataSet)} from starting token of: {reader.TokenType}.")); 43 | } 44 | 45 | public override void Write(Utf8JsonWriter writer, DataSet dataSet, JsonSerializerOptions options) 46 | { 47 | if (dataSet is null) 48 | { 49 | writer.WriteNullValue(); 50 | return; 51 | } 52 | 53 | options.Converters.Add(new DataTableJsonConverter()); 54 | 55 | writer.WriteStartObject(); 56 | 57 | if (dataSet.DataSetName.IsNotBlank()) 58 | writer.WriteString(nameof(dataSet.DataSetName), dataSet.DataSetName); 59 | 60 | var i = -1; 61 | foreach (var table in dataSet.Tables.OfType()) 62 | { 63 | writer.WritePropertyName(table.TableName.IsNotBlank() ? table.TableName : Invariant($"Table{++i + 1}")); 64 | JsonSerializer.Serialize(writer, table, options); 65 | } 66 | 67 | writer.WriteEndObject(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/DataViewJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using TypeCache.Extensions; 7 | 8 | namespace TypeCache.Converters; 9 | 10 | public sealed class DataViewJsonConverter : JsonConverter 11 | { 12 | public override DataView? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | => throw new NotImplementedException($"Cannot deserialize {nameof(DataView)} at all."); 14 | 15 | public override void Write(Utf8JsonWriter writer, DataView view, JsonSerializerOptions options) 16 | { 17 | if (view?.Table is null) 18 | { 19 | writer.WriteNullValue(); 20 | return; 21 | } 22 | 23 | writer.WriteStartArray(); 24 | var columns = view.Table.Columns.OfType().Select(column => column.ColumnName).ToArray(); 25 | foreach (var row in view.OfType()) 26 | { 27 | writer.WriteStartObject(); 28 | columns.ForEach(column => 29 | { 30 | var value = row[column]; 31 | if (value is DBNull || value is null) 32 | writer.WriteNull(column); 33 | else 34 | { 35 | writer.WritePropertyName(column); 36 | writer.WriteValue(row[column], options); 37 | } 38 | }); 39 | writer.WriteEndObject(); 40 | } 41 | 42 | writer.WriteEndArray(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/DictionaryJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using TypeCache.Extensions; 6 | 7 | namespace TypeCache.Converters; 8 | 9 | public sealed class DictionaryJsonConverter : JsonConverter> 10 | { 11 | public override IDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 12 | { 13 | var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); 14 | 15 | if (reader.TokenType is JsonTokenType.StartObject) 16 | { 17 | while (reader.Read() && reader.TokenType is JsonTokenType.PropertyName) 18 | { 19 | var name = reader.GetString()!; 20 | if (reader.Read()) 21 | dictionary.Add(name, reader.GetValue()); 22 | } 23 | } 24 | 25 | return dictionary; 26 | } 27 | 28 | public override void Write(Utf8JsonWriter writer, IDictionary dictionary, JsonSerializerOptions options) 29 | { 30 | if (dictionary.Any()) 31 | { 32 | writer.WriteStartObject(); 33 | foreach (var pair in dictionary) 34 | { 35 | writer.WritePropertyName(pair.Key); 36 | writer.WriteValue(pair.Value, options); 37 | } 38 | writer.WriteEndObject(); 39 | } 40 | else 41 | writer.WriteNullValue(); 42 | } 43 | } 44 | 45 | public class DictionaryJsonConverter : JsonConverter> 46 | where T : struct, Enum 47 | { 48 | public override IDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 49 | { 50 | var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); 51 | 52 | if (reader.TokenType is JsonTokenType.StartObject) 53 | { 54 | while (reader.Read() && reader.TokenType is JsonTokenType.PropertyName) 55 | { 56 | var name = reader.GetString()!; 57 | if (reader.Read()) 58 | { 59 | var tokenName = reader.GetString()!; 60 | dictionary.Add(name, Enum.Parse(tokenName, true)); 61 | } 62 | } 63 | } 64 | 65 | return dictionary; 66 | } 67 | 68 | public override void Write(Utf8JsonWriter writer, IDictionary dictionary, JsonSerializerOptions options) 69 | { 70 | if (dictionary.Any()) 71 | { 72 | writer.WriteStartObject(); 73 | foreach (var pair in dictionary) 74 | { 75 | writer.WritePropertyName(pair.Key); 76 | writer.WriteValue(pair.Value, options); 77 | } 78 | writer.WriteEndObject(); 79 | } 80 | else 81 | writer.WriteNullValue(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/FieldJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using TypeCache.Extensions; 6 | 7 | namespace TypeCache.Converters; 8 | 9 | public sealed class FieldJsonConverter : JsonConverter 10 | where T : class, new() 11 | { 12 | public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | { 14 | if (reader.TokenType is JsonTokenType.StartObject) 15 | { 16 | var output = (T)typeof(T).Create()!; 17 | while (reader.Read() && reader.TokenType is JsonTokenType.PropertyName) 18 | { 19 | var name = reader.GetString()!; 20 | if (reader.Read()) 21 | { 22 | var fieldInfo = typeof(T).GetPublicFields().FirstOrDefault(_ => _.Name().EqualsIgnoreCase(name)); 23 | if (fieldInfo is not null && !fieldInfo.IsInitOnly) 24 | fieldInfo.SetValueEx(output!, reader.TokenType switch 25 | { 26 | JsonTokenType.StartObject or JsonTokenType.StartArray => JsonSerializer.Deserialize(ref reader, fieldInfo.FieldType, options), 27 | _ => reader.GetValue() 28 | }); 29 | } 30 | } 31 | 32 | return output; 33 | } 34 | 35 | return null; 36 | } 37 | 38 | public override void Write(Utf8JsonWriter writer, T? input, JsonSerializerOptions options) 39 | { 40 | if (input is not null) 41 | { 42 | writer.WriteStartObject(); 43 | foreach (var field in typeof(T).GetPublicFields()) 44 | { 45 | writer.WritePropertyName(field!.Name()); 46 | var value = field.GetValueEx(input); 47 | writer.WriteValue(value, options); 48 | } 49 | writer.WriteEndObject(); 50 | } 51 | else 52 | writer.WriteNullValue(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/PropertyJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using TypeCache.Extensions; 6 | 7 | namespace TypeCache.Reflection.Converters; 8 | 9 | public sealed class PropertyJsonConverter : JsonConverter where T : class, new() 10 | { 11 | public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 12 | { 13 | if (reader.TokenType is JsonTokenType.StartObject) 14 | { 15 | var output = (T)typeof(T).Create()!; 16 | while (reader.Read() && reader.TokenType is JsonTokenType.PropertyName) 17 | { 18 | var name = reader.GetString(); 19 | if (!reader.Read() || name.IsBlank()) 20 | continue; 21 | 22 | typeof(T).SetPropertyValue(name, output, reader.TokenType switch 23 | { 24 | JsonTokenType.StartObject or JsonTokenType.StartArray => JsonSerializer.Deserialize(ref reader, typeof(T).GetProperty(name)!.PropertyType, options), 25 | _ => reader.GetValue() 26 | }); 27 | } 28 | 29 | return output; 30 | } 31 | 32 | return null; 33 | } 34 | 35 | public override void Write(Utf8JsonWriter writer, T? input, JsonSerializerOptions options) 36 | { 37 | if (input is null) 38 | { 39 | writer.WriteNullValue(); 40 | return; 41 | } 42 | 43 | writer.WriteStartObject(); 44 | foreach (var property in typeof(T).GetPublicProperties()) 45 | { 46 | writer.WritePropertyName(property.Name()); 47 | var value = property.GetValueEx(input); 48 | writer.WriteValue(value, options); 49 | } 50 | writer.WriteEndObject(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/StringValuesJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using Microsoft.Extensions.Primitives; 6 | using TypeCache.Extensions; 7 | 8 | namespace TypeCache.Converters; 9 | 10 | public sealed class StringValuesJsonConverter : JsonConverter 11 | { 12 | public override StringValues Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | { 14 | if (reader.TokenType is JsonTokenType.StartArray) 15 | { 16 | var list = new List(0); 17 | while (reader.Read() && reader.TokenType is not JsonTokenType.EndArray) 18 | list.Add(reader.GetString()); 19 | 20 | return new(list.ToArray()); 21 | } 22 | 23 | return new(reader.GetString()); 24 | } 25 | 26 | public override void Write(Utf8JsonWriter writer, StringValues value, JsonSerializerOptions options) 27 | { 28 | if (value.Count > 1) 29 | { 30 | writer.WriteStartArray(); 31 | value.ToArray().ForEach(writer.WriteStringValue); 32 | writer.WriteEndArray(); 33 | return; 34 | } 35 | 36 | var text = (string?)value; 37 | if (text is not null) 38 | writer.WriteStringValue(text); 39 | else 40 | writer.WriteNullValue(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/TypeCache/Converters/ValueJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using TypeCache.Extensions; 6 | 7 | namespace TypeCache.Converters; 8 | 9 | public sealed class ValueJsonConverter : JsonConverter 10 | { 11 | [MethodImpl(AggressiveInlining), DebuggerHidden] 12 | public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | => reader.GetValue(); 14 | 15 | [MethodImpl(AggressiveInlining), DebuggerHidden] 16 | public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) 17 | => JsonSerializer.Serialize(writer, value, options); 18 | } 19 | -------------------------------------------------------------------------------- /src/TypeCache/Data/ColumnSchema.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.Data; 6 | 7 | public sealed class ColumnSchema(string name, bool nullable, bool primaryKey, bool readOnly, bool unique, RuntimeTypeHandle dataTypeHandle) : IEquatable 8 | { 9 | public string Name { get; } = name; 10 | 11 | public bool Nullable { get; } = nullable; 12 | 13 | public bool PrimaryKey { get; } = primaryKey; 14 | 15 | public bool ReadOnly { get; } = readOnly; 16 | 17 | public bool Unique { get; } = unique; 18 | 19 | public RuntimeTypeHandle DataTypeHandle { get; } = dataTypeHandle; 20 | 21 | public bool Equals(ColumnSchema? other) 22 | => this.Name.EqualsIgnoreCase(other?.Name); 23 | 24 | public override bool Equals(object? other) 25 | => this.Equals(other as ColumnSchema); 26 | 27 | public override int GetHashCode() 28 | => this.Name.GetHashCode(); 29 | } 30 | -------------------------------------------------------------------------------- /src/TypeCache/Data/ComparisonOperator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace TypeCache.Data; 6 | 7 | [JsonConverter(typeof(JsonStringEnumConverter))] 8 | public enum ComparisonOperator 9 | { 10 | /// 11 | /// = 12 | /// 13 | Equal, 14 | /// 15 | /// <> 16 | /// 17 | NotEqual, 18 | /// 19 | /// > 20 | /// 21 | MoreThan, 22 | /// 23 | /// >= 24 | /// 25 | MoreThanOrEqual, 26 | /// 27 | /// < 28 | /// 29 | LessThan, 30 | /// 31 | /// <= 32 | /// 33 | LessThanOrEqual, 34 | /// 35 | /// LIKE N'Value%' 36 | /// 37 | StartWith, 38 | /// 39 | /// NOT LIKE N'Value%' 40 | /// 41 | NotStartWith, 42 | /// 43 | /// LIKE N'%Value' 44 | /// 45 | EndWith, 46 | /// 47 | /// NOT LIKE N'%Value' 48 | /// 49 | NotEndWith, 50 | /// 51 | /// LIKE N'%Value%' 52 | /// 53 | Like, 54 | /// 55 | /// NOT LIKE N'%Value%' 56 | /// 57 | NotLike 58 | } 59 | -------------------------------------------------------------------------------- /src/TypeCache/Data/DataSourceType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Data; 4 | 5 | public enum DataSourceType 6 | { 7 | Unknown = 0, 8 | SqlServer, 9 | Oracle, 10 | PostgreSql, 11 | MySql 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache/Data/DatabaseObjectType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace TypeCache.Data; 6 | 7 | [JsonConverter(typeof(JsonStringEnumConverter))] 8 | public enum DatabaseObjectType 9 | { 10 | Table = 1, 11 | View, 12 | Function, 13 | StoredProcedure, 14 | TableType 15 | } 16 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Extensions/DbConnectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data.Common; 4 | 5 | namespace TypeCache.Data.Extensions; 6 | 7 | public static class DbConnectionExtensions 8 | { 9 | public static DbCommand CreateCommand(this DbConnection @this, SqlCommand sqlCommand) 10 | { 11 | var dbCommand = @this.CreateCommand(); 12 | dbCommand.CommandType = sqlCommand.Type; 13 | dbCommand.CommandText = sqlCommand.ToSQL(); 14 | if (sqlCommand.Timeout.HasValue) 15 | dbCommand.CommandTimeout = (int)sqlCommand.Timeout.Value.TotalSeconds; 16 | 17 | foreach (var pair in sqlCommand.Parameters) 18 | dbCommand.AddInputParameter(pair.Key, pair.Value); 19 | 20 | foreach (var pair in sqlCommand.OutputParameters) 21 | dbCommand.AddOutputParameter(pair.Key, pair.Value); 22 | 23 | return dbCommand; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Extensions/DbProviderFactoryExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data.Common; 4 | 5 | namespace TypeCache.Data.Extensions; 6 | 7 | public static class DbProviderFactoryExtensions 8 | { 9 | public static DbConnection CreateConnection(this DbProviderFactory @this, string connectionString) 10 | { 11 | var dbConnection = @this.CreateConnection()!; 12 | dbConnection.ConnectionString = connectionString; 13 | return dbConnection; 14 | } 15 | 16 | public static DbConnectionStringBuilder CreateConnectionStringBuilder(this DbProviderFactory @this, string connectionString) 17 | { 18 | var dbConnectionStringBuilder = @this.CreateConnectionStringBuilder()!; 19 | dbConnectionStringBuilder.ConnectionString = connectionString; 20 | return dbConnectionStringBuilder; 21 | } 22 | 23 | [MethodImpl(AggressiveInlining), DebuggerHidden] 24 | public static void Register(this DbProviderFactory @this, string databaseProvider) 25 | => DbProviderFactories.RegisterFactory(databaseProvider, @this); 26 | } 27 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Extensions/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using TypeCache.Extensions; 5 | 6 | namespace TypeCache.Data.Extensions; 7 | 8 | public static class EnumExtensions 9 | { 10 | [DebuggerHidden] 11 | public static bool IsSQLType(this ScalarType @this) => @this switch 12 | { 13 | ScalarType.Boolean 14 | or ScalarType.SByte or ScalarType.Byte 15 | or ScalarType.Int16 or ScalarType.Int32 or ScalarType.Int64 16 | or ScalarType.UInt16 or ScalarType.UInt32 or ScalarType.UInt64 17 | or ScalarType.Single or ScalarType.Double or ScalarType.Decimal 18 | or ScalarType.DateOnly or ScalarType.DateTime or ScalarType.DateTimeOffset 19 | or ScalarType.TimeOnly or ScalarType.TimeSpan 20 | or ScalarType.Guid or ScalarType.Char or ScalarType.String => true, 21 | _ => false 22 | }; 23 | 24 | [DebuggerHidden] 25 | public static Type ToType(this SqlDbType @this) => @this switch 26 | { 27 | SqlDbType.Bit => typeof(bool), 28 | SqlDbType.TinyInt => typeof(sbyte), 29 | SqlDbType.SmallInt => typeof(short), 30 | SqlDbType.Int => typeof(int), 31 | SqlDbType.BigInt => typeof(long), 32 | SqlDbType.Binary or SqlDbType.Image or SqlDbType.Timestamp or SqlDbType.VarBinary => typeof(byte[]), 33 | SqlDbType.Char or SqlDbType.Text or SqlDbType.VarChar or SqlDbType.NChar or SqlDbType.NText or SqlDbType.NVarChar => typeof(string), 34 | SqlDbType.Date => typeof(DateOnly), 35 | SqlDbType.DateTime or SqlDbType.DateTime2 or SqlDbType.SmallDateTime => typeof(DateTime), 36 | SqlDbType.DateTimeOffset => typeof(DateTimeOffset), 37 | SqlDbType.Time => typeof(TimeOnly), 38 | SqlDbType.Real => typeof(float), 39 | SqlDbType.Float => typeof(double), 40 | SqlDbType.Decimal or SqlDbType.Money or SqlDbType.SmallMoney => typeof(decimal), 41 | SqlDbType.UniqueIdentifier => typeof(Guid), 42 | _ => typeof(object) 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Extensions/SqlCommandExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Data.Mediation; 4 | 5 | namespace TypeCache.Data.Extensions; 6 | 7 | public static class SqlCommandExtensions 8 | { 9 | public static SqlDataSetRequest ToSqlDataSetRequest(this SqlCommand @this) 10 | => new() 11 | { 12 | Command = @this 13 | }; 14 | 15 | public static SqlDataTableRequest ToSqlDataTableRequest(this SqlCommand @this) 16 | => new() 17 | { 18 | Command = @this 19 | }; 20 | 21 | public static SqlExecuteRequest ToSqlExecuteRequest(this SqlCommand @this) 22 | => new() 23 | { 24 | Command = @this 25 | }; 26 | 27 | public static SqlResultJsonRequest ToSqlJsonArrayRequest(this SqlCommand @this) 28 | => new() 29 | { 30 | Command = @this 31 | }; 32 | 33 | public static SqlModelsRequest ToSqlModelsRequest(this SqlCommand @this, int listInitialCapacity = 0) 34 | => new() 35 | { 36 | Command = @this, 37 | ListInitialCapacity = listInitialCapacity, 38 | ModelType = typeof(T) 39 | }; 40 | 41 | public static SqlScalarRequest ToSqlScalarRequest(this SqlCommand @this) 42 | => new() 43 | { 44 | Command = @this 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Extensions/StringBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using System.Text; 5 | using Microsoft.Extensions.Primitives; 6 | using TypeCache.Extensions; 7 | using static System.Reflection.BindingFlags; 8 | using static TypeCache.Data.DataSourceType; 9 | 10 | namespace TypeCache.Data.Extensions; 11 | 12 | public static class StringBuilderExtensions 13 | { 14 | public static StringBuilder AppendOutputSQL(this StringBuilder @this, DataSourceType dataSourceType, StringValues output) 15 | => @this.AppendLineIf(output.Count is not 0, dataSourceType switch 16 | { 17 | SqlServer or PostgreSql => Invariant($"OUTPUT {output.ToCSV()}"), 18 | Oracle => Invariant($"RETURNING {output.ToCSV()}"), 19 | _ => string.Empty 20 | }); 21 | 22 | public static StringBuilder AppendStatementEndSQL(this StringBuilder @this) 23 | { 24 | while (@this[@this.Length - 1] is '\r' || @this[@this.Length - 1] is '\n') 25 | @this.Remove(@this.Length - 1, 1); 26 | 27 | return @this.Append(';').AppendLine(); 28 | } 29 | 30 | public static StringBuilder AppendValuesSQL(this StringBuilder @this, T[] input, StringValues columns) 31 | { 32 | var propertyInfos = typeof(T).GetPublicProperties(); 33 | var propertyMap = propertyInfos 34 | .Where(_ => columns.ContainsIgnoreCase(_.Name)) 35 | .ToDictionaryIgnoreCase(_ => _.Name, _ => _.GetValueFunc()); 36 | 37 | @this.Append("VALUES "); 38 | input.ForEach(row => @this.Append('(').Append(columns.Select(column => propertyMap[column!].Invoke(row!, null).ToSQL()).ToCSV()).Append(')'), 39 | () => @this.AppendLine().Append("\t, ")); 40 | 41 | return @this.AppendLine(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/TypeCache/Data/IDataSource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using System.Data.Common; 5 | 6 | namespace TypeCache.Data; 7 | 8 | public interface IDataSource : IEquatable 9 | { 10 | string ConnectionString { get; } 11 | 12 | IReadOnlySet Databases { get; } 13 | 14 | string DefaultDatabase { get; } 15 | 16 | public string DefaultSchema { get; } 17 | 18 | DbProviderFactory Factory { get; } 19 | 20 | string Name { get; } 21 | 22 | IReadOnlyDictionary ObjectSchemas { get; } 23 | 24 | public string Server { get; } 25 | 26 | IReadOnlySet SupportedMetadataCollections { get; } 27 | 28 | DataSourceType Type { get; } 29 | 30 | public string Version { get; } 31 | 32 | ObjectSchema? this[string objectName] { get; } 33 | 34 | DbConnection CreateDbConnection(); 35 | 36 | /// 37 | string Escape(string databaseObject); 38 | 39 | SqlCommand CreateSqlCommand(string sql); 40 | 41 | Task GetDatabaseSchemaAsync(string? database = null, CancellationToken token = default); 42 | 43 | Task GetDatabaseSchemaAsync(SchemaCollection collection, string? database = null, CancellationToken token = default); 44 | 45 | DataSet GetDatabaseSchema(string? database = null); 46 | 47 | DataTable GetDatabaseSchema(SchemaCollection collection, string? database = null); 48 | } 49 | -------------------------------------------------------------------------------- /src/TypeCache/Data/LogicalOperator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace TypeCache.Data; 6 | 7 | [JsonConverter(typeof(JsonStringEnumConverter))] 8 | public enum LogicalOperator 9 | { 10 | And, 11 | Or 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Mediation/SqlDataSetRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using TypeCache.Data.Extensions; 5 | using TypeCache.Mediation; 6 | 7 | namespace TypeCache.Data.Mediation; 8 | 9 | public sealed class SqlDataSetRequest : IRequest 10 | { 11 | public required SqlCommand Command { get; set; } 12 | } 13 | 14 | internal sealed class SqlDataSetRule : IRule 15 | { 16 | public async Task Map(SqlDataSetRequest request, CancellationToken token) 17 | { 18 | await using var connection = request.Command.DataSource.CreateDbConnection(); 19 | await connection.OpenAsync(token); 20 | await using var command = connection.CreateCommand(request.Command); 21 | using var adapter = request.Command.DataSource.Factory.CreateDataAdapter()!; 22 | adapter.SelectCommand = command; 23 | 24 | var result = new DataSet(); 25 | adapter.Fill(result); 26 | 27 | command.CopyOutputParameters(request.Command); 28 | 29 | return result; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Mediation/SqlDataTableRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using TypeCache.Data.Extensions; 5 | using TypeCache.Mediation; 6 | 7 | namespace TypeCache.Data.Mediation; 8 | 9 | public sealed class SqlDataTableRequest : IRequest 10 | { 11 | public required SqlCommand Command { get; set; } 12 | } 13 | 14 | internal sealed class SqlDataTableRule : IRule 15 | { 16 | public async Task Map(SqlDataTableRequest request, CancellationToken token) 17 | { 18 | await using var connection = request.Command.DataSource.CreateDbConnection(); 19 | await connection.OpenAsync(token); 20 | await using var command = connection.CreateCommand(request.Command); 21 | 22 | var result = await command.GetDataTableAsync(token); 23 | 24 | command.CopyOutputParameters(request.Command); 25 | 26 | return result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Mediation/SqlExecuteRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Data.Extensions; 4 | using TypeCache.Mediation; 5 | 6 | namespace TypeCache.Data.Mediation; 7 | 8 | public sealed class SqlExecuteRequest : IRequest 9 | { 10 | public required SqlCommand Command { get; set; } 11 | } 12 | 13 | internal sealed class SqlExecuteRule : IRule 14 | { 15 | public async Task Execute(SqlExecuteRequest request, CancellationToken token = default) 16 | { 17 | await using var connection = request.Command.DataSource.CreateDbConnection(); 18 | await connection.OpenAsync(token); 19 | await using var command = connection.CreateCommand(request.Command); 20 | 21 | await command.ExecuteNonQueryAsync(token); 22 | 23 | command.CopyOutputParameters(request.Command); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Mediation/SqlResultJsonRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json.Nodes; 4 | using TypeCache.Data.Extensions; 5 | using TypeCache.Mediation; 6 | 7 | namespace TypeCache.Data.Mediation; 8 | 9 | public sealed class SqlResultJsonRequest : IRequest 10 | { 11 | public required SqlCommand Command { get; set; } 12 | 13 | public JsonNodeOptions JsonOptions { get; set; } 14 | } 15 | 16 | internal sealed class SqlResultJsonRule : IRule 17 | { 18 | public async Task Map(SqlResultJsonRequest request, CancellationToken token) 19 | { 20 | await using var connection = request.Command.DataSource.CreateDbConnection(); 21 | await connection.OpenAsync(token); 22 | await using var command = connection.CreateCommand(request.Command); 23 | 24 | var result = await command.GetResultsAsJsonAsync(request.JsonOptions, token); 25 | 26 | command.CopyOutputParameters(request.Command); 27 | 28 | return result; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Mediation/SqlResultSetJsonRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json.Nodes; 4 | using TypeCache.Data.Extensions; 5 | using TypeCache.Mediation; 6 | 7 | namespace TypeCache.Data.Mediation; 8 | 9 | public sealed class SqlResultSetJsonRequest : IRequest 10 | { 11 | public required SqlCommand Command { get; set; } 12 | 13 | public JsonNodeOptions JsonOptions { get; set; } 14 | } 15 | 16 | internal sealed class SqlResultSetJsonRule : IRule 17 | { 18 | public async Task Map(SqlResultSetJsonRequest request, CancellationToken token) 19 | { 20 | await using var connection = request.Command.DataSource.CreateDbConnection(); 21 | await connection.OpenAsync(token); 22 | await using var command = connection.CreateCommand(request.Command); 23 | 24 | var result = await command.GetResultSetAsJsonAsync(request.JsonOptions, token); 25 | 26 | command.CopyOutputParameters(request.Command); 27 | 28 | return result; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Mediation/SqlResultsRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Data.Extensions; 4 | using TypeCache.Mediation; 5 | 6 | namespace TypeCache.Data.Mediation; 7 | 8 | public sealed class SqlModelsRequest : IRequest> 9 | { 10 | public required SqlCommand Command { get; set; } 11 | 12 | public int ListInitialCapacity { get; set; } 13 | 14 | public required Type ModelType { get; set; } 15 | } 16 | 17 | internal sealed class SqlResultsRule : IRule> 18 | { 19 | public async Task> Map(SqlModelsRequest request, CancellationToken token) 20 | { 21 | await using var connection = request.Command.DataSource.CreateDbConnection(); 22 | await connection.OpenAsync(token); 23 | await using var command = connection.CreateCommand(request.Command); 24 | 25 | var result = await command.GetModelsAsync(request.ModelType, request.ListInitialCapacity, token); 26 | request.Command.RecordsAffected = (int?)command.Parameters[nameof(request.Command.RecordsAffected)]?.Value ?? 0; 27 | 28 | command.CopyOutputParameters(request.Command); 29 | 30 | return result; 31 | } 32 | } 33 | 34 | public sealed class SqlResultsRequest : IRequest> 35 | where T : notnull, new() 36 | { 37 | public required SqlCommand Command { get; set; } 38 | 39 | public int ListInitialCapacity { get; set; } 40 | } 41 | 42 | internal sealed class SqlResultsRule : IRule, IList> 43 | where T : notnull, new() 44 | { 45 | public async Task> Map(SqlResultsRequest request, CancellationToken token) 46 | { 47 | await using var connection = request.Command.DataSource.CreateDbConnection(); 48 | await connection.OpenAsync(token); 49 | await using var command = connection.CreateCommand(request.Command); 50 | 51 | var result = await command.GetModelsAsync(request.ListInitialCapacity, token); 52 | request.Command.RecordsAffected = (int?)command.Parameters[nameof(request.Command.RecordsAffected)]?.Value ?? 0; 53 | 54 | command.CopyOutputParameters(request.Command); 55 | 56 | return result; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Mediation/SqlScalarRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Data.Extensions; 4 | using TypeCache.Mediation; 5 | 6 | namespace TypeCache.Data.Mediation; 7 | 8 | public sealed class SqlScalarRequest : IRequest 9 | { 10 | public required SqlCommand Command { get; set; } 11 | } 12 | 13 | internal sealed class SqlScalarRule : IRule 14 | { 15 | public async Task Map(SqlScalarRequest request, CancellationToken token) 16 | { 17 | await using var connection = request.Command.DataSource.CreateDbConnection(); 18 | await connection.OpenAsync(token); 19 | await using var dbCommand = connection.CreateCommand(request.Command); 20 | 21 | var result = await dbCommand.ExecuteScalarAsync(token); 22 | 23 | dbCommand.CopyOutputParameters(request.Command); 24 | 25 | return result is not DBNull ? result : null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TypeCache/Data/ParameterSchema.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using TypeCache.Extensions; 5 | 6 | namespace TypeCache.Data; 7 | 8 | public sealed class ParameterSchema(string name, ParameterDirection direction) : IEquatable 9 | { 10 | public string Name { get; } = name; 11 | 12 | public ParameterDirection Direction { get; } = direction; 13 | 14 | public bool Equals(ParameterSchema? other) 15 | => this.Name.EqualsIgnoreCase(other?.Name); 16 | 17 | public override bool Equals(object? other) 18 | => this.Equals(other as ParameterSchema); 19 | 20 | public override int GetHashCode() 21 | => this.Name.GetHashCode(); 22 | } 23 | -------------------------------------------------------------------------------- /src/TypeCache/Data/SchemaCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Data; 4 | 5 | public enum SchemaCollection 6 | { 7 | MetaDataCollections, 8 | DataSourceInformation, 9 | DataTypes, 10 | Restrictions, 11 | ReservedWords, 12 | Users, 13 | Databases, 14 | Tables, 15 | Columns, 16 | AllColumns, 17 | ColumnSetColumns, 18 | StructuredTypeMembers, 19 | Views, 20 | ViewColumns, 21 | ProcedureParameters, 22 | Procedures, 23 | ForeignKeys, 24 | IndexColumns, 25 | Indexes, 26 | UserDefinedTypes 27 | } 28 | -------------------------------------------------------------------------------- /src/TypeCache/Data/SchemaColumn.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Data; 4 | 5 | public static class SchemaColumn 6 | { 7 | public const string collectionName = nameof(collectionName); 8 | public const string column_name = nameof(column_name); 9 | public const string database_name = nameof(database_name); 10 | public const string index_name = nameof(index_name); 11 | public const string is_nullable = nameof(is_nullable); 12 | public const string ordinal_position = nameof(ordinal_position); 13 | public const string parameter_mode = nameof(parameter_mode); 14 | public const string parameter_name = nameof(parameter_name); 15 | public const string routine_name = nameof(routine_name); 16 | public const string routine_schema = nameof(routine_schema); 17 | public const string routine_type = nameof(routine_type); 18 | public const string specific_name = nameof(specific_name); 19 | public const string specific_schema = nameof(specific_schema); 20 | public const string table_name = nameof(table_name); 21 | public const string table_schema = nameof(table_schema); 22 | public const string table_type = nameof(table_type); 23 | public const string type_desc = nameof(type_desc); 24 | } 25 | -------------------------------------------------------------------------------- /src/TypeCache/Data/Sort.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace TypeCache.Data; 6 | 7 | [JsonConverter(typeof(JsonStringEnumConverter))] 8 | public enum Sort 9 | { 10 | Ascending, 11 | Descending 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache/Data/SqlCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Data; 4 | using System.Data.Common; 5 | using TypeCache.Extensions; 6 | 7 | namespace TypeCache.Data; 8 | 9 | /// 10 | public sealed class SqlCommand 11 | { 12 | internal SqlCommand(IDataSource dataSource, string sql) 13 | { 14 | dataSource.ThrowIfNull(); 15 | sql.ThrowIfBlank(); 16 | 17 | this.DataSource = dataSource; 18 | this.SQL = sql; 19 | } 20 | 21 | /// 22 | /// Associated . 23 | /// 24 | public IDataSource DataSource { get; } 25 | 26 | public IDictionary OutputParameters { get; } = new Dictionary(0, StringComparer.OrdinalIgnoreCase); 27 | 28 | public IDictionary Parameters { get; } = new Dictionary(0, StringComparer.OrdinalIgnoreCase); 29 | 30 | /// 31 | public int RecordsAffected { get; set; } 32 | 33 | /// 34 | /// Raw SQL. 35 | /// 36 | public string SQL { get; } 37 | 38 | /// 39 | public TimeSpan? Timeout { get; set; } 40 | 41 | /// 42 | public CommandType Type { get; set; } = CommandType.Text; 43 | 44 | /// 45 | /// SQL 46 | /// 47 | public override string ToString() 48 | => this.SQL!; 49 | } 50 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/ActionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Utilities; 4 | 5 | namespace TypeCache.Extensions; 6 | 7 | public static class ActionExtensions 8 | { 9 | /// 10 | /// Retry a failed . The # of dictates the # of retry attempts.
11 | /// Some built-in interval sequences to use for retry delays:
12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// These are increasing infinite sequences, hence an infinite # of retries will be attempted.
18 | /// To limit the number of retries, call Linq's Take(...) method on the returned collection. 19 | ///
20 | public static async Task Retry(this Action @this, IEnumerable retryDelays, TimeProvider? timeProvider = default, CancellationToken token = default) 21 | { 22 | @this.ThrowIfNull(); 23 | 24 | try 25 | { 26 | await Task.Run(@this, token); 27 | } 28 | catch (Exception lastError) 29 | { 30 | timeProvider ??= TimeProvider.System; 31 | 32 | foreach (var delay in retryDelays) 33 | { 34 | await Task.Delay(delay, timeProvider, token); 35 | try 36 | { 37 | await Task.Run(@this, token); 38 | return; 39 | } 40 | catch (Exception ex) 41 | { 42 | lastError = ex; 43 | } 44 | } 45 | 46 | await Task.FromException(lastError); 47 | } 48 | } 49 | 50 | /// 51 | /// Runs the action and returns how long it took to run. 52 | /// 53 | public static TimeSpan Timed(this Action @this) 54 | { 55 | var ticks = Stopwatch.GetTimestamp(); 56 | @this(); 57 | return Stopwatch.GetElapsedTime(ticks); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/AssemblyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace TypeCache.Extensions; 4 | 5 | public static class AssemblyExtensions 6 | { 7 | public static string GetNuGetVersion(this Assembly assembly) 8 | { 9 | var version = assembly.GetCustomAttribute()?.InformationalVersion; 10 | return version?.LastIndexOf('+') switch 11 | { 12 | null => string.Empty, 13 | -1 => version, 14 | int i => version[0..i] 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/AsyncEnumeratorExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Extensions; 4 | 5 | public static class AsyncEnumeratorExtensions 6 | { 7 | public async static ValueTask CountAsync(this IAsyncEnumerator @this) 8 | { 9 | var count = 0; 10 | while (await @this.MoveNextAsync()) 11 | ++count; 12 | return count; 13 | } 14 | 15 | /// 16 | /// => @.MoveAsync() ? @.Current : ; 17 | /// 18 | [MethodImpl(AggressiveInlining), DebuggerHidden] 19 | public static async ValueTask GetAsync(this IAsyncEnumerator @this, int index) 20 | => await @this.MoveAsync(index) ? @this.Current : default; 21 | 22 | public static async ValueTask MoveAsync(this IAsyncEnumerator @this, int count) 23 | { 24 | while (count > 0 && await @this.MoveNextAsync()) 25 | --count; 26 | return count == 0; 27 | } 28 | 29 | /// 30 | /// => @.MoveNextAsync() ? @.Current : ; 31 | /// 32 | [MethodImpl(AggressiveInlining), DebuggerHidden] 33 | public static async ValueTask NextAsync(this IAsyncEnumerator @this) 34 | => await @this.MoveNextAsync() ? @this.Current : default; 35 | 36 | /// Read this many items. 37 | public static async IAsyncEnumerator ReadAsync(this IAsyncEnumerator @this, int count) 38 | { 39 | while (--count > -1 && await @this.MoveNextAsync()) 40 | yield return @this.Current; 41 | } 42 | 43 | public static async IAsyncEnumerable RestAsync(this IAsyncEnumerator @this) 44 | { 45 | while (await @this.MoveNextAsync()) 46 | yield return @this.Current; 47 | } 48 | 49 | /// 50 | public async static ValueTask SkipAsync(this IAsyncEnumerator @this, int count) 51 | { 52 | while (count > 0 && await @this.MoveNextAsync()) 53 | --count; 54 | 55 | return count == 0 56 | ? @this.Current 57 | : throw new IndexOutOfRangeException($"{nameof(IAsyncEnumerator)}.{nameof(SkipAsync)}: Remaining {nameof(count)} of {count}."); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Collections.ObjectModel; 4 | 5 | namespace TypeCache.Extensions; 6 | 7 | public static class DictionaryExtensions 8 | { 9 | /// 10 | public static bool If(this IDictionary @this, K key, Action action) 11 | where K : notnull 12 | { 13 | @this.ThrowIfNull(); 14 | 15 | var success = @this.ContainsKey(key); 16 | if (success) 17 | action(); 18 | 19 | return success; 20 | } 21 | 22 | /// 23 | public static bool If(this IDictionary @this, K key, Action> action) 24 | where K : notnull 25 | { 26 | @this.ThrowIfNull(); 27 | 28 | var success = @this.TryGetValue(key, out var value); 29 | if (success) 30 | action(KeyValuePair.Create(key, value!)); 31 | 32 | return success; 33 | } 34 | 35 | /// 36 | /// 37 | /// => ReadOnlyDictionary<, >(@); 38 | /// 39 | [MethodImpl(AggressiveInlining), DebuggerHidden] 40 | public static IReadOnlyDictionary ToReadOnly(this IDictionary @this) 41 | where K : notnull 42 | => new ReadOnlyDictionary(@this); 43 | } 44 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/ExpressionExtensions.UnaryOperator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Linq.Expressions; 4 | 5 | namespace TypeCache.Extensions; 6 | 7 | public enum UnaryOperator 8 | { 9 | /// 10 | /// i == 11 | IsTrue, 12 | /// 13 | /// i == 14 | IsFalse, 15 | /// 16 | /// ++i 17 | PreIncrement, 18 | /// 19 | /// i + 1 20 | Increment, 21 | /// 22 | /// i++ 23 | PostIncrement, 24 | /// 25 | /// --i 26 | PreDecrement, 27 | /// 28 | /// i - 1 29 | Decrement, 30 | /// 31 | /// i-- 32 | PostDecrement, 33 | /// 34 | /// -i 35 | Negate, 36 | /// 37 | /// (-i) 38 | NegateChecked, 39 | /// 40 | /// ~i 41 | Complement 42 | } 43 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/FuncExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Utilities; 4 | 5 | namespace TypeCache.Extensions; 6 | 7 | public static class FuncExtensions 8 | { 9 | /// 10 | /// Retry a failed . The # of dictates the # of retry attempts.
11 | /// Some built-in interval sequences to use for retry delays:
12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// These are increasing infinite sequences, hence an infinite # of retries will be attempted.
18 | /// To limit the number of retries, call Linq's Take(...) method on the returned collection. 19 | ///
20 | public static async Task Retry(this Func @this, IEnumerable retryDelays, TimeProvider? timeProvider = default, CancellationToken token = default) 21 | { 22 | @this.ThrowIfNull(); 23 | 24 | try 25 | { 26 | return await Task.Run(@this, token); 27 | } 28 | catch (Exception lastError) 29 | { 30 | timeProvider ??= TimeProvider.System; 31 | foreach (var delay in retryDelays) 32 | { 33 | await Task.Delay(delay, timeProvider, token); 34 | try 35 | { 36 | return await Task.Run(@this, token); 37 | } 38 | catch (Exception ex) 39 | { 40 | lastError = ex; 41 | } 42 | } 43 | 44 | return await Task.FromException(lastError); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/IndexExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Extensions; 4 | 5 | public static class IndexExtensions 6 | { 7 | public static Index FromStart(this Index @this, int count) 8 | => @this.IsFromEnd ? (count > 0 ? new Index(count - @this.Value) : default) : @this; 9 | 10 | /// 11 | public static Index Max(this (Index, Index) @this) 12 | { 13 | (@this.Item1.IsFromEnd == @this.Item2.IsFromEnd).ThrowIfFalse(); 14 | 15 | return @this.Item1.IsFromEnd 16 | ? Index.FromEnd((@this.Item1.Value, @this.Item2.Value).Min()) 17 | : Index.FromStart((@this.Item1.Value, @this.Item2.Value).Max()); 18 | } 19 | 20 | /// 21 | public static Index Min(this (Index, Index) @this) 22 | { 23 | (@this.Item1.IsFromEnd == @this.Item2.IsFromEnd).ThrowIfFalse(); 24 | 25 | return @this.Item1.IsFromEnd 26 | ? Index.FromEnd((@this.Item1.Value, @this.Item2.Value).Max()) 27 | : Index.FromStart((@this.Item1.Value, @this.Item2.Value).Min()); 28 | } 29 | 30 | /// 31 | /// 32 | /// => (@.Value + ); 33 | /// 34 | [MethodImpl(AggressiveInlining), DebuggerHidden] 35 | public static Index Next(this Index @this, int increment = 1) 36 | => new(@this.Value + increment); 37 | 38 | /// 39 | /// 40 | /// => (@.Value - ); 41 | /// 42 | [MethodImpl(AggressiveInlining), DebuggerHidden] 43 | public static Index Previous(this Index @this, int increment = 1) 44 | => new(@this.Value - increment); 45 | } 46 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Extensions; 4 | 5 | public static class ListExtensions 6 | { 7 | public static void AddIfNotBlank(this IList @this, string? item) 8 | { 9 | @this.ThrowIfNull(); 10 | 11 | if (item.IsNotBlank()) 12 | @this.Add(item); 13 | } 14 | 15 | public static void AddIfHasValue(this IList @this, T? item) 16 | where T : struct 17 | { 18 | @this.ThrowIfNull(); 19 | 20 | if (item.HasValue) 21 | @this.Add(item.Value); 22 | } 23 | 24 | public static void AddIfNotNull(this IList @this, T? item) 25 | where T : class 26 | { 27 | @this.ThrowIfNull(); 28 | 29 | if (item is not null) 30 | @this.Add(item); 31 | } 32 | 33 | public static void InsertIfNotBlank(this IList @this, int index, string? item) 34 | { 35 | @this.ThrowIfNull(); 36 | 37 | if (item.IsNotBlank()) 38 | @this.Insert(index, item); 39 | } 40 | 41 | public static void InsertIfHasValue(this IList @this, int index, T? item) 42 | where T : struct 43 | { 44 | @this.ThrowIfNull(); 45 | 46 | if (item.HasValue) 47 | @this.Insert(index, item.Value); 48 | } 49 | 50 | public static void InsertIfNotNull(this IList @this, int index, T? item) 51 | where T : class 52 | { 53 | @this.ThrowIfNull(); 54 | 55 | if (item is not null) 56 | @this.Insert(index, item); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/LoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using Microsoft.Extensions.Logging; 4 | using TypeCache.Utilities; 5 | 6 | namespace TypeCache.Extensions; 7 | 8 | public static class LoggerExtensions 9 | { 10 | /// 11 | /// Logs the and each inner exception. 12 | /// 13 | public static void LogAggregateException(this ILogger @this, EventId eventId, AggregateException error, string message, object?[]? args = null) 14 | { 15 | @this.ThrowIfNull(); 16 | error.ThrowIfNull(); 17 | 18 | args ??= Array.Empty; 19 | 20 | if (error.InnerExceptions.Count > 1) 21 | error.InnerExceptions.ForEach(exception => @this.LogError(eventId, exception, message, args)); 22 | else if (error.InnerException is not null) 23 | @this.LogError(eventId, error.InnerException, message, args); 24 | else 25 | @this.LogError(eventId, error, message, args); 26 | } 27 | 28 | /// 29 | /// Logs the and each inner exception. 30 | /// 31 | public static void LogAggregateException(this ILogger @this, AggregateException error, string message, object?[]? args = null) 32 | { 33 | @this.ThrowIfNull(); 34 | error.ThrowIfNull(); 35 | 36 | args ??= Array.Empty; 37 | 38 | if (error.InnerExceptions.Count > 1) 39 | error.InnerExceptions.ForEach(exception => @this.LogError(exception, message, args)); 40 | else if (error.InnerException is not null) 41 | @this.LogError(error.InnerException, message, args); 42 | else 43 | @this.LogError(error, message, args); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/NumericExtensions.FloatingPointIeee754.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace TypeCache.Extensions; 4 | 5 | public static partial class NumericExtensions 6 | { 7 | /// 8 | /// 9 | /// => .BitDecrement(@); 10 | /// 11 | [MethodImpl(AggressiveInlining), DebuggerHidden] 12 | public static T BitDecrement(this T @this) 13 | where T : IFloatingPointIeee754 14 | => T.BitDecrement(@this); 15 | 16 | /// 17 | /// 18 | /// => .BitIncrement(@); 19 | /// 20 | [MethodImpl(AggressiveInlining), DebuggerHidden] 21 | public static T BitIncrement(this T @this) 22 | where T : IFloatingPointIeee754 23 | => T.BitIncrement(@this); 24 | } 25 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/NumericExtensions.Number.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace TypeCache.Extensions; 5 | 6 | public static partial class NumericExtensions 7 | { 8 | /// 9 | /// 10 | /// => .Max(@.Item1, @.Item2); 11 | /// 12 | [MethodImpl(AggressiveInlining), DebuggerHidden] 13 | public static T Max(this (T, T) @this) 14 | where T : INumber 15 | => T.Max(@this.Item1, @this.Item2); 16 | 17 | /// 18 | /// 19 | /// => .Min(@.Item1, @.Item2); 20 | /// 21 | [MethodImpl(AggressiveInlining), DebuggerHidden] 22 | public static T Min(this (T, T) @this) 23 | where T : INumber 24 | => T.Min(@this.Item1, @this.Item2); 25 | 26 | /// 27 | /// 28 | /// => .Sign(@); 29 | /// 30 | [MethodImpl(AggressiveInlining), DebuggerHidden] 31 | public static int Sign(this T @this) 32 | where T : INumber 33 | => T.Sign(@this); 34 | 35 | public static byte[] ToBytes(this T @this) 36 | where T : struct, INumber 37 | => @this switch 38 | { 39 | char value => BitConverter.GetBytes(value), 40 | sbyte value => [(byte)value], 41 | short value => BitConverter.GetBytes(value), 42 | int value => BitConverter.GetBytes(value), 43 | nint value => BitConverter.GetBytes(value), 44 | long value => BitConverter.GetBytes(value), 45 | Int128 value => BitConverter.GetBytes(value), 46 | byte value => [value], 47 | ushort value => BitConverter.GetBytes(value), 48 | uint value => BitConverter.GetBytes(value), 49 | nuint value => BitConverter.GetBytes(value), 50 | ulong value => BitConverter.GetBytes(value), 51 | UInt128 value => BitConverter.GetBytes(value), 52 | BigInteger value => value.ToByteArray(), 53 | Half value => BitConverter.GetBytes(value), 54 | float value => BitConverter.GetBytes(value), 55 | double value => BitConverter.GetBytes(value), 56 | decimal value => decimal.GetBits(value).SelectMany(BitConverter.GetBytes).ToArray(), 57 | NFloat value => BitConverter.GetBytes(value), 58 | _ => throw new UnreachableException(Invariant($"Cannot convert [{@this.GetType().FullName}] to bytes.")) 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/ParallelExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Collections.Concurrent; 4 | 5 | namespace TypeCache.Extensions; 6 | 7 | public static class ParallelExtensions 8 | { 9 | public static ParallelLoopResult ForEach(this OrderablePartitioner @this, Action action, ParallelOptions? options = null) 10 | => options is not null 11 | ? Parallel.ForEach(@this, options, action) 12 | : Parallel.ForEach(@this, action); 13 | 14 | public static ParallelLoopResult ForEach(this OrderablePartitioner @this, Func localInit, Func action, Action localFinally, ParallelOptions? options = null) 15 | => options is not null 16 | ? Parallel.ForEach(@this, options, localInit, action, localFinally) 17 | : Parallel.ForEach(@this, localInit, action, localFinally); 18 | 19 | public static ParallelLoopResult ForEach(this Partitioner @this, Action action, ParallelOptions? options = null) 20 | => options is not null 21 | ? Parallel.ForEach(@this, options, action) 22 | : Parallel.ForEach(@this, action); 23 | 24 | public static ParallelLoopResult ForEach(this Partitioner @this, Action action, ParallelOptions? options = null) 25 | => options is not null 26 | ? Parallel.ForEach(@this, options, action) 27 | : Parallel.ForEach(@this, action); 28 | 29 | public static ParallelLoopResult ForEach(this Partitioner @this, Func localInit, Func action, Action localFinally, ParallelOptions? options = null) 30 | => options is not null 31 | ? Parallel.ForEach(@this, options, localInit, action, localFinally) 32 | : Parallel.ForEach(@this, localInit, action, localFinally); 33 | } 34 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/ReflectionExtensions.Delegate.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | 5 | namespace TypeCache.Extensions; 6 | 7 | public partial class ReflectionExtensions 8 | { 9 | [DebuggerHidden] 10 | public static MethodInfo ToMethodInfo(this Delegate @this) 11 | => @this.GetType().GetMethod(nameof(Action.Invoke), INSTANCE_BINDING_FLAGS)!; 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/ReflectionExtensions.MemberInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace TypeCache.Extensions; 7 | 8 | public partial class ReflectionExtensions 9 | { 10 | /// 11 | /// @.GetCustomAttribute<>() ; 12 | /// 13 | [MethodImpl(AggressiveInlining), DebuggerHidden] 14 | public static bool HasCustomAttribute(this MemberInfo @this, bool inherit = true) 15 | where T : Attribute 16 | => @this.GetCustomAttribute(inherit) is not null; 17 | 18 | /// 19 | /// @.GetCustomAttribute(, ) ; 20 | /// 21 | [MethodImpl(AggressiveInlining), DebuggerHidden] 22 | public static bool HasCustomAttribute(this MemberInfo @this, Type attributeType, bool inherit = true) 23 | => @this.GetCustomAttribute(attributeType, inherit) is not null; 24 | 25 | [DebuggerHidden] 26 | public static string Name(this MemberInfo @this) 27 | { 28 | var name = @this.Name.Replace("@", string.Empty); 29 | var index = name.IndexOf(GENERIC_TICKMARK); 30 | return index > -1 ? name.Left(index) : name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/ReflectionExtensions.ParameterInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace TypeCache.Extensions; 7 | 8 | public partial class ReflectionExtensions 9 | { 10 | /// 11 | /// @.GetCustomAttribute<>() ; 12 | /// 13 | [MethodImpl(AggressiveInlining), DebuggerHidden] 14 | public static bool HasCustomAttribute(this ParameterInfo @this, bool inherit = true) 15 | where T : Attribute 16 | => @this.GetCustomAttribute(inherit) is not null; 17 | 18 | /// 19 | /// @.GetCustomAttribute(, ) ; 20 | /// 21 | [MethodImpl(AggressiveInlining), DebuggerHidden] 22 | public static bool HasCustomAttribute(this ParameterInfo @this, Type attributeType, bool inherit = true) 23 | => @this.GetCustomAttribute(attributeType, inherit) is not null; 24 | 25 | public static string Name(this ParameterInfo @this) 26 | => @this.Name!.IndexOf(GENERIC_TICKMARK) switch 27 | { 28 | var index when index > 0 => @this.Name.Left(index), 29 | _ => @this.Name, 30 | }; 31 | 32 | /// 33 | /// 34 | /// => .Parameter(@); 35 | /// 36 | [MethodImpl(AggressiveInlining), DebuggerHidden] 37 | public static ParameterExpression ToExpression(this ParameterInfo @this) 38 | => Expression.Parameter(@this.ParameterType, @this.Name); 39 | } 40 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/ReflectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Reflection; 4 | using static System.Reflection.BindingFlags; 5 | 6 | namespace TypeCache.Extensions; 7 | 8 | public static partial class ReflectionExtensions 9 | { 10 | private const char GENERIC_TICKMARK = '`'; 11 | 12 | private const BindingFlags ALL_BINDING_FLAGS = FlattenHierarchy | IgnoreCase | Instance | NonPublic | Public | Static; 13 | private const BindingFlags INSTANCE_BINDING_FLAGS = FlattenHierarchy | IgnoreCase | Instance | NonPublic | Public; 14 | private const BindingFlags PUBLIC_INSTANCE_BINDING_FLAGS = FlattenHierarchy | IgnoreCase | Instance | Public; 15 | private const BindingFlags PUBLIC_STATIC_BINDING_FLAGS = FlattenHierarchy | IgnoreCase | Public | Static; 16 | private const BindingFlags STATIC_BINDING_FLAGS = FlattenHierarchy | IgnoreCase | NonPublic | Public | Static; 17 | 18 | private const string Item = nameof(Item); 19 | private const string Rest = nameof(Rest); 20 | 21 | private static T? GetMemberInfo(this T[] @this, string name) 22 | where T : MemberInfo 23 | { 24 | var memberInfos = @this.Where(_ => _.Name.EqualsIgnoreCase(name)).ToArray(); 25 | return memberInfos switch 26 | { 27 | { Length: 0 } => null, 28 | { Length: 1 } => memberInfos[0], 29 | _ => memberInfos.FirstOrDefault(_ => _.Name.EqualsOrdinal(name)) 30 | }; 31 | } 32 | 33 | private static MethodInfo[] GetMethodInfos(this MethodInfo[] @this, string name) 34 | { 35 | var methodInfos = @this.Where(_ => _.Name.EqualsIgnoreCase(name)).ToArray(); 36 | var lookup = methodInfos.ToLookup(_ => _.Name, StringComparer.Ordinal); 37 | return lookup.Count switch 38 | { 39 | 0 => [], 40 | 1 => lookup.First().ToArray(), 41 | _ => lookup[name].ToArray() 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/SetExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace TypeCache.Extensions; 2 | 3 | public static class SetExtensions 4 | { 5 | public static void ForEach(this ISet @this, Action action) 6 | { 7 | action.ThrowIfNull(); 8 | 9 | foreach (var item in @this) 10 | action(item); 11 | } 12 | 13 | public static void ForEach(this ISet @this, Action action) 14 | { 15 | var i = -1; 16 | @this.ForEach(item => action(item, ++i)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/TypeCache/Extensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace TypeCache.Extensions; 8 | 9 | public static class TaskExtensions 10 | { 11 | public static async Task Cast(this Task task) 12 | { 13 | task.ThrowIfNull(); 14 | task.GetType().Is(typeof(Task<>)).ThrowIfFalse(); 15 | 16 | await task; 17 | 18 | object result = task.GetType().GetProperty(nameof(Task.Result))!.GetValueEx(task)!; 19 | return (T)result; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/TypeCache/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Buffers.Text; 3 | global using System.Diagnostics; 4 | global using System.Diagnostics.CodeAnalysis; 5 | global using static System.FormattableString; 6 | global using static System.Runtime.CompilerServices.MethodImplOptions; 7 | global using CancellationToken = System.Threading.CancellationToken; 8 | global using MethodImplAttribute = System.Runtime.CompilerServices.MethodImplAttribute; 9 | global using Task = System.Threading.Tasks.Task; 10 | global using ValueTask = System.Threading.Tasks.ValueTask; 11 | -------------------------------------------------------------------------------- /src/TypeCache/Mediation/CustomAfterRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Mediation; 4 | 5 | internal sealed class CustomAfterRule(Func execute) 6 | : IAfterRule 7 | where REQUEST : IRequest 8 | { 9 | [MethodImpl(AggressiveInlining), DebuggerHidden] 10 | public Task Handle(REQUEST request, CancellationToken token = default) 11 | => execute(request, token); 12 | } 13 | 14 | internal sealed class CustomAfterRule(Func execute) 15 | : IAfterRule 16 | where REQUEST : IRequest 17 | { 18 | [MethodImpl(AggressiveInlining), DebuggerHidden] 19 | public Task Handle(REQUEST request, RESPONSE response, CancellationToken token = default) 20 | => execute(request, response, token); 21 | } 22 | -------------------------------------------------------------------------------- /src/TypeCache/Mediation/CustomRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Mediation; 4 | 5 | internal sealed class CustomRule(Func execute) 6 | : IRule 7 | where REQUEST : IRequest 8 | { 9 | [MethodImpl(AggressiveInlining), DebuggerHidden] 10 | public Task Execute(REQUEST request, CancellationToken token = default) 11 | => execute(request, token); 12 | } 13 | 14 | internal sealed class CustomRule(Func> map) 15 | : IRule 16 | where REQUEST : IRequest 17 | { 18 | [MethodImpl(AggressiveInlining), DebuggerHidden] 19 | public Task Map(REQUEST request, CancellationToken token = default) 20 | => map(request, token); 21 | } 22 | -------------------------------------------------------------------------------- /src/TypeCache/Mediation/CustomValidationRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Mediation; 4 | 5 | internal sealed class CustomValidationRule(Func validate) 6 | : IValidationRule 7 | where REQUEST : IRequest 8 | { 9 | [MethodImpl(AggressiveInlining), DebuggerHidden] 10 | public Task Validate(REQUEST request, CancellationToken token = default) 11 | => validate(request, token); 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache/Mediation/IAfterRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Mediation; 4 | 5 | public interface IAfterRule 6 | where REQUEST : IRequest 7 | { 8 | Task Handle(REQUEST request, CancellationToken token = default); 9 | } 10 | 11 | public interface IAfterRule 12 | where REQUEST : IRequest 13 | { 14 | Task Handle(REQUEST request, RESPONSE response, CancellationToken token = default); 15 | } 16 | -------------------------------------------------------------------------------- /src/TypeCache/Mediation/IRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Mediation; 4 | 5 | public interface IRequest 6 | { 7 | } 8 | 9 | public interface IRequest 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/TypeCache/Mediation/IRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Mediation; 4 | 5 | public interface IRule 6 | where REQUEST : IRequest 7 | { 8 | Task Execute(REQUEST request, CancellationToken token = default); 9 | } 10 | 11 | public interface IRule 12 | where REQUEST : IRequest 13 | { 14 | Task Map(REQUEST request, CancellationToken token = default); 15 | } 16 | -------------------------------------------------------------------------------- /src/TypeCache/Mediation/IValidationRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Mediation; 4 | 5 | public interface IValidationRule 6 | { 7 | Task Validate(REQUEST request, CancellationToken token = default); 8 | } 9 | -------------------------------------------------------------------------------- /src/TypeCache/TypeCache.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | TypeCache 7 | true 8 | TypeCache 9 | 9.1.2 10 | Samuel Abraham <sam987883@gmail.com> 11 | Samuel Abraham <sam987883@gmail.com> 12 | TypeCache Reflection 13 | A library containing various useful utilities such as: 14 | TypeCache.Data - Simplified database access (SQL Server/Oracle/PostGre SQL/MySQL). 15 | TypeCache.Extensions - A bunch of extensions you didn't know you needed. 16 | TypeCache.Mediation - Simplified Mediator Pattern. 17 | TypeCache.Reflection - High performance System.Reflection alternative. 18 | 19 | Copyright (c) 2021 Samuel Abraham 20 | TypeCache 21 | false 22 | False 23 | MIT 24 | https://www.nuget.org/packages/TypeCache/ 25 | https://github.com/sam987883/TypeCache/ 26 | git 27 | en-US 28 | TypeCash.png 29 | Reflection;Linq;GraphQL;WebAPI;MVC 30 | enable 31 | README.md 32 | 33 | 34 | 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | True 47 | 48 | 49 | 50 | 51 | 52 | 53 | True 54 | \ 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/Array.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Utilities; 4 | 5 | public static class Array 6 | { 7 | /// 8 | /// 9 | /// => ; 10 | /// 11 | public static T[] Empty => Array.Empty(); 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/CustomObservable.cs: -------------------------------------------------------------------------------- 1 | namespace TypeCache.Utilities; 2 | 3 | /// 4 | /// Converts an for value types into an IObservable<object?>. 5 | /// 6 | public sealed class CustomObservable(IObservable observable) : IObservable 7 | { 8 | [MethodImpl(AggressiveInlining), DebuggerHidden] 9 | public IDisposable Subscribe(IObserver observer) 10 | => observable.Subscribe(new ObserverAdapter(observer)); 11 | } 12 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/CustomStringWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text; 4 | 5 | namespace TypeCache.Utilities; 6 | 7 | public sealed class CustomStringWriter : StringWriter 8 | { 9 | public override Encoding Encoding { get; } 10 | 11 | public CustomStringWriter(Encoding encoding) 12 | => this.Encoding = encoding; 13 | 14 | internal CustomStringWriter(StringBuilder builder, Encoding encoding) : base(builder) 15 | => this.Encoding = encoding; 16 | } 17 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/Enum.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Collections.Frozen; 4 | using TypeCache.Extensions; 5 | 6 | namespace TypeCache.Utilities; 7 | 8 | public static class Enum 9 | where T : struct, Enum 10 | { 11 | private static readonly Comparison EnumCompare; 12 | private static readonly Func EnumEquals; 13 | private static readonly Func EnumGetHashCode; 14 | 15 | static Enum() 16 | { 17 | var underlyingType = typeof(T).GetEnumUnderlyingType(); 18 | EnumCompare = CreateCompare(underlyingType); 19 | EnumEquals = CreateEquals(underlyingType); 20 | EnumGetHashCode = CreateGetHashCode(underlyingType); 21 | } 22 | 23 | private static Comparison CreateCompare(Type underlyingType) 24 | => LambdaFactory.CreateComparison((value1, value2) => 25 | value1.Cast(underlyingType).Call(nameof(IComparable.CompareTo), [value2.Cast(underlyingType)])).Compile(); 26 | 27 | private static Func CreateEquals(Type underlyingType) 28 | => LambdaFactory.CreateFunc((value1, value2) => 29 | value1.Cast(underlyingType).Operation(BinaryOperator.Equal, value2.Cast(underlyingType))).Compile(); 30 | 31 | private static Func CreateGetHashCode(Type underlyingType) 32 | => LambdaFactory.CreateFunc(value => 33 | value.Cast(underlyingType).Call(nameof(object.GetHashCode))).Compile(); 34 | 35 | [MethodImpl(AggressiveInlining), DebuggerHidden] 36 | public static int Compare([AllowNull] T x, [AllowNull] T y) 37 | => EnumCompare(x, y); 38 | 39 | [MethodImpl(AggressiveInlining), DebuggerHidden] 40 | public static bool Equals([AllowNull] T x, [AllowNull] T y) 41 | => EnumEquals(x, y); 42 | 43 | [MethodImpl(AggressiveInlining), DebuggerHidden] 44 | public static int GetHashCode([DisallowNull] T value) 45 | => EnumGetHashCode(value); 46 | 47 | [DebuggerHidden] 48 | public static IReadOnlySet Attributes { get; } = typeof(T).GetCustomAttributes(false).Cast().ToFrozenSet(); 49 | 50 | [DebuggerHidden] 51 | public static bool Flags { get; } = typeof(T).HasCustomAttribute(); 52 | 53 | [DebuggerHidden] 54 | public static string Name { get; } = typeof(T).Name; 55 | 56 | [DebuggerHidden] 57 | public static IReadOnlySet Values { get; } = Enum.GetValues().ToFrozenSet(); 58 | 59 | [DebuggerHidden] 60 | public static Type UnderlyingType => typeof(T).GetEnumUnderlyingType(); 61 | 62 | [DebuggerHidden] 63 | public static bool IsDefined(T value) 64 | => Enum.IsDefined(value) || (Flags && Values.Any(_ => value.HasFlag(_))); 65 | } 66 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/EnumComparer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Runtime.CompilerServices; 4 | using TypeCache.Extensions; 5 | 6 | namespace TypeCache.Utilities; 7 | 8 | public readonly struct EnumComparer : IComparer, IEqualityComparer 9 | where T : struct, Enum 10 | { 11 | [MethodImpl(AggressiveInlining), DebuggerHidden] 12 | public int Compare([AllowNull] T x, [AllowNull] T y) 13 | => Enum.Compare(x, y); 14 | 15 | [MethodImpl(AggressiveInlining), DebuggerHidden] 16 | public bool Equals([AllowNull] T x, [AllowNull] T y) 17 | => Enum.Equals(x, y); 18 | 19 | [MethodImpl(AggressiveInlining), DebuggerHidden] 20 | public int GetHashCode([DisallowNull] T value) 21 | => Enum.GetHashCode(value); 22 | } 23 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/Enumerable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | namespace TypeCache.Utilities; 4 | 5 | public static class Enumerable 6 | { 7 | /// 8 | /// 9 | /// => ; 10 | /// 11 | public static IEnumerable Empty => Enumerable.Empty(); 12 | } 13 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/EventHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Collections.Concurrent; 4 | using System.Reflection; 5 | using TypeCache.Extensions; 6 | using static System.Reflection.BindingFlags; 7 | 8 | namespace TypeCache.Utilities; 9 | 10 | public static class EventHandler 11 | where T : class 12 | { 13 | private readonly record struct EventHandlerReference(WeakReference Instance, RuntimeMethodHandle? AddMethodHandle, RuntimeMethodHandle? RemoveMethodHandle, Delegate EventHandler); 14 | 15 | private static readonly IDictionary EventHandlers = new ConcurrentDictionary(); 16 | 17 | public static EventInfo[] Events => typeof(T).GetEvents(FlattenHierarchy | Instance | Public); 18 | 19 | /// 20 | /// 21 | public static long Add(T instance, string eventMemberName, Delegate handler) 22 | { 23 | instance.ThrowIfNull(); 24 | eventMemberName.ThrowIfBlank(); 25 | handler.ThrowIfNull(); 26 | 27 | var eventInfo = typeof(T).GetEvent(eventMemberName); 28 | eventInfo.ThrowIfNull(); 29 | eventInfo.AddMethod.ThrowIfNull(); 30 | 31 | var key = DateTime.UtcNow.Ticks; 32 | var reference = new EventHandlerReference(instance.WeakReference(), eventInfo.AddMethod.MethodHandle, eventInfo.RemoveMethod?.MethodHandle, handler); 33 | EventHandlers.Add(key, reference); 34 | eventInfo.AddMethod.InvokeAction(instance, [handler]); 35 | return key; 36 | } 37 | 38 | public static Delegate? GetEventHandler(long key) 39 | => EventHandlers.TryGetValue(key, out var handler) ? handler.EventHandler : null; 40 | 41 | public static T? GetEventInstance(long key) 42 | => EventHandlers.TryGetValue(key, out var handler) && handler.Instance.TryGetTarget(out var target) ? target : null; 43 | 44 | public static bool Remove(long key) 45 | { 46 | if (!EventHandlers.TryGetValue(key, out var handler)) 47 | return false; 48 | 49 | if (handler.Instance.TryGetTarget(out var target)) 50 | ((MethodInfo)handler.RemoveMethodHandle?.ToMethodBase()!).InvokeAction(target, [handler.EventHandler]); 51 | 52 | return EventHandlers.Remove(key); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/IHashMaker.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Security.Cryptography; 4 | 5 | namespace TypeCache.Utilities; 6 | 7 | public interface IHashMaker : IDisposable 8 | { 9 | byte[] Decrypt(ReadOnlySpan data, byte[]? rgbIV = null, PaddingMode paddingMode = PaddingMode.PKCS7); 10 | 11 | byte[] Decrypt(byte[] data, byte[]? rgbIV = null, PaddingMode paddingMode = PaddingMode.PKCS7); 12 | 13 | long Decrypt(ReadOnlySpan hashId, byte[]? rgbIV = null, PaddingMode paddingMode = PaddingMode.PKCS7); 14 | 15 | byte[] Encrypt(ReadOnlySpan data, byte[]? rgbIV = null, PaddingMode paddingMode = PaddingMode.PKCS7); 16 | 17 | byte[] Encrypt(byte[] data, byte[]? rgbIV = null, PaddingMode paddingMode = PaddingMode.PKCS7); 18 | 19 | ReadOnlySpan Encrypt(long id, byte[]? rgbIV = null, PaddingMode paddingMode = PaddingMode.PKCS7); 20 | } 21 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/LazyDictionary.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Collections; 4 | using System.Collections.Concurrent; 5 | using TypeCache.Extensions; 6 | 7 | namespace TypeCache.Utilities; 8 | 9 | public sealed class LazyDictionary : IReadOnlyDictionary 10 | where K : notnull 11 | { 12 | private readonly ConcurrentDictionary> _Dictionary; 13 | private readonly Func> _CreateValue; 14 | 15 | /// 16 | public LazyDictionary(Func createValue, LazyThreadSafetyMode mode = LazyThreadSafetyMode.PublicationOnly, IEqualityComparer? comparer = null) 17 | { 18 | createValue.ThrowIfNull(); 19 | this._CreateValue = key => new Lazy(() => createValue(key), mode); 20 | this._Dictionary = new(comparer); 21 | } 22 | 23 | /// 24 | public LazyDictionary(Func createValue, int concurrencyLevel, int capacity, LazyThreadSafetyMode mode = LazyThreadSafetyMode.PublicationOnly, IEqualityComparer? comparer = null) 25 | { 26 | createValue.ThrowIfNull(); 27 | 28 | this._CreateValue = key => new Lazy(() => createValue(key), mode); 29 | this._Dictionary = new(concurrencyLevel, capacity, comparer); 30 | } 31 | 32 | public V this[K key] => this._Dictionary.GetOrAdd(key, this._CreateValue).Value; 33 | 34 | public IEnumerable Keys => this._Dictionary.Keys; 35 | 36 | public IEnumerable Values => this._Dictionary.Values.Select(_ => _.Value); 37 | 38 | public int Count => this._Dictionary.Count; 39 | 40 | [MethodImpl(AggressiveInlining), DebuggerHidden] 41 | public bool ContainsKey(K key) 42 | => this._Dictionary.ContainsKey(key); 43 | 44 | [MethodImpl(AggressiveInlining), DebuggerHidden] 45 | public IEnumerator> GetEnumerator() 46 | => this._Dictionary.Select(pair => KeyValuePair.Create(pair.Key, pair.Value.Value)).GetEnumerator(); 47 | 48 | [MethodImpl(AggressiveInlining), DebuggerHidden] 49 | IEnumerator IEnumerable.GetEnumerator() 50 | => this._Dictionary.Select(pair => KeyValuePair.Create(pair.Key, pair.Value.Value)).GetEnumerator(); 51 | 52 | bool IReadOnlyDictionary.TryGetValue(K key, [MaybeNullWhen(false)] out V value) 53 | { 54 | var success = this._Dictionary.TryGetValue(key, out var lazy) 55 | || this._Dictionary.TryAdd(key, lazy = this._CreateValue(key)); 56 | value = success ? lazy.Value : default; 57 | return success; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/ObservableAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using TypeCache.Extensions; 5 | 6 | namespace TypeCache.Utilities; 7 | 8 | /// 9 | /// adapter for . 10 | /// 11 | public sealed class ObservableAdapter 12 | : IObservable, IDisposable 13 | { 14 | private IAsyncEnumerable _Enumerable; 15 | private readonly CancellationTokenSource _CancellationTokenSource; 16 | 17 | public ObservableAdapter(IAsyncEnumerable enumerable, CancellationTokenSource cancellationTokenSource) 18 | { 19 | enumerable.ThrowIfNull(); 20 | cancellationTokenSource.ThrowIfNull(); 21 | 22 | this._Enumerable = enumerable; 23 | this._CancellationTokenSource = cancellationTokenSource; 24 | } 25 | 26 | public IDisposable Subscribe(IObserver observer) 27 | { 28 | this._Subscribe(observer); 29 | 30 | return this; 31 | 32 | } 33 | 34 | async void _Subscribe(IObserver observer) 35 | { 36 | try 37 | { 38 | var items = Interlocked.Exchange(ref this._Enumerable!, null) 39 | ?? throw new InvalidOperationException("This Subscribe method can only be called once."); 40 | 41 | if (this._CancellationTokenSource.IsCancellationRequested) 42 | return; 43 | 44 | await foreach (var item in items.WithCancellation(this._CancellationTokenSource.Token)) 45 | { 46 | observer.OnNext(item); 47 | 48 | if (this._CancellationTokenSource.IsCancellationRequested) 49 | return; 50 | } 51 | 52 | observer.OnCompleted(); 53 | } 54 | catch (Exception ex) 55 | { 56 | if (!this._CancellationTokenSource.IsCancellationRequested) 57 | observer.OnError(ex); 58 | } 59 | } 60 | 61 | public void Dispose() 62 | { 63 | if (!this._CancellationTokenSource.IsCancellationRequested) 64 | this._CancellationTokenSource.Cancel(); 65 | 66 | this._CancellationTokenSource.Dispose(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/ObserverAdapter.cs: -------------------------------------------------------------------------------- 1 | namespace TypeCache.Utilities; 2 | 3 | public sealed class ObserverAdapter(IObserver observer) : IObserver 4 | { 5 | [MethodImpl(AggressiveInlining), DebuggerHidden] 6 | public void OnCompleted() 7 | => observer.OnCompleted(); 8 | 9 | [MethodImpl(AggressiveInlining), DebuggerHidden] 10 | public void OnError(Exception error) 11 | => observer.OnError(error); 12 | 13 | [MethodImpl(AggressiveInlining), DebuggerHidden] 14 | public void OnNext(T value) 15 | => observer.OnNext(value); 16 | } 17 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/Singleton.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Extensions; 4 | 5 | namespace TypeCache.Utilities; 6 | 7 | public static class Singleton 8 | where T : class, new() 9 | { 10 | public static T Instance { get; } = (T)typeof(T).Create()!; 11 | } 12 | -------------------------------------------------------------------------------- /src/TypeCache/Utilities/ValueBox.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Runtime.CompilerServices; 4 | using TypeCache.Extensions; 5 | 6 | namespace TypeCache.Utilities; 7 | 8 | public static class ValueBox 9 | where T : struct 10 | { 11 | private static readonly IReadOnlyDictionary BoxedValues = new LazyDictionary(value => value); 12 | 13 | public static object GetValue(T value) 14 | => BoxedValues[value]; 15 | } 16 | -------------------------------------------------------------------------------- /tests/TypeCache.GraphQL.TestApp/Models/Detail.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.GraphQL.Attributes; 4 | 5 | namespace TypeCache.GraphQL.TestApp.Models; 6 | 7 | public class Detail 8 | { 9 | public required string Alias { get; set; } 10 | 11 | public required string FirstName { get; set; } 12 | 13 | public int SomeValue { get; set; } 14 | 15 | [GraphQLName("detail")] 16 | public static ValueTask GetDetail() 17 | => ValueTask.FromResult(new Detail() { Alias = "A1", FirstName = "John", SomeValue = 111 }); 18 | 19 | [GraphQLName("details")] 20 | public static ValueTask> GetDetails(IEnumerable firstNames) 21 | => ValueTask.FromResult((IEnumerable)new Detail[] 22 | { 23 | new () { Alias = "A1", FirstName = "John", SomeValue = 111 }, 24 | new () { Alias = "A2", FirstName = "Samuel", SomeValue = 222 }, 25 | new () { Alias = "A3", FirstName = "Samuel", SomeValue = 333 }, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /tests/TypeCache.GraphQL.TestApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:43664", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "TypeCache.GraphQL.TestApp": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "applicationUrl": "http://localhost:5198", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/TypeCache.GraphQL.TestApp/Tables/Person.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using GraphQL.Types; 4 | using TypeCache.Attributes; 5 | using TypeCache.GraphQL.Attributes; 6 | using TypeCache.GraphQL.Types; 7 | 8 | namespace TypeCache.GraphQL.TestApp.Tables; 9 | 10 | [SqlApi] 11 | public class Person 12 | { 13 | [GraphQLType()] 14 | public int BusinessEntityID { get; set; } 15 | public string? PersonType { get; set; } 16 | public bool NameStyle { get; set; } 17 | public string? Title { get; set; } 18 | public string? FirstName { get; set; } 19 | public string? MiddleName { get; set; } 20 | public string? LastName { get; set; } 21 | public string? Suffix { get; set; } 22 | public int EmailPromotion { get; set; } 23 | public string? AdditionalContactInfo { get; set; } 24 | public string? Demographics { get; set; } 25 | [GraphQLType()] 26 | [GraphQLName("rowguid")] 27 | public Guid Rowguid { get; set; } 28 | [GraphQLType>()] 29 | public DateTime ModifiedDate { get; set; } 30 | 31 | public IEnumerable GetPersons() 32 | => new Person[] 33 | { 34 | new() { FirstName = "John", LastName = "Smith" } 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /tests/TypeCache.GraphQL.TestApp/Tables/Product.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Attributes; 4 | 5 | namespace TypeCache.GraphQL.TestApp.Tables; 6 | 7 | [SqlApi] 8 | public class Product 9 | { 10 | public int ProductID { get; set; } 11 | public string? Name { get; set; } 12 | public string? ProductNumber { get; set; } 13 | public bool MakeFlag { get; set; } 14 | public bool FinishedGoodsFlag { get; set; } 15 | public string? Color { get; set; } 16 | public int SafetyStockLevel { get; set; } 17 | public int ReorderPoint { get; set; } 18 | public decimal StandardCost { get; set; } 19 | public decimal ListPrice { get; set; } 20 | public string? Size { get; set; } 21 | public string? SizeUnitMeasureCode { get; set; } 22 | public string? WeightUnitMeasureCode { get; set; } 23 | public decimal Weight { get; set; } 24 | public int DaysToManufacture { get; set; } 25 | public string? ProductLine { get; set; } 26 | public string? Class { get; set; } 27 | public string? Style { get; set; } 28 | public int? ProductSubcategoryID { get; set; } 29 | public int? ProductModelID { get; set; } 30 | public DateTime SellStartDate { get; set; } 31 | public DateTime? SellEndDate { get; set; } 32 | public DateTime? DiscontinuedDate { get; set; } 33 | public Guid RowGuid { get; set; } 34 | public DateTime ModifiedDate { get; set; } 35 | } 36 | -------------------------------------------------------------------------------- /tests/TypeCache.GraphQL.TestApp/Tables/WorkOrder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using TypeCache.Attributes; 4 | 5 | namespace TypeCache.GraphQL.TestApp.Tables; 6 | 7 | [SqlApi] 8 | public class WorkOrder 9 | { 10 | public int WorkOrderID { get; set; } 11 | public int ProductID { get; set; } 12 | public int OrderQty { get; set; } 13 | public int StockedQty { get; set; } 14 | public int ScrappedQty { get; set; } 15 | public DateTime StartDate { get; set; } 16 | public DateTime? EndDate { get; set; } 17 | public DateTime DueDate { get; set; } 18 | public int? ScrapReasonID { get; set; } 19 | public DateTime ModifiedDate { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /tests/TypeCache.GraphQL.TestApp/TypeCache.GraphQL.TestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/TypeCache.GraphQL.TestApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "Default": "Data Source=localhost;initial catalog=AdventureWorks2019; persist security info=True; Integrated Security=SSPI;Encrypt=false;ApplicationIntent=ReadWrite;" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Runtime.InteropServices; 3 | 4 | // In SDK-style projects such as this one, several assembly attributes that were historically 5 | // defined in this file are now automatically added during build and populated with 6 | // values defined in project properties. For details of which attributes are included 7 | // and how to customise this process see: https://aka.ms/assembly-info-properties 8 | 9 | 10 | // Setting ComVisible to false makes the types in this assembly not visible to COM 11 | // components. If you need to access a type in this assembly from COM, set the ComVisible 12 | // attribute to true on that type. 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | // The following GUID is for the ID of the typelib if this project is exposed to COM. 17 | 18 | [assembly: Guid("249a4db6-4c92-4d86-a659-5d91ce2c6d8f")] 19 | [assembly: ExcludeFromCodeCoverage] 20 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Converters/JsonConverterTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Data; 5 | using System.Numerics; 6 | using System.Text.Json; 7 | using TypeCache.Converters; 8 | using Xunit; 9 | 10 | namespace TypeCache.Tests.Converters; 11 | 12 | public class JsonConverterTests 13 | { 14 | [Fact] 15 | public void BigIntegerJsonConverter() 16 | { 17 | var value = new BigInteger(999999999999999999999999M); 18 | 19 | var jsonOptions = new JsonSerializerOptions(); 20 | jsonOptions.Converters.Add(new BigIntegerJsonConverter()); 21 | 22 | var json = JsonSerializer.Serialize(value); 23 | Assert.NotEqual("999999999999999999999999", json); 24 | 25 | json = JsonSerializer.Serialize(value, jsonOptions); 26 | Assert.Equal("999999999999999999999999", json); 27 | 28 | var result = JsonSerializer.Deserialize(json, jsonOptions); 29 | Assert.Equal(value, result); 30 | } 31 | 32 | [Fact] 33 | public void DataRowsJsonConverter() 34 | { 35 | var dataTable = new DataTable("Table1"); 36 | dataTable.Columns.Add("Column1", typeof(bool)); 37 | dataTable.Columns.Add("Column2", typeof(string)); 38 | dataTable.Columns.Add("Column3", typeof(int)); 39 | dataTable.Columns.Add("Column4", typeof(decimal)); 40 | 41 | var dataRow1 = dataTable.NewRow(); 42 | dataRow1["Column1"] = true; 43 | dataRow1["Column2"] = "AAAAAA"; 44 | dataRow1["Column3"] = 24; 45 | dataRow1["Column4"] = 99999.99M; 46 | var dataRow2 = dataTable.NewRow(); 47 | dataRow2["Column1"] = false; 48 | dataRow2["Column2"] = "BBBBBB"; 49 | dataRow2["Column3"] = DBNull.Value; 50 | dataRow2["Column4"] = 0M; 51 | var dataRow3 = dataTable.NewRow(); 52 | dataRow3["Column1"] = DBNull.Value; 53 | dataRow3["Column2"] = DBNull.Value; 54 | dataRow3["Column3"] = -24; 55 | dataRow3["Column4"] = -99999.99M; 56 | dataTable.Rows.Add(dataRow1); 57 | dataTable.Rows.Add(dataRow2); 58 | dataTable.Rows.Add(dataRow3); 59 | 60 | var jsonOptions = new JsonSerializerOptions(); 61 | jsonOptions.Converters.Add(new DataRowJsonConverter()); 62 | 63 | var json = JsonSerializer.Serialize(dataTable.Rows, jsonOptions); 64 | Assert.Equal(""" 65 | [{"Column1":true,"Column2":"AAAAAA","Column3":24,"Column4":99999.99},{"Column1":false,"Column2":"BBBBBB","Column3":null,"Column4":0},{"Column1":null,"Column2":null,"Column3":-24,"Column4":-99999.99}] 66 | """, json); 67 | 68 | json = JsonSerializer.Serialize(null as DataRow, jsonOptions); 69 | Assert.Equal("null", json); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/ActionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Time.Testing; 7 | using TypeCache.Extensions; 8 | using TypeCache.Utilities; 9 | using Xunit; 10 | 11 | namespace TypeCache.Tests.Extensions; 12 | 13 | public class ActionExtensions 14 | { 15 | [Fact] 16 | public async Task Retry() 17 | { 18 | var attempt = 0; 19 | var action = () => 20 | { 21 | if (++attempt < 4) 22 | throw new Exception(); 23 | 24 | Console.WriteLine($"Action.{nameof(Retry)} Attempt #: {attempt}"); 25 | }; 26 | 27 | var timeProvider = new FakeTimeProvider(); 28 | 29 | var task = action.Retry(Sequence.LinearTime(1.Seconds(), 10), timeProvider); 30 | while (!task.IsCompleted) 31 | timeProvider.Advance(1.Seconds()); 32 | 33 | await task; 34 | } 35 | 36 | [Fact] 37 | public void Timed() 38 | { 39 | var expected = TimeSpan.FromMilliseconds(10); 40 | var actual = new Action(() => Thread.Sleep(expected)).Timed(); 41 | 42 | Assert.True(expected <= actual); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/BooleanExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using TypeCache.Extensions; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Extensions; 8 | 9 | public class BooleanExtensions 10 | { 11 | [Fact] 12 | public void Else() 13 | { 14 | var actual = false; 15 | 16 | true.Else(() => actual = true); 17 | Assert.False(actual); 18 | 19 | false.Else(() => actual = true); 20 | Assert.True(actual); 21 | } 22 | 23 | [Fact] 24 | public void Then() 25 | { 26 | var actual = false; 27 | 28 | false.Then(() => actual = true); 29 | Assert.False(actual); 30 | 31 | true.Then(() => actual = true); 32 | Assert.True(actual); 33 | } 34 | 35 | [Fact] 36 | public void ThrowIfFalse() 37 | { 38 | true.ThrowIfFalse(); 39 | Assert.Throws(() => false.ThrowIfFalse()); 40 | } 41 | 42 | [Fact] 43 | public void ThrowIfNotTrue() 44 | { 45 | ((bool?)true).ThrowIfNotTrue(); 46 | Assert.Throws(() => ((bool?)false).ThrowIfNotTrue()); 47 | Assert.Throws(() => ((bool?)null).ThrowIfNotTrue()); 48 | } 49 | 50 | [Fact] 51 | public void ThrowIfTrue() 52 | { 53 | false.ThrowIfTrue(); 54 | ((bool?)false).ThrowIfTrue(); 55 | ((bool?)null).ThrowIfTrue(); 56 | Assert.Throws(() => true.ThrowIfTrue()); 57 | Assert.Throws(() => ((bool?)true).ThrowIfTrue()); 58 | } 59 | 60 | [Fact] 61 | public void ToBytes() 62 | { 63 | Assert.Equal(BitConverter.GetBytes(false), false.ToBytes()); 64 | Assert.Equal(BitConverter.GetBytes(true), true.ToBytes()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/ComparableExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using TypeCache.Extensions; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Extensions; 8 | 9 | public class ComparableExtensions 10 | { 11 | [Fact] 12 | public void EqualTo() 13 | { 14 | Assert.False(((IComparable)2).EqualTo(1)); 15 | Assert.True(((IComparable)2).EqualTo(2)); 16 | Assert.False(((IComparable)2).EqualTo(3)); 17 | Assert.False(2.EqualTo(1)); 18 | Assert.True(2.EqualTo(2)); 19 | Assert.False(2.EqualTo(3)); 20 | } 21 | 22 | [Fact] 23 | public void GreaterThan() 24 | { 25 | Assert.True(((IComparable)2).GreaterThan(1)); 26 | Assert.False(((IComparable)2).GreaterThan(2)); 27 | Assert.False(((IComparable)2).GreaterThan(3)); 28 | Assert.True(2.GreaterThan(1)); 29 | Assert.False(2.GreaterThan(2)); 30 | Assert.False(2.GreaterThan(3)); 31 | } 32 | 33 | [Fact] 34 | public void GreaterThanOrEqualTo() 35 | { 36 | Assert.True(((IComparable)2).GreaterThanOrEqualTo(1)); 37 | Assert.True(((IComparable)2).GreaterThanOrEqualTo(2)); 38 | Assert.False(((IComparable)2).GreaterThanOrEqualTo(3)); 39 | Assert.True(2.GreaterThanOrEqualTo(1)); 40 | Assert.True(2.GreaterThanOrEqualTo(2)); 41 | Assert.False(2.GreaterThanOrEqualTo(3)); 42 | } 43 | 44 | [Fact] 45 | public void LessThan() 46 | { 47 | Assert.False(((IComparable)2).LessThan(1)); 48 | Assert.False(((IComparable)2).LessThan(2)); 49 | Assert.True(((IComparable)2).LessThan(3)); 50 | Assert.False(2.LessThan(1)); 51 | Assert.False(2.LessThan(2)); 52 | Assert.True(2.LessThan(3)); 53 | } 54 | 55 | [Fact] 56 | public void LessThanOrEqualTo() 57 | { 58 | Assert.False(((IComparable)2).LessThanOrEqualTo(1)); 59 | Assert.True(((IComparable)2).LessThanOrEqualTo(2)); 60 | Assert.True(((IComparable)2).LessThanOrEqualTo(3)); 61 | Assert.False(2.LessThanOrEqualTo(1)); 62 | Assert.True(2.LessThanOrEqualTo(2)); 63 | Assert.True(2.LessThanOrEqualTo(3)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using TypeCache.Extensions; 7 | using Xunit; 8 | 9 | namespace TypeCache.Tests.Extensions; 10 | 11 | public class DictionaryExtensions 12 | { 13 | [Fact] 14 | public void If() 15 | { 16 | var dictionary = new Dictionary() 17 | { 18 | { 1, "111" }, 19 | { 2, "222" }, 20 | { 3, "333" }, 21 | }; 22 | Action action = () => 0.ToString(); 23 | Action> actionPair = pair => pair.ToString(); 24 | 25 | Assert.True(dictionary.If(1, action)); 26 | Assert.False(dictionary.If(0, action)); 27 | Assert.True(dictionary.If(2, actionPair)); 28 | Assert.False(dictionary.If(-1, actionPair)); 29 | } 30 | 31 | [Fact] 32 | public void ToReadOnly() 33 | { 34 | IDictionary intStringDictionary = new Dictionary() 35 | { 36 | { 1, "111" }, 37 | { 2, "222" }, 38 | { 3, "333" }, 39 | }; 40 | IDictionary stringIntDictionary = new Dictionary() 41 | { 42 | { "111", 1 }, 43 | { "222", 2 }, 44 | { "333", 3 }, 45 | }; 46 | 47 | var intReadOnlyDictionary = intStringDictionary.ToReadOnly(); 48 | Assert.NotNull(intReadOnlyDictionary); 49 | Assert.IsType>(intReadOnlyDictionary); 50 | 51 | var stringReadOnlyDictionary = stringIntDictionary.ToReadOnly(); 52 | Assert.NotNull(stringReadOnlyDictionary); 53 | Assert.IsType>(stringReadOnlyDictionary); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using TypeCache.Extensions; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Extensions; 8 | 9 | public class EnumerableExtensions 10 | { 11 | [Fact] 12 | public void ThrowIfEmpty() 13 | { 14 | "AAA".ThrowIfEmpty(); 15 | Assert.Throws(() => (null as string).ThrowIfEmpty()); 16 | Assert.Throws(() => string.Empty.ThrowIfEmpty()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/EnumeratorExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TypeCache.Extensions; 6 | using Xunit; 7 | 8 | namespace TypeCache.Tests.Extensions; 9 | 10 | public class EnumeratorExtensions 11 | { 12 | private IEnumerable GetInts() 13 | { 14 | yield return 1; 15 | yield return 2; 16 | yield return 3; 17 | yield return 4; 18 | yield return 5; 19 | yield return 6; 20 | yield return 7; 21 | } 22 | 23 | private IEnumerable GetStrings() 24 | { 25 | yield return "aaa"; 26 | yield return ""; 27 | yield return null; 28 | yield return "aaa"; 29 | yield return "Aaa"; 30 | yield return "BBB"; 31 | yield return "CcC"; 32 | } 33 | 34 | [Fact] 35 | public void Count() 36 | { 37 | Assert.Equal(7, this.GetInts().GetEnumerator().Count()); 38 | Assert.Equal(7, this.GetStrings().GetEnumerator().Count()); 39 | } 40 | 41 | [Fact] 42 | public void IfNext() 43 | { 44 | var enumerator = this.GetInts().GetEnumerator(); 45 | Assert.True(enumerator.IfNext(out object item)); 46 | Assert.True(enumerator.IfNext(out int value)); 47 | 48 | while (enumerator.MoveNext()) { } 49 | Assert.False(enumerator.IfNext(out _)); 50 | Assert.False(enumerator.IfNext(out _)); 51 | } 52 | 53 | [Fact] 54 | public void Move() 55 | { 56 | var enumerator = this.GetInts().GetEnumerator(); 57 | 58 | Assert.True(enumerator.Move(4)); 59 | Assert.Equal(new[] { 5, 6, 7 }, enumerator.Rest().ToArray()); 60 | Assert.False(enumerator.Move(1)); 61 | Assert.True(enumerator.Move(0)); 62 | } 63 | 64 | [Fact] 65 | public void Next() 66 | { 67 | var enumerator = this.GetStrings().GetEnumerator(); 68 | var list = new List(2); 69 | 70 | while (enumerator.Next() is not null) 71 | list.Add(enumerator.Current); 72 | 73 | Assert.Equal(2, list.Count); 74 | } 75 | 76 | [Fact] 77 | public void Rest() 78 | { 79 | var enumerator = (1..8).ToEnumerable().GetEnumerator(); 80 | 81 | Assert.True(enumerator.Move(4)); 82 | Assert.Equal(new[] { 5, 6, 7 }, enumerator.Rest().ToArray()); 83 | Assert.Empty(enumerator.Rest()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/EqualityComparerExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using TypeCache.Extensions; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Extensions; 8 | 9 | public class EqualityComparerExtensions 10 | { 11 | [Fact] 12 | public void ThrowIfEqual() 13 | { 14 | StringComparer.Ordinal.ThrowIfEqual("AAA", "BBB"); 15 | StringComparer.OrdinalIgnoreCase.ThrowIfEqual("AAA", "bbb"); 16 | StringComparer.OrdinalIgnoreCase.ThrowIfEqual("AAA", "aba"); 17 | Assert.Throws(() => StringComparer.Ordinal.ThrowIfEqual("AAA", "AAA")); 18 | Assert.Throws(() => StringComparer.OrdinalIgnoreCase.ThrowIfEqual("AAA", "aaa")); 19 | Assert.Throws(() => StringComparer.OrdinalIgnoreCase.ThrowIfEqual("ABA", "aba")); 20 | } 21 | 22 | [Fact] 23 | public void ThrowIfNotEqual() 24 | { 25 | StringComparer.Ordinal.ThrowIfNotEqual("AAA", "AAA"); 26 | StringComparer.OrdinalIgnoreCase.ThrowIfNotEqual("AAA", "aaa"); 27 | StringComparer.OrdinalIgnoreCase.ThrowIfNotEqual("AAA", "AAA"); 28 | Assert.Throws(() => StringComparer.Ordinal.ThrowIfNotEqual("AAA", "bbb")); 29 | Assert.Throws(() => StringComparer.OrdinalIgnoreCase.ThrowIfNotEqual("AAA", "bbb")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/EquatableExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using TypeCache.Extensions; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Extensions; 8 | 9 | public class EquatableExtensions 10 | { 11 | [Fact] 12 | public void ThrowIfEqual() 13 | { 14 | "AAA".ThrowIfEqual("AA"); 15 | (null as string).ThrowIfEqual(string.Empty); 16 | Assert.Throws(() => "bbb".ThrowIfEqual("bbb")); 17 | Assert.Throws(() => (null as string).ThrowIfEqual(null as string)); 18 | Assert.Throws(() => "AAA".ThrowIfEqual("AAA")); 19 | } 20 | 21 | [Fact] 22 | public void ThrowIfNotEqual() 23 | { 24 | "AAA".ThrowIfNotEqual("AAA"); 25 | (null as string).ThrowIfNotEqual(null); 26 | Assert.Throws(() => "AAA".ThrowIfNotEqual("bbb")); 27 | Assert.Throws(() => (null as string).ThrowIfNotEqual("bbb")); 28 | Assert.Throws(() => "AAA".ThrowIfNotEqual(null)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/FuncExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Time.Testing; 7 | using NSubstitute; 8 | using TypeCache.Extensions; 9 | using TypeCache.Utilities; 10 | using Xunit; 11 | using static System.Collections.Specialized.BitVector32; 12 | 13 | namespace TypeCache.Tests.Extensions; 14 | 15 | public class FuncExtensions 16 | { 17 | [Fact] 18 | public async Task Retry() 19 | { 20 | var expectedValue = 22; 21 | var attempt = 0; 22 | var func = () => 23 | { 24 | if (++attempt < 4) 25 | throw new Exception(); 26 | 27 | Console.WriteLine($"Func {nameof(Retry)} Attempt #: {attempt}"); 28 | return expectedValue; 29 | }; 30 | 31 | var timeProvider = new FakeTimeProvider(); 32 | 33 | var task = func.Retry(Sequence.LinearTime(1.Seconds(), 10), timeProvider); 34 | while (!task.IsCompleted) 35 | timeProvider.Advance(1.Seconds()); 36 | 37 | var actualValue = await task; 38 | Assert.Equal(expectedValue, actualValue); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/GlobalExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using TypeCache.Extensions; 7 | using Xunit; 8 | 9 | namespace TypeCache.Tests.Extensions; 10 | 11 | public class GlobalExtensions 12 | { 13 | [Fact] 14 | public void Between() 15 | { 16 | Assert.False((-1).Between(1, 3)); 17 | Assert.True(1.Between(1, 3)); 18 | Assert.True(2U.Between(1U, 3U)); 19 | Assert.True(3L.Between(1L, 3L)); 20 | Assert.False(4UL.Between(1UL, 3UL)); 21 | } 22 | 23 | [Fact] 24 | public void Box() 25 | { 26 | var expected = 333333; 27 | var box = expected.Box(); 28 | Assert.Equal(expected, (int)box); 29 | } 30 | 31 | [Fact] 32 | public void InBetween() 33 | { 34 | Assert.False((-1).InBetween(1, 3)); 35 | Assert.False(1.InBetween(1, 3)); 36 | Assert.True(2U.InBetween(1U, 3U)); 37 | Assert.False(3L.InBetween(1L, 3L)); 38 | Assert.False(4UL.InBetween(1UL, 3UL)); 39 | } 40 | 41 | [Theory] 42 | [InlineData(0)] 43 | [InlineData(1)] 44 | [InlineData(10)] 45 | [InlineData(19)] 46 | public void Repeat(int expected) 47 | { 48 | Assert.Equal(expected, true.Repeat(expected).Count()); 49 | } 50 | 51 | [Fact] 52 | public void Swap() 53 | { 54 | decimal a = 1M; 55 | decimal b = 2M; 56 | 57 | a.Swap(ref b); 58 | 59 | Assert.Equal(1M, b); 60 | Assert.Equal(2M, a); 61 | 62 | var swap = (a, b).Swap(); 63 | 64 | Assert.Equal(1M, swap.Item1); 65 | Assert.Equal(2M, swap.Item2); 66 | } 67 | 68 | [Fact] 69 | public void ThrowIfEqual() 70 | { 71 | 123456.ThrowIfEqual(12345); 72 | Assert.Throws(() => 123.ThrowIfEqual(123)); 73 | } 74 | 75 | [Fact] 76 | public void ThrowIfNotEqual() 77 | { 78 | 123456.ThrowIfNotEqual(123456); 79 | Assert.Throws(() => 123.ThrowIfNotEqual(456)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/IndexExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using TypeCache.Extensions; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Extensions; 8 | 9 | public class IndexExtensions 10 | { 11 | [Fact] 12 | public void FromStart() 13 | { 14 | Assert.Equal(2, new Index(8, true).FromStart(10).Value); 15 | Assert.Equal(6, new Index(6).FromStart(10).Value); 16 | } 17 | 18 | [Fact] 19 | public void Next() 20 | { 21 | Assert.Equal(5, new Index(3, true).Next(2).Value); 22 | Assert.Equal(5, new Index(4, true).Next().Value); 23 | } 24 | 25 | [Fact] 26 | public void Previous() 27 | { 28 | Assert.Equal(3, new Index(5, true).Previous(2).Value); 29 | Assert.Equal(3, new Index(4, true).Previous().Value); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using TypeCache.Extensions; 9 | using Xunit; 10 | 11 | namespace TypeCache.Tests.Extensions; 12 | 13 | public class ListExtensions 14 | { 15 | [Fact] 16 | public void AddIfNotBlank() 17 | { 18 | var list = new List(1); 19 | list.AddIfNotBlank(" \n \r "); 20 | 21 | Assert.Empty(list); 22 | 23 | list.AddIfNotBlank("AAA"); 24 | Assert.Equal(1, list.Count); 25 | } 26 | 27 | [Fact] 28 | public void AddIfNotNull() 29 | { 30 | var list = new List(1); 31 | list.AddIfNotNull(null); 32 | 33 | Assert.Empty(list); 34 | 35 | list.AddIfNotNull(string.Empty); 36 | Assert.Equal(1, list.Count); 37 | } 38 | 39 | [Fact] 40 | public void InsertIfNotBlank() 41 | { 42 | var list = new List { "1", "2", "4" }; 43 | list.InsertIfNotBlank(2, " \n \r "); 44 | 45 | Assert.Equal(3, list.Count); 46 | 47 | list.InsertIfNotBlank(2, "AAA"); 48 | Assert.Equal(4, list.Count); 49 | } 50 | 51 | [Fact] 52 | public void InsertIfNotNull() 53 | { 54 | var list = new List { "1", "2", "4" }; 55 | list.InsertIfNotNull(2, null); 56 | 57 | Assert.Equal(3, list.Count); 58 | 59 | list.InsertIfNotNull(2, string.Empty); 60 | Assert.Equal(4, list.Count); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/MathExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using TypeCache.Extensions; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Extensions; 8 | 9 | public class MathExtensions 10 | { 11 | [Fact] 12 | public void AbsoluteValue() 13 | { 14 | Assert.Equal(-(sbyte.MinValue + 1), (sbyte.MinValue + 1).Abs()); 15 | Assert.Equal(-(short.MinValue + 1), (short.MinValue + 1).Abs()); 16 | Assert.Equal(-(int.MinValue + 1), (int.MinValue + 1).Abs()); 17 | Assert.Equal(-(long.MinValue + 1), (long.MinValue + 1).Abs()); 18 | Assert.Equal(1F, (-1F).Abs()); 19 | Assert.Equal(1D, (-1D).Abs()); 20 | Assert.Equal(1M, (-1M).Abs()); 21 | } 22 | 23 | [Fact] 24 | public void BitDecrement() 25 | { 26 | Assert.Equal(1D, 1D.BitIncrement().BitDecrement()); 27 | } 28 | 29 | [Fact] 30 | public void BitIncrement() 31 | { 32 | Assert.Equal(1D, 1D.BitDecrement().BitIncrement()); 33 | } 34 | 35 | [Fact] 36 | public void Ceiling() 37 | { 38 | Assert.Equal(2D, 1.111D.Ceiling()); 39 | Assert.Equal(2M, 1.111M.Ceiling()); 40 | } 41 | 42 | [Fact] 43 | public void Floor() 44 | { 45 | Assert.Equal(1D, 1.111D.Floor()); 46 | Assert.Equal(1M, 1.111M.Floor()); 47 | } 48 | 49 | [Fact] 50 | public void Round() 51 | { 52 | Assert.Equal(123D, 123.456D.Round()); 53 | Assert.Equal(123.5D, 123.456D.Round(1)); 54 | Assert.Equal(123D, 123.456D.Round(MidpointRounding.AwayFromZero)); 55 | Assert.Equal(123.46D, 123.456D.Round(2, MidpointRounding.ToEven)); 56 | Assert.Equal(123M, 123.456M.Round()); 57 | Assert.Equal(123.5M, 123.456M.Round(1)); 58 | Assert.Equal(123M, 123.456M.Round(MidpointRounding.AwayFromZero)); 59 | Assert.Equal(123.46M, 123.456M.Round(2, MidpointRounding.ToEven)); 60 | } 61 | 62 | [Fact] 63 | public void Sign() 64 | { 65 | Assert.Equal(1, sbyte.MaxValue.Sign()); 66 | Assert.Equal(-1, sbyte.MinValue.Sign()); 67 | Assert.Equal(1, short.MaxValue.Sign()); 68 | Assert.Equal(-1, short.MinValue.Sign()); 69 | Assert.Equal(1, int.MaxValue.Sign()); 70 | Assert.Equal(-1, int.MinValue.Sign()); 71 | Assert.Equal(1, long.MaxValue.Sign()); 72 | Assert.Equal(-1, long.MinValue.Sign()); 73 | Assert.Equal(-1, -666F.Sign()); 74 | Assert.Equal(1, 666F.Sign()); 75 | Assert.Equal(-1, -666D.Sign()); 76 | Assert.Equal(1, 666D.Sign()); 77 | Assert.Equal(-1, -666M.Sign()); 78 | Assert.Equal(1, 666M.Sign()); 79 | } 80 | 81 | [Fact] 82 | public void Truncate() 83 | { 84 | Assert.Equal(-123D, -123.456D.Truncate()); 85 | Assert.Equal(123M, 123.456M.Truncate()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using TypeCache.Extensions; 6 | using Xunit; 7 | 8 | namespace TypeCache.Tests.Extensions; 9 | 10 | public class ObjectExtensions 11 | { 12 | public void ThrowIfEqual() 13 | { 14 | var list = new List(0); 15 | Assert.Throws(() => list.ThrowIfEqual(list)); 16 | new List(0).ThrowIfEqual(new List(0)); 17 | } 18 | 19 | public void ThrowIfNotEqual() 20 | { 21 | var list = new List(0); 22 | list.ThrowIfNotEqual(list); 23 | Assert.Throws(() => new List(0).ThrowIfNotEqual(new List(0))); 24 | } 25 | 26 | public void ThrowIfNot() 27 | { 28 | true.ThrowIfNot(); 29 | 999.ThrowIfNot(); 30 | "AAA".ThrowIfNot(); 31 | Assert.Throws(() => true.ThrowIfNot()); 32 | Assert.Throws(() => 999.ThrowIfNot()); 33 | Assert.Throws(() => "AAA".ThrowIfNot()); 34 | } 35 | 36 | public void ThrowIfNotSame() 37 | { 38 | var item = new object(); 39 | item.ThrowIfNotReferenceEqual(item); 40 | Assert.Throws(() => new object().ThrowIfNotReferenceEqual(new object())); 41 | } 42 | 43 | public void ThrowIfNull() 44 | { 45 | ((int?)123456).ThrowIfNull(); 46 | "AAA".ThrowIfNull(); 47 | Assert.Throws(() => (null as string).ThrowIfNull()); 48 | Assert.Throws(() => (null as int?).ThrowIfNull()); 49 | } 50 | 51 | public void ThrowIfSame() 52 | { 53 | var item = new object(); 54 | Assert.Throws(() => item.ThrowIfReferenceEqual(item)); 55 | new object().ThrowIfReferenceEqual(new object()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/ReadOnlySpanExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using TypeCache.Extensions; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Extensions; 8 | 9 | public class ReadOnlySpanExtensions 10 | { 11 | [Fact] 12 | public void Read() 13 | { 14 | Assert.True(((ReadOnlySpan)true.ToBytes().AsSpan()).Read()); 15 | Assert.False(((ReadOnlySpan)false.ToBytes().AsSpan()).Read()); 16 | Assert.Equal(char.MinValue, ((ReadOnlySpan)char.MinValue.ToBytes()).Read()); 17 | Assert.Equal(char.MaxValue, ((ReadOnlySpan)char.MaxValue.ToBytes()).Read()); 18 | Assert.Equal(float.MinValue, ((ReadOnlySpan)float.MinValue.ToBytes()).Read()); 19 | Assert.Equal(float.MaxValue, ((ReadOnlySpan)float.MaxValue.ToBytes()).Read()); 20 | Assert.Equal(double.MinValue, ((ReadOnlySpan)double.MinValue.ToBytes()).Read()); 21 | Assert.Equal(double.MaxValue, ((ReadOnlySpan)double.MaxValue.ToBytes()).Read()); 22 | Assert.Equal(short.MinValue, ((ReadOnlySpan)short.MinValue.ToBytes()).Read()); 23 | Assert.Equal(short.MaxValue, ((ReadOnlySpan)short.MaxValue.ToBytes()).Read()); 24 | Assert.Equal(int.MinValue, ((ReadOnlySpan)int.MinValue.ToBytes()).Read()); 25 | Assert.Equal(int.MaxValue, ((ReadOnlySpan)int.MaxValue.ToBytes()).Read()); 26 | Assert.Equal(long.MinValue, ((ReadOnlySpan)long.MinValue.ToBytes()).Read()); 27 | Assert.Equal(long.MaxValue, ((ReadOnlySpan)long.MaxValue.ToBytes()).Read()); 28 | Assert.Equal(ushort.MinValue, ((ReadOnlySpan)ushort.MinValue.ToBytes()).Read()); 29 | Assert.Equal(ushort.MaxValue, ((ReadOnlySpan)ushort.MaxValue.ToBytes()).Read()); 30 | Assert.Equal(uint.MinValue, ((ReadOnlySpan)uint.MinValue.ToBytes()).Read()); 31 | Assert.Equal(uint.MaxValue, ((ReadOnlySpan)uint.MaxValue.ToBytes()).Read()); 32 | Assert.Equal(ulong.MinValue, ((ReadOnlySpan)ulong.MinValue.ToBytes()).Read()); 33 | Assert.Equal(ulong.MaxValue, ((ReadOnlySpan)ulong.MaxValue.ToBytes()).Read()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Extensions/ValueExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Linq; 5 | using TypeCache.Extensions; 6 | using TypeCache.Utilities; 7 | using Xunit; 8 | 9 | namespace TypeCache.Tests.Extensions; 10 | 11 | public class ValueExtensions 12 | { 13 | [Fact] 14 | public void Normalize() 15 | { 16 | Assert.Equal(Index.FromStart(15), Index.FromEnd(5).FromStart(20)); 17 | Assert.Equal(Index.FromStart(5), Index.FromStart(5).FromStart(20)); 18 | } 19 | 20 | [Fact] 21 | public void Repeat() 22 | { 23 | Assert.Equal(new[] { 'f', 'f', 'f', 'f', 'f', 'f' }, 'f'.Repeat(6).ToArray()); 24 | Assert.Equal(Array.Empty, 123.Repeat(0).ToArray()); 25 | Assert.Equal(Enumerable.Empty, 123.Repeat(-18)); 26 | } 27 | 28 | [Fact] 29 | public void Swap() 30 | { 31 | var a = 123; 32 | var b = -456; 33 | b.Swap(ref a); 34 | 35 | Assert.Equal(-456, a); 36 | Assert.Equal(123, b); 37 | } 38 | 39 | [Fact] 40 | public void ToBytes() 41 | { 42 | Assert.Equal(16, (-999999.9999M).ToBytes().Length); 43 | Assert.Equal(16, 999999.9999M.ToBytes().Length); 44 | 45 | Assert.NotEmpty(decimal.MinValue.ToBytes()); 46 | Assert.NotEmpty(decimal.MaxValue.ToBytes()); 47 | } 48 | 49 | [Fact] 50 | public void ToDouble() 51 | { 52 | Assert.Equal(double.MinValue, double.MinValue.ToInt64().ToDouble()); 53 | Assert.Equal(double.MaxValue, double.MaxValue.ToInt64().ToDouble()); 54 | } 55 | 56 | [Fact] 57 | public void ToSingle() 58 | { 59 | Assert.Equal(float.MinValue, float.MinValue.ToInt32().ToSingle()); 60 | Assert.Equal(float.MaxValue, float.MaxValue.ToInt32().ToSingle()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/TypeCache.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | false 7 | 8 | False 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Utilities/EnumTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System; 4 | using System.Reflection; 5 | using TypeCache.Extensions; 6 | using TypeCache.Utilities; 7 | using Xunit; 8 | 9 | namespace TypeCache.Tests.Utilities; 10 | 11 | public class EnumTests 12 | { 13 | [Fact] 14 | public void EnumOfBindingFlags() 15 | { 16 | Assert.Equal(typeof(BindingFlags).GetCustomAttributes(), Enum.Attributes); 17 | Assert.True(Enum.Flags); 18 | Assert.Equal(typeof(BindingFlags).Name, Enum.Name); 19 | Assert.Equal(ScalarType.Int32, Enum.UnderlyingType.GetScalarType()); 20 | Assert.Equal(typeof(int).TypeHandle, Enum.UnderlyingType.TypeHandle); 21 | } 22 | 23 | [Fact] 24 | public void EnumOfDataType() 25 | { 26 | Assert.Equal(typeof(ScalarType).GetCustomAttributes(), Enum.Attributes); 27 | Assert.Empty(Enum.Attributes); 28 | Assert.False(Enum.Flags); 29 | Assert.Equal(typeof(ScalarType).Name, Enum.Name); 30 | Assert.Equal(ScalarType.Int32, Enum.UnderlyingType.GetScalarType()); 31 | Assert.Equal(typeof(int).TypeHandle, Enum.UnderlyingType.TypeHandle); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Utilities/EventOfTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.IO; 4 | using TypeCache.Utilities; 5 | using Xunit; 6 | 7 | namespace TypeCache.Tests.Utilities; 8 | 9 | public class EventOfTests 10 | { 11 | [Fact] 12 | public void EventOfFileSystemWatcher() 13 | { 14 | Assert.Equal(6, EventHandler.Events.Length); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/TypeCache.Tests/Utilities/HashMakerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Samuel Abraham 2 | 3 | using System.Text; 4 | using TypeCache.Extensions; 5 | using TypeCache.Utilities; 6 | using Xunit; 7 | 8 | namespace TypeCache.Tests.Utilities; 9 | 10 | public class HashMakerTests 11 | { 12 | private readonly byte[] _Key = Encoding.UTF8.GetBytes("123456ABCDEFghij"); 13 | 14 | [Fact] 15 | public void Encrypt() 16 | { 17 | using var hashMaker = new HashMaker(this._Key); 18 | 19 | var rgbIV = Encoding.UTF8.GetBytes("XXXXyyyyZZZZ1234"); 20 | var id = 998999L; 21 | var hashId = hashMaker.Encrypt(id, rgbIV); 22 | 23 | Assert.Equal("61O-LQ358J9GQOxSZBmEWg", hashId); 24 | 25 | var bytes = hashMaker.Encrypt(id.ToBytes(), rgbIV); 26 | 27 | } 28 | } 29 | --------------------------------------------------------------------------------