Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions GFramework.Core.Tests/Rule/ContextAwareServiceExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
using GFramework.Core.Extensions;
using GFramework.Core.Ioc;
using GFramework.Core.Rule;
using GFramework.Core.Tests.Architectures;

namespace GFramework.Core.Tests.Rule;

Expand All @@ -18,6 +18,11 @@ namespace GFramework.Core.Tests.Rule;
[TestFixture]
public class ContextAwareServiceExtensionsTests
{
private MicrosoftDiContainer _container = null!;
private ArchitectureContext _context = null!;

private TestContextAware _contextAware = null!;

[SetUp]
public void SetUp()
{
Expand All @@ -34,10 +39,6 @@ public void TearDown()
_container.Clear();
}

private TestContextAware _contextAware = null!;
private ArchitectureContext _context = null!;
private MicrosoftDiContainer _container = null!;

[Test]
public void GetService_Should_Return_Registered_Service()
{
Expand All @@ -53,6 +54,18 @@ public void GetService_Should_Return_Registered_Service()
Assert.That(result, Is.SameAs(service));
}

[Test]
public void GetService_Should_Throw_When_Context_Returns_Null_Service()
{
// Arrange
var contextAware = new TestContextAware();
((IContextAware)contextAware).SetContext(new TestArchitectureContextV3());

// Act / Assert
Assert.That(() => contextAware.GetService<TestService>(),
Throws.InvalidOperationException.With.Message.Contains("Service"));
}

[Test]
public void GetSystem_Should_Return_Registered_System()
{
Expand All @@ -68,6 +81,18 @@ public void GetSystem_Should_Return_Registered_System()
Assert.That(result, Is.SameAs(system));
}

[Test]
public void GetSystem_Should_Throw_When_Context_Returns_Null_System()
{
// Arrange
var contextAware = new TestContextAware();
((IContextAware)contextAware).SetContext(new TestArchitectureContextV3());

// Act / Assert
Assert.That(() => contextAware.GetSystem<TestSystem>(),
Throws.InvalidOperationException.With.Message.Contains("System"));
}

[Test]
public void GetModel_Should_Return_Registered_Model()
{
Expand All @@ -83,6 +108,18 @@ public void GetModel_Should_Return_Registered_Model()
Assert.That(result, Is.SameAs(model));
}

[Test]
public void GetModel_Should_Throw_When_Context_Returns_Null_Model()
{
// Arrange
var contextAware = new TestContextAware();
((IContextAware)contextAware).SetContext(new TestArchitectureContextV3());

// Act / Assert
Assert.That(() => contextAware.GetModel<TestModel>(),
Throws.InvalidOperationException.With.Message.Contains("Model"));
}

[Test]
public void GetUtility_Should_Return_Registered_Utility()
{
Expand All @@ -98,6 +135,18 @@ public void GetUtility_Should_Return_Registered_Utility()
Assert.That(result, Is.SameAs(utility));
}

[Test]
public void GetUtility_Should_Throw_When_Context_Returns_Null_Utility()
{
// Arrange
var contextAware = new TestContextAware();
((IContextAware)contextAware).SetContext(new TestArchitectureContextV3());

// Act / Assert
Assert.That(() => contextAware.GetUtility<TestUtility>(),
Throws.InvalidOperationException.With.Message.Contains("Utility"));
}

[Test]
public void GetServices_Should_Return_All_Registered_Services()
{
Expand Down
37 changes: 28 additions & 9 deletions GFramework.Core/Extensions/ContextAwareServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Abstractions.Systems;
Expand All @@ -18,13 +19,15 @@ public static class ContextAwareServiceExtensions
/// </summary>
/// <typeparam name="TService">要获取的服务类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的上下文感知对象</param>
/// <returns>指定类型的服务实例,如果未找到则返回 null</returns>
/// <returns>指定类型的服务实例,如果未找到则抛出异常</returns>
/// <exception cref="ArgumentNullException">当 contextAware 参数为 null 时抛出</exception>
public static TService? GetService<TService>(this IContextAware contextAware) where TService : class
/// <exception cref="InvalidOperationException">当指定服务未注册时抛出</exception>
public static TService GetService<TService>(this IContextAware contextAware) where TService : class
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
{
ArgumentNullException.ThrowIfNull(contextAware);
var context = contextAware.GetContext();
return context.GetService<TService>();
return GetRequiredComponent(context, static architectureContext => architectureContext.GetService<TService>(),
"Service");
}

/// <summary>
Expand All @@ -34,11 +37,13 @@ public static class ContextAwareServiceExtensions
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <returns>指定类型的系统实例</returns>
/// <exception cref="ArgumentNullException">当 contextAware 为 null 时抛出</exception>
public static TSystem? GetSystem<TSystem>(this IContextAware contextAware) where TSystem : class, ISystem
/// <exception cref="InvalidOperationException">当指定系统未注册时抛出</exception>
public static TSystem GetSystem<TSystem>(this IContextAware contextAware) where TSystem : class, ISystem
{
ArgumentNullException.ThrowIfNull(contextAware);
var context = contextAware.GetContext();
return context.GetSystem<TSystem>();
return GetRequiredComponent(context, static architectureContext => architectureContext.GetSystem<TSystem>(),
"System");
}

/// <summary>
Expand All @@ -48,11 +53,13 @@ public static class ContextAwareServiceExtensions
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <returns>指定类型的模型实例</returns>
/// <exception cref="ArgumentNullException">当 contextAware 为 null 时抛出</exception>
public static TModel? GetModel<TModel>(this IContextAware contextAware) where TModel : class, IModel
/// <exception cref="InvalidOperationException">当指定模型未注册时抛出</exception>
public static TModel GetModel<TModel>(this IContextAware contextAware) where TModel : class, IModel
{
ArgumentNullException.ThrowIfNull(contextAware);
var context = contextAware.GetContext();
return context.GetModel<TModel>();
return GetRequiredComponent(context, static architectureContext => architectureContext.GetModel<TModel>(),
"Model");
}

/// <summary>
Expand All @@ -62,11 +69,13 @@ public static class ContextAwareServiceExtensions
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <returns>指定类型的工具实例</returns>
/// <exception cref="ArgumentNullException">当 contextAware 为 null 时抛出</exception>
public static TUtility? GetUtility<TUtility>(this IContextAware contextAware) where TUtility : class, IUtility
/// <exception cref="InvalidOperationException">当指定工具未注册时抛出</exception>
public static TUtility GetUtility<TUtility>(this IContextAware contextAware) where TUtility : class, IUtility
{
ArgumentNullException.ThrowIfNull(contextAware);
var context = contextAware.GetContext();
return context.GetUtility<TUtility>();
return GetRequiredComponent(context, static architectureContext => architectureContext.GetUtility<TUtility>(),
"Utility");
}

#endregion
Expand Down Expand Up @@ -193,5 +202,15 @@ public static IReadOnlyList<TUtility> GetUtilitiesByPriority<TUtility>(this ICon
return context.GetUtilitiesByPriority<TUtility>();
}

private static TComponent GetRequiredComponent<TComponent>(IArchitectureContext context,
Func<IArchitectureContext, TComponent> resolver, string componentKind)
where TComponent : class
{
ArgumentNullException.ThrowIfNull(context);

var component = resolver(context);
return component ?? throw new InvalidOperationException($"{componentKind} {typeof(TComponent)} not registered");
}

#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记类需要自动推断并注入上下文相关字段。
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class GetAllAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记字段需要自动注入单个模型实例。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetModelAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记字段需要自动注入模型集合。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetModelsAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记字段需要自动注入单个服务实例。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetServiceAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记字段需要自动注入服务集合。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetServicesAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记字段需要自动注入单个系统实例。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetSystemAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记字段需要自动注入系统集合。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetSystemsAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记字段需要自动注入工具集合。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetUtilitiesAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;

/// <summary>
/// 标记字段需要自动注入单个工具实例。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetUtilityAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using GFramework.SourceGenerators.Common.Info;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace GFramework.SourceGenerators.Common.Extensions;

Expand Down Expand Up @@ -69,38 +71,50 @@ public static GenericInfo ResolveGenerics(this INamedTypeSymbol symbol)
: $"where {tp.Name} : {string.Join(", ", parts)}";
}

/// <summary>
/// 判断类型的所有声明是否均带有 partial 关键字。
/// </summary>
/// <param name="symbol">要获取完整类名的命名类型符号</param>
extension(INamedTypeSymbol symbol)
/// <returns>如果所有声明均为 partial,则返回 <c>true</c>。</returns>
public static bool AreAllDeclarationsPartial(this INamedTypeSymbol symbol)
{
/// <summary>
/// 获取命名类型符号的完整类名(包括嵌套类型名称)
/// </summary>
/// <returns>完整的类名,格式为"外层类名.内层类名.当前类名"</returns>
public string GetFullClassName()
{
var names = new Stack<string>();
var current = symbol;

// 遍历包含类型链,将所有类型名称压入栈中
while (current != null)
{
names.Push(current.Name);
current = current.ContainingType;
}
return symbol.DeclaringSyntaxReferences
.Select(static reference => reference.GetSyntax())
.OfType<ClassDeclarationSyntax>()
.All(static declaration =>
declaration.Modifiers.Any(static modifier => modifier.IsKind(SyntaxKind.PartialKeyword)));
}

// 将栈中的名称用点号连接,形成完整的类名
return string.Join(".", names);
}
/// <summary>
/// 获取命名类型符号的完整类名(包括嵌套类型名称)
/// </summary>
/// <param name="symbol">要获取完整类名的命名类型符号</param>
/// <returns>完整的类名,格式为"外层类名.内层类名.当前类名"</returns>
public static string GetFullClassName(this INamedTypeSymbol symbol)
{
var names = new Stack<string>();
var current = symbol;

/// <summary>
/// 获取命名类型符号的命名空间名称
/// </summary>
/// <returns>命名空间名称,如果是全局命名空间则返回null</returns>
public string? GetNamespace()
// 遍历包含类型链,将所有类型名称压入栈中
while (current != null)
{
return symbol.ContainingNamespace.IsGlobalNamespace
? null
: symbol.ContainingNamespace.ToDisplayString();
names.Push(current.Name);
current = current.ContainingType;
}

// 将栈中的名称用点号连接,形成完整的类名
return string.Join(".", names);
}

/// <summary>
/// 获取命名类型符号的命名空间名称
/// </summary>
/// <param name="symbol">要获取完整类名的命名类型符号</param>
/// <returns>命名空间名称,如果是全局命名空间则返回null</returns>
public static string? GetNamespace(this INamedTypeSymbol symbol)
{
return symbol.ContainingNamespace.IsGlobalNamespace
? null
: symbol.ContainingNamespace.ToDisplayString();
}
}
Loading
Loading