diff options
author | crupest <crupest@outlook.com> | 2022-12-05 13:50:52 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2022-12-20 20:32:52 +0800 |
commit | 9f7162f12944cc722dba9e91f4d30aa312142d49 (patch) | |
tree | b6e68cfecdfcdb25b240277d667d26eb1d1b6806 | |
parent | 2d63ac857ed1082f6f7d365674aa734c582a99dd (diff) | |
download | crupest-9f7162f12944cc722dba9e91f4d30aa312142d49.tar.gz crupest-9f7162f12944cc722dba9e91f4d30aa312142d49.tar.bz2 crupest-9f7162f12944cc722dba9e91f4d30aa312142d49.zip |
Develop secret api. v7
7 files changed, 214 insertions, 8 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/CrupestApi.Commons.Tests.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/CrupestApi.Commons.Tests.csproj new file mode 100644 index 0000000..59edf0d --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/CrupestApi.Commons.Tests.csproj @@ -0,0 +1,28 @@ +<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net7.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="coverlet.collector" Version="3.2.0">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\CrupestApi.Commons\CrupestApi.Commons.csproj" />
+ </ItemGroup>
+
+</Project>
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Usings.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit;
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs new file mode 100644 index 0000000..8b4607d --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs @@ -0,0 +1,69 @@ +using System.Reflection; + +namespace CrupestApi.Commons.Crud; + +public class ColumnInfo +{ + private Type ExtractRealTypeFromNullable(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return type.GetGenericArguments()[0]; + } + + return type; + } + + // A column with no property. + public ColumnInfo(Type entityType, string sqlColumnName, bool isPrimaryKey, bool isAutoIncrement, IColumnTypeInfo typeInfo) + { + EntityType = entityType; + PropertyName = null; + PropertyType = typeof(int); + PropertyRealType = typeof(int); + SqlColumnName = sqlColumnName; + ColumnTypeInfo = typeInfo; + Nullable = false; + IsPrimaryKey = isPrimaryKey; + IsAutoIncrement = isAutoIncrement; + } + + public ColumnInfo(Type entityType, string entityPropertyName) + { + EntityType = entityType; + PropertyName = entityPropertyName; + + var property = entityType.GetProperty(entityPropertyName); + + if (property is null) + throw new Exception("Public property with given name does not exist."); + + PropertyType = property.PropertyType; + PropertyRealType = ExtractRealTypeFromNullable(PropertyType); + + var columnAttribute = property.GetCustomAttribute<ColumnAttribute>(); + if (columnAttribute is null) + { + SqlColumnName = PropertyName; + Nullable = true; + } + else + { + SqlColumnName = columnAttribute.DatabaseName ?? PropertyName; + Nullable = !columnAttribute.NonNullable; + } + ColumnTypeInfo = ColumnTypeInfoRegistry.Singleton.GetRequiredByDataType(PropertyRealType); + + } + + public Type EntityType { get; } + // If null, there is no corresponding property. + public string? PropertyName { get; } + public Type PropertyType { get; } + public Type PropertyRealType { get; } + public string SqlColumnName { get; } + public IColumnTypeInfo ColumnTypeInfo { get; } + public bool Nullable { get; } + public bool IsPrimaryKey { get; } + public bool IsAutoIncrement { get; } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs new file mode 100644 index 0000000..cbf38f7 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs @@ -0,0 +1,21 @@ +namespace CrupestApi.Commons.Crud; + +public interface IColumnMetadata +{ + +} + +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +public class ColumnAttribute : Attribute, IColumnMetadata +{ + // if null, use the property name. + public string? DatabaseName { get; set; } + + // default false. + public bool NonNullable { get; set; } + + // default false + public bool IsPrimaryKey { get; set; } + + public bool IsAutoIncrement { get; set; } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs index 02848f3..1bdc9c0 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace CrupestApi.Commons; +namespace CrupestApi.Commons.Crud; public interface IColumnTypeInfo { @@ -15,7 +15,7 @@ public interface IColumnTypeInfo IColumnTypeInfo current = this; - while (!typeof(BuiltinColumnTypeInfo<>).IsInstanceOfType(current)) + while (current is not IBuiltinColumnTypeInfo) { var dataType = GetDataType(); @@ -86,7 +86,12 @@ public class BuiltinColumnTypeInfo<T> : IBuiltinColumnTypeInfo } } -public abstract class CustomColumnTypeInfo<TDataType, TDatabaseType> : IColumnTypeInfo +public interface ICustomColumnTypeInfo : IColumnTypeInfo +{ + +} + +public abstract class CustomColumnTypeInfo<TDataType, TDatabaseType> : ICustomColumnTypeInfo where TDataType : notnull where TDatabaseType : notnull { @@ -105,13 +110,13 @@ public abstract class CustomColumnTypeInfo<TDataType, TDatabaseType> : IColumnTy object IColumnTypeInfo.ConvertToDatabase(object data) { - Debug.Assert(typeof(TDataType).IsInstanceOfType(data)); + Debug.Assert(data is TDataType); return ConvertToDatabase((TDataType)data); } object IColumnTypeInfo.ConvertFromDatabase(object databaseData) { - Debug.Assert(typeof(TDatabaseType).IsInstanceOfType(databaseData)); + Debug.Assert(databaseData is TDatabaseType); return ConvertFromDatabase((TDatabaseType)databaseData); } } @@ -155,8 +160,6 @@ public class ColumnTypeInfoRegistry Singleton.Validate(); } - - private readonly List<IColumnTypeInfo> _list; private readonly Dictionary<Type, IColumnTypeInfo> _map; private bool _dirty = false; @@ -180,6 +183,11 @@ public class ColumnTypeInfoRegistry return _map.GetValueOrDefault(type); } + public IColumnTypeInfo GetRequiredByDataType(Type type) + { + return GetByDataType(type) ?? throw new Exception("Unsupported type."); + } + public string GetSqlType(Type type) { EnsureValidity(); @@ -190,7 +198,7 @@ public class ColumnTypeInfoRegistry throw new Exception("Unsupported type for sql."); } - while (!typeof(IBuiltinColumnTypeInfo).IsInstanceOfType(current)) + while (current is not IBuiltinColumnTypeInfo) { current = GetByDataType(current.GetDatabaseType()); Debug.Assert(current is not null); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs new file mode 100644 index 0000000..fb9e1ad --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs @@ -0,0 +1,73 @@ +namespace CrupestApi.Commons.Crud; + +public class TableInfo +{ + public TableInfo(Type entityType) + { + EntityType = entityType; + + var properties = entityType.GetProperties(); + + var columnInfos = new List<ColumnInfo>(); + + bool hasPrimaryKey = false; + bool hasId = false; + + foreach (var property in properties) + { + var columnInfo = new ColumnInfo(entityType, property.Name); + columnInfos.Add(columnInfo); + if (columnInfo.IsPrimaryKey) + hasPrimaryKey = true; + if (columnInfo.SqlColumnName.Equals("id", StringComparison.OrdinalIgnoreCase)) + { + hasId = true; + } + } + + if (!hasPrimaryKey) + { + if (hasId) throw new Exception("A column named id already exists but is not primary key."); + var columnInfo = new ColumnInfo(entityType, "id", true, true, ColumnTypeInfoRegistry.Singleton.GetRequiredByDataType(typeof(int))); + columnInfos.Add(columnInfo); + } + + ColumnInfos = columnInfos; + + CheckValidity(); + } + + public Type EntityType { get; } + public IReadOnlyList<ColumnInfo> ColumnInfos { get; } + + public void CheckValidity() + { + // Check if there is only one primary key. + bool hasPrimaryKey = false; + foreach (var column in ColumnInfos) + { + if (column.IsPrimaryKey) + { + if (hasPrimaryKey) throw new Exception("Two columns are primary key."); + hasPrimaryKey = true; + } + } + + if (!hasPrimaryKey) throw new Exception("No column is primary key."); + + // Check two columns have the same sql name. + HashSet<string> sqlNameSet = new HashSet<string>(); + + foreach (var column in ColumnInfos) + { + if (sqlNameSet.Contains(column.SqlColumnName)) + throw new Exception($"Two columns have the same sql name '{column.SqlColumnName}'."); + sqlNameSet.Add(column.SqlColumnName); + } + } + + public string GenerateCreateTableSql() + { + throw new NotImplementedException(); + } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.sln b/docker/crupest-api/CrupestApi/CrupestApi.sln index 5f21ff5..ebfd960 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.sln +++ b/docker/crupest-api/CrupestApi/CrupestApi.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Secrets", "Crupe EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Commons", "CrupestApi.Commons\CrupestApi.Commons.csproj", "{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Commons.Tests", "CrupestApi.Commons.Tests\CrupestApi.Commons.Tests.csproj", "{0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -36,5 +38,9 @@ Global {38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
|