first version

This commit is contained in:
2026-08-16 12:46:15 +08:00
commit 932fce1b9e
40 changed files with 4005 additions and 0 deletions
@@ -0,0 +1,145 @@
/*
* Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。
* 自定义 IRequestDelegateFactory:在结果派发时自动封装响应,
* 对齐 C# SimApiResponseFilterIResultFilter)的行为:
* - 返回 SimApiBaseResponse 或其子类 → 原样输出
* - 返回 null/voidUnit)→ SimApiBaseResponse(){code:200, message:成功}
* - 返回 String → SimApiResponse<String>data 为字符串)
* - 返回其他对象 → SimApiResponse<Any>data 为对象)
*
* 注册方式:必须在 soulsoft addControllers() 之前注册(tryAddSingleton 先到先得)。
*/
package simapi.extensions
import std.collection.*
import std.reflect.*
import soulsoft_web_http.*
import soulsoft_web_mvc.core.*
import soulsoft_web_mvc.routing.*
import soulsoft_web_mvc.controllers.*
import soulsoft_web_mvc.modelBindings.*
import soulsoft_web_mvc.abstractions.*
import soulsoft_extensions_options.*
import soulsoft_extensions_injection.*
import simapi.communications.*
import simapi.controllers.*
/**
* 自定义请求委托工厂:接管 soulsoft 的 ControllerRequestDelegateFactory
* 在结果派发时自动封装响应(对齐 C# SimApiResponseFilter)。
*/
public class SimApiRequestDelegateFactory <: IRequestDelegateFactory {
private let _mvcOptions: MvcOptions
private let _modelBinder: IActionModelBinder
public init(mvcOptions: IOptions<MvcOptions>, modelBinder: IActionModelBinder, services: IServiceProvider) {
_mvcOptions = mvcOptions.value
_modelBinder = modelBinder
}
public func createRequestDelegate(actionDescriptor: ControllerActionDescriptor): RequestDelegate {
return {
context => SimApiActionInvoker(context, _modelBinder, actionDescriptor, _mvcOptions).apply()
}
}
}
/**
* 单次请求的动作执行器(复制 soulsoft ControllerActionInvoker
* dispatchResult 改为自动封装响应)。
*/
struct SimApiActionInvoker {
SimApiActionInvoker(let context: HttpContext, let modelBinder: IActionModelBinder,
let actionDescriptor: ControllerActionDescriptor, let mvcOptions: MvcOptions) {
}
public func apply(): Unit {
let controller = createControllerInstance()
let modelBindingContext = ActionBindingContext(context, actionDescriptor.actionFunction.parameters)
let boundParameters = modelBinder.bind(modelBindingContext)
if (!modelBindingContext.modelState.isValid) {
handleInvalidModelState(modelBindingContext)
} else {
let actionResult = actionDescriptor.actionFunction.apply(controller, boundParameters)
dispatchResult(actionResult)
}
}
/// 模型绑定失败时写入 ProblemDetails 响应
private func handleInvalidModelState(modelBindingContext: ActionBindingContext) {
let options = context.services.getOrThrow<IOptions<ApiBehaviorOptions>>()
if (let Some(factory) <- options.value.invalidModelStateResponseFactory) {
let actionContext = ActionContext(context, modelBindingContext.modelState)
factory(actionContext).invoke(context)
} else {
let details = createValidationProblemDetails(modelBindingContext)
if (let Some(status) <- details.status) {
context.response.statusCode = UInt16(status)
}
context.response.writeAsJson(details)
}
}
/// 结果派发 + 自动封装(对齐 C# SimApiResponseFilter
private func dispatchResult(actionResult: Any) {
if (let result: IActionResult <- actionResult) {
// 显式返回 IActionResult(如 ContentResult)→ 原样
result.invoke(context)
} else if (let result: SimApiBaseResponse <- actionResult) {
// 已是 SimApiBaseResponse(含子类)→ 原样输出
ObjectResult<Any>(result).invoke(context)
} else if (let result: String <- actionResult) {
// String → SimApiResponse<String>data 为字符串)
ObjectResult<Any>(SimApiResponse<String>(result)).invoke(context)
} else if (let result: Unit <- actionResult) {
// void/无返回 → SimApiBaseResponse(){code:200, message:成功}
context.response.writeAsJson(SimApiBaseResponse())
} else {
// 其他对象(DTO/数组/动态结构)→ SimApiDataResponse
// data 由 SimApiDataResponse.serializeObject 内嵌为对象(ISerializable → 对象;
// HashMap<String,Any> 等动态结构 → SimApiJson 序列化后解析内嵌),不会变成 JSON 字符串
ObjectResult<Any>(SimApiDataResponse(actionResult)).invoke(context)
}
}
/// 根据 ModelState 错误构建 ValidationProblemDetails
private func createValidationProblemDetails(modelBindingContext: ActionBindingContext) {
let details = ValidationProblemDetails()
if (hasUnsupportedContentTypeError(modelBindingContext.modelState)) {
details.`type` = "https://tools.ietf.org/html/rfc9110#section-15.5.16"
details.title = "Unsupported Media Type"
details.status = 415
} else {
details.`type` = "https://tools.ietf.org/html/rfc9110#section-15.5.1"
details.title = "One or more validation errors occurred."
details.status = 400
for ((name, entry) in modelBindingContext.modelState) {
details.errors.add(name, entry.errors |> map {f => f.description} |> collectArray)
}
}
return details
}
/// 检查 ModelState 中是否含有 UnsupportedContentTypeException 错误
private func hasUnsupportedContentTypeError(modelState: ModelStateDictionary) {
for ((_, entry) in modelState) {
for (error in entry.errors) {
if (error.exception.flatMap {f => f as UnsupportedContentTypeException}.isSome()) {
return true
}
}
}
return false
}
/// 通过 DI 容器实例化控制器,并注入当前 HttpContext
private func createControllerInstance(): Object {
let instance = ActivatorUtilities.createInstance(context.services, actionDescriptor.controllerType)
if (let controller: SimApiBaseController <- instance) {
controller.bindRequestContext(context)
}
return instance
}
}