增加了openapi
This commit is contained in:
@@ -1,20 +1,20 @@
|
|||||||
version = 0
|
version = 0
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
soulsoft_extensions_hosting = {version = "1.0.20260528"}
|
|
||||||
soulsoft_web_http = {version = "1.0.20260528"}
|
soulsoft_web_http = {version = "1.0.20260528"}
|
||||||
soulsoft_web_hosting = {version = "1.0.20260528"}
|
soulsoft_extensions_hosting = {version = "1.0.20260528"}
|
||||||
|
soulsoft_extensions_logging_configuration = {version = "1.0.20260528"}
|
||||||
soulsoft_extensions_options_configuration = {version = "1.0.20260528"}
|
soulsoft_extensions_options_configuration = {version = "1.0.20260528"}
|
||||||
|
soulsoft_web_hosting = {version = "1.0.20260528"}
|
||||||
|
soulsoft_extensions_configuration = {version = "1.0.20260528"}
|
||||||
soulsoft_web_routing = {version = "1.0.20260528"}
|
soulsoft_web_routing = {version = "1.0.20260528"}
|
||||||
soulsoft_web_mvc = {version = "1.0.20260528"}
|
soulsoft_web_mvc = {version = "1.0.20260528"}
|
||||||
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
|
soulsoft_extensions_injection = {version = "1.0.20260528"}
|
||||||
soulsoft_identity_claims = {version = "1.0.20260528"}
|
|
||||||
soulsoft_extensions_options = {version = "1.0.20260528"}
|
soulsoft_extensions_options = {version = "1.0.20260528"}
|
||||||
soulsoft_web_cors = {version = "1.0.20260528"}
|
soulsoft_web_cors = {version = "1.0.20260528"}
|
||||||
soulsoft_extensions_logging = {version = "1.0.20260528"}
|
|
||||||
soulsoft_extensions_logging_configuration = {version = "1.0.20260528"}
|
|
||||||
soulsoft_serialization = {version = "1.0.20260528"}
|
soulsoft_serialization = {version = "1.0.20260528"}
|
||||||
soulsoft_extensions_injection = {version = "1.0.20260528"}
|
|
||||||
redis = {version = "1.0.20260627"}
|
redis = {version = "1.0.20260627"}
|
||||||
soulsoft_extensions_configuration = {version = "1.0.20260528"}
|
soulsoft_identity_claims = {version = "1.0.20260528"}
|
||||||
|
soulsoft_extensions_logging = {version = "1.0.20260528"}
|
||||||
"simcu::serialization" = {version = "1.2.1"}
|
"simcu::serialization" = {version = "1.2.1"}
|
||||||
|
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
package simcu::simapi.communications
|
package simcu::simapi.communications
|
||||||
|
|
||||||
import std.collection.*
|
import std.collection.*
|
||||||
|
import simcu::serialization.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录信息项:Token 认证通过后注入请求上下文。
|
* 登录信息项:Token 认证通过后注入请求上下文。
|
||||||
@@ -16,8 +17,11 @@ import std.collection.*
|
|||||||
*/
|
*/
|
||||||
public class SimApiLoginItem {
|
public class SimApiLoginItem {
|
||||||
public var id: String = ""
|
public var id: String = ""
|
||||||
|
@NotRequired
|
||||||
public var types: Array<String> = ["user"]
|
public var types: Array<String> = ["user"]
|
||||||
|
@NotRequired
|
||||||
public var meta: HashMap<String, String> = HashMap<String, String>()
|
public var meta: HashMap<String, String> = HashMap<String, String>()
|
||||||
|
@NotRequired
|
||||||
public var extra: HashMap<String, Any> = HashMap<String, Any>()
|
public var extra: HashMap<String, Any> = HashMap<String, Any>()
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package simcu::simapi.controllers
|
package simcu::simapi.controllers
|
||||||
|
|
||||||
import soulsoft_web_mvc.annotations.*
|
|
||||||
import simcu::simapi.communications.*
|
import simcu::simapi.communications.*
|
||||||
import simcu::simapi.helpers.*
|
import simcu::simapi.helpers.*
|
||||||
|
|
||||||
@@ -22,10 +21,8 @@ public class SimApiAuthController <: SimApiBaseController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /auth/logout:退出登录(void 自动封装为 SimApiBaseResponse())。
|
* POST /auth/logout:退出登录(void 自动封装为 SimApiBaseResponse())。
|
||||||
* 对应路由 [HttpPost],
|
* 动态注册:路由路径由 SimApiRouteOptions.logoutRoute 决定(见 simapi_extensions.cj)。
|
||||||
* 由 MapControllerRoute(pattern=LogoutRoute) 注册。
|
|
||||||
*/
|
*/
|
||||||
@HttpPost["/auth/logout"]
|
|
||||||
public func logout(): Unit {
|
public func logout(): Unit {
|
||||||
if (let Some(token) <- request.headers.get("Token")) {
|
if (let Some(token) <- request.headers.get("Token")) {
|
||||||
_auth.logout(token)
|
_auth.logout(token)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import simcu::simapi.communications.*
|
|||||||
import simcu::simapi.helpers.*
|
import simcu::simapi.helpers.*
|
||||||
import simcu::simapi.interfaces.*
|
import simcu::simapi.interfaces.*
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础控制器:所有控制器均继承本控制器。
|
* 基础控制器:所有控制器均继承本控制器。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import soulsoft_web_mvc.annotations.*
|
|||||||
import simcu::simapi.communications.*
|
import simcu::simapi.communications.*
|
||||||
import simcu::simapi.configurations.*
|
import simcu::simapi.configurations.*
|
||||||
import simcu::simapi.helpers.*
|
import simcu::simapi.helpers.*
|
||||||
|
import simcu::simapi.openapi.annotations.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用控制器:错误反馈、WebConfig、用户信息。
|
* 通用控制器:错误反馈、WebConfig、用户信息。
|
||||||
@@ -28,30 +29,24 @@ public class SimApiCommonController <: SimApiBaseController {
|
|||||||
* 抛 SimApiException,由异常中间件统一输出。
|
* 抛 SimApiException,由异常中间件统一输出。
|
||||||
*/
|
*/
|
||||||
@HttpGet["exception/{code}"]
|
@HttpGet["exception/{code}"]
|
||||||
|
@SimApiDoc[tags:"公共",summary:"异常报错"]
|
||||||
public func exceptionHandler(@FromRoute[] code: Int64): Unit { // cjlint-ignore !G.FUN.02 注解绑定参数误报
|
public func exceptionHandler(@FromRoute[] code: Int64): Unit { // cjlint-ignore !G.FUN.02 注解绑定参数误报
|
||||||
SimApiError.error(code: code)
|
SimApiError.error(code: code)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST/GET /config:给前端的自定义信息(含版本)。
|
* POST/GET /config:给前端的自定义信息(含版本)。
|
||||||
* 对应路由 [HttpPost, HttpGet] 无路径,
|
* 动态注册:路由路径由 SimApiRouteOptions.webConfigRoute 决定(见 simapi_extensions.cj)。
|
||||||
* 由 MapControllerRoute(pattern=WebConfigRoute) 注册;soulsoft 约定路由不支持 defaults,故用特性路由直接对齐路径。
|
|
||||||
*/
|
*/
|
||||||
@HttpGet["/config"]
|
|
||||||
public func webConfig(): HashMap<String, Any> {
|
public func webConfig(): HashMap<String, Any> {
|
||||||
webConfigMap()
|
webConfigMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@HttpPost["/config"]
|
|
||||||
public func webConfigPost(): HashMap<String, Any> {
|
|
||||||
webConfigMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /user/info:获取已登录用户信息(需登录)。
|
* POST /user/info:获取已登录用户信息(需登录)。
|
||||||
* 返回 SimApiLoginItem 由 SimApiResponseFilter 自动封装(data 为登录信息对象)。
|
* 动态注册:路由路径由 SimApiRouteOptions.userInfoRoute 决定(见 simapi_extensions.cj)。
|
||||||
*/
|
*/
|
||||||
@HttpPost["/user/info"]
|
|
||||||
public func userInfo(): SimApiLoginItem {
|
public func userInfo(): SimApiLoginItem {
|
||||||
requireLogin()
|
requireLogin()
|
||||||
loginInfo
|
loginInfo
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
|
public import simcu::simapi.openapi.annotations.*
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
|
import soulsoft_web_http.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI 端点约定扩展。
|
||||||
|
*/
|
||||||
|
public interface OpenApiEndpointConventionBuilderExtensions {
|
||||||
|
/**
|
||||||
|
* @brief 为端点附加 OpenAPI 元数据。
|
||||||
|
* @param metadata 要附加的 OpenAPI 元数据。
|
||||||
|
* @return 当前端点约定构建器实例。
|
||||||
|
*/
|
||||||
|
func withOpenApi(metadata: SimApiDoc): EndpointConventionBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
extend EndpointConventionBuilder <: OpenApiEndpointConventionBuilderExtensions {
|
||||||
|
/**
|
||||||
|
* @brief 为端点附加 API 文档元数据。
|
||||||
|
* @param metadata 要附加的 API 文档元数据。
|
||||||
|
* @return 当前端点约定构建器实例。
|
||||||
|
*/
|
||||||
|
public func withOpenApi(metadata: SimApiDoc): EndpointConventionBuilder {
|
||||||
|
this.add {
|
||||||
|
builder => builder.metadata.add(metadata)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
|
import soulsoft_web_http.*
|
||||||
|
import soulsoft_web_routing.*
|
||||||
|
import simcu::simapi.openapi.services.*
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
import simcu::simapi.openapi.infrastructure.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI 文档路由映射扩展。
|
||||||
|
*/
|
||||||
|
public interface OpenApiEndpointRouteBuilderExtension {
|
||||||
|
/**
|
||||||
|
* @brief 映射默认 OpenAPI 文档路由。
|
||||||
|
* @return 当前端点路由构建器实例。
|
||||||
|
*/
|
||||||
|
func mapOpenApi(): EndpointRouteBuilder {
|
||||||
|
mapOpenApi(OpenApiConstants.DefaultOpenApiRoute)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief 映射指定模式的 OpenAPI 文档路由。
|
||||||
|
* @param pattern OpenAPI 文档路由模式。
|
||||||
|
* @return 当前端点路由构建器实例。
|
||||||
|
*/
|
||||||
|
func mapOpenApi(pattern: String): EndpointRouteBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
extend EndpointRouteBuilder <: OpenApiEndpointRouteBuilderExtension {
|
||||||
|
/**
|
||||||
|
* @brief 映射指定模式的 OpenAPI 文档路由。
|
||||||
|
* @param pattern OpenAPI 文档路由模式。
|
||||||
|
* @return 当前端点路由构建器实例。
|
||||||
|
*/
|
||||||
|
public func mapOpenApi(pattern: String): EndpointRouteBuilder {
|
||||||
|
verifyOpenApiServicesAreRegistered()
|
||||||
|
this.mapGet(pattern) {
|
||||||
|
context =>
|
||||||
|
let documentName = context.request.routeValues.get("documentName").flatMap {f => f} ?? "v1"
|
||||||
|
let documentFactory = context.services.getOrThrow<IDocumentProvider>()
|
||||||
|
let document = documentFactory.create(documentName, context.services)
|
||||||
|
context.response.contentType = "application/json; charset=utf-8"
|
||||||
|
let writer = JsonOpenApiWriter(context.response.body)
|
||||||
|
document.serializeAsV3(writer)
|
||||||
|
writer.flush()
|
||||||
|
}.withOpenApi(SimApiDoc(ignore: true))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private func verifyOpenApiServicesAreRegistered() {
|
||||||
|
let callSiteFactory = this.services.getOrThrow<IServiceProviderIsService>()
|
||||||
|
if (!callSiteFactory.isService<IDocumentProvider>()) {
|
||||||
|
throw Exception(
|
||||||
|
"Unable to find the required services. Please add all the required services by calling 'ServiceCollection.addOpenApi' inside the call to 'configureServices(...)' in the application startup code.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
|
import soulsoft_web_routing.*
|
||||||
|
import soulsoft_extensions_options.*
|
||||||
|
import simcu::simapi.openapi.services.*
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
import simcu::simapi.openapi.infrastructure.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI 服务注册扩展。
|
||||||
|
*/
|
||||||
|
public interface OpenApiServiceCollectionExtensions {
|
||||||
|
/**
|
||||||
|
* @brief 注册默认名称的 OpenAPI 服务。
|
||||||
|
* @return 当前服务集合实例。
|
||||||
|
*/
|
||||||
|
func addOpenApi(): ServiceCollection {
|
||||||
|
addOpenApi(OpenApiConstants.DefaultDocumentName, {_ =>})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册指定文档名称的 OpenAPI 服务。
|
||||||
|
* @param documentName OpenAPI 文档名称。
|
||||||
|
* @return 当前服务集合实例。
|
||||||
|
*/
|
||||||
|
func addOpenApi(documentName: String): ServiceCollection {
|
||||||
|
addOpenApi(documentName, {_ =>})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册默认文档名称的 OpenAPI 服务并配置选项。
|
||||||
|
* @param configureOptions 用于配置 OpenAPI 选项的回调。
|
||||||
|
* @return 当前服务集合实例。
|
||||||
|
*/
|
||||||
|
func addOpenApi(configureOptions: (OpenApiOptions) -> Unit): ServiceCollection {
|
||||||
|
addOpenApi(OpenApiConstants.DefaultDocumentName, configureOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册指定文档名称的 OpenAPI 服务并配置选项。
|
||||||
|
* @param documentName OpenAPI 文档名称。
|
||||||
|
* @param configureOptions 用于配置 OpenAPI 选项的回调。
|
||||||
|
* @return 当前服务集合实例。
|
||||||
|
*/
|
||||||
|
func addOpenApi(documentName: String, configureOptions: (OpenApiOptions) -> Unit): ServiceCollection
|
||||||
|
}
|
||||||
|
|
||||||
|
extend ServiceCollection <: OpenApiServiceCollectionExtensions {
|
||||||
|
/**
|
||||||
|
* @brief 注册指定文档名称的 OpenAPI 服务并配置选项。
|
||||||
|
* @param documentName OpenAPI 文档名称。
|
||||||
|
* @param configureOptions 用于配置 OpenAPI 选项的回调。
|
||||||
|
* @return 当前服务集合实例。
|
||||||
|
*/
|
||||||
|
public func addOpenApi(documentName: String, configureOptions: (OpenApiOptions) -> Unit): ServiceCollection {
|
||||||
|
this.addRouting()
|
||||||
|
this.configure<OpenApiOptions>(documentName, configureOptions)
|
||||||
|
this.addSingleton(NamedService(documentName))
|
||||||
|
this.addSingleton<IDocumentProvider, OpenApiDocumentProvider>()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
|
import std.io.*
|
||||||
|
import std.fs.*
|
||||||
|
import soulsoft_web_http.*
|
||||||
|
import soulsoft_web_hosting.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI UI 静态资源中间件。
|
||||||
|
*/
|
||||||
|
public class OpenApiUIMiddleware <: IMiddleware {
|
||||||
|
private static let _openApiRootPath = PathString("/openapi")
|
||||||
|
private let _env: IWebHostEnvironment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI UI 中间件实例。
|
||||||
|
* @param evn 当前 Web 主机环境。
|
||||||
|
*/
|
||||||
|
public init(evn: IWebHostEnvironment) {
|
||||||
|
_env = evn
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理 OpenAPI UI 相关请求。
|
||||||
|
* @param context 当前 HTTP 上下文。
|
||||||
|
* @param next 下一个请求委托。
|
||||||
|
*/
|
||||||
|
public func invoke(context: HttpContext, next: RequestDelegate): Unit {
|
||||||
|
let remainingPath = if (let Some(path) <- context.request.path.startsWithSegments(
|
||||||
|
OpenApiUIMiddleware._openApiRootPath
|
||||||
|
)) {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
next(context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.request.path == "/openapi") {
|
||||||
|
context.response.redirect("/openapi/index.html")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = if (let Some(path) <- resolveAssetPath(remainingPath)) {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
next(context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists(path) && isPathWithinRoot(path)) {
|
||||||
|
try (fs = File(path, OpenMode.Read)) {
|
||||||
|
let data = readToEnd(fs)
|
||||||
|
context.response.write(data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resolveAssetPath(remainingPath: PathString): ?Path {
|
||||||
|
let relativePath = if (remainingPath == "/" || !remainingPath.hasValue) {
|
||||||
|
"index.html"
|
||||||
|
} else {
|
||||||
|
let candidate = remainingPath.value.trimStart('/')
|
||||||
|
if (!isSafeRelativePath(candidate)) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
let openApiRoot = Path(_env.webRootPath).join("openapi")
|
||||||
|
return openApiRoot.join(relativePath).normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isSafeRelativePath(relativePath: String): Bool {
|
||||||
|
if (relativePath.isEmpty() || relativePath.startsWith('/') || relativePath.endsWith('/')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (segment in relativePath.split('/')) {
|
||||||
|
if (segment.isEmpty() || segment == "." || segment == "..") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isPathWithinRoot(path: Path): Bool {
|
||||||
|
try {
|
||||||
|
let rootPath = canonicalize(Path(_env.webRootPath).join("openapi"))
|
||||||
|
let targetPath = canonicalize(path)
|
||||||
|
let rootPathString = rootPath.toString()
|
||||||
|
let targetPathString = targetPath.toString()
|
||||||
|
if (targetPathString == rootPathString) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootPathString == Path.Separator) {
|
||||||
|
return targetPathString.startsWith(rootPathString)
|
||||||
|
}
|
||||||
|
|
||||||
|
let rootPathPrefix = if (rootPathString.endsWith(Path.Separator)) {
|
||||||
|
rootPathString
|
||||||
|
} else {
|
||||||
|
"${rootPathString}${Path.Separator}"
|
||||||
|
}
|
||||||
|
return targetPathString.startsWith(rootPathPrefix)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
|
import soulsoft_web_http.*
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
import simcu::simapi.openapi.services.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI UI 中间件扩展。
|
||||||
|
*/
|
||||||
|
public interface OpenApiUIMiddlewareExtensions {
|
||||||
|
/**
|
||||||
|
* @brief 启用 OpenAPI UI 中间件。
|
||||||
|
*/
|
||||||
|
func useOpenApiUI(): Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
extend ApplicationBuilder <: OpenApiUIMiddlewareExtensions{
|
||||||
|
/**
|
||||||
|
* @brief 启用 OpenAPI UI 中间件。
|
||||||
|
*/
|
||||||
|
public func useOpenApiUI(): Unit {
|
||||||
|
verifyOpenApiServicesAreRegistered()
|
||||||
|
use<OpenApiUIMiddleware>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func verifyOpenApiServicesAreRegistered() {
|
||||||
|
let callSiteFactory = this.services.getOrThrow<IServiceProviderIsService>()
|
||||||
|
if (!callSiteFactory.isService<IDocumentProvider>()) {
|
||||||
|
throw Exception("Unable to find the required services. Please add all the required services by calling 'ServiceCollection.addOpenApi' inside the call to 'configureServices(...)' in the application startup code.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.annotations
|
||||||
|
|
||||||
|
import simcu::simapi.openapi.metadata.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 为控制器或动作提供 API 文档元数据(对齐 C# [SimApiDoc] 特性)。
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* @SimApiDoc[tags: "登录", summary: "用户登录相关接口"]
|
||||||
|
*/
|
||||||
|
@Annotation[target: [MemberFunction, Type, MemberProperty, MemberVariable, Parameter]]
|
||||||
|
public class SimApiDoc <: IApiTagsMetadata & IApiNameMetadata & IApiGroupNameProvider & IApiDescriptionMetadata & IApiSummaryMetadata & IApiVisibilityProvider {
|
||||||
|
private let _ignore: Bool
|
||||||
|
private let _name: ?String
|
||||||
|
private let _tags: ?String
|
||||||
|
private let _summary: ?String
|
||||||
|
private let _groupName: ?String
|
||||||
|
private let _description: ?String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 SimApiDoc 注解实例。
|
||||||
|
* @param name API 名称。
|
||||||
|
* @param ignore 是否忽略当前 API。
|
||||||
|
* @param summary API 摘要。
|
||||||
|
* @param groupName API 分组名称。
|
||||||
|
* @param description API 描述。
|
||||||
|
* @param tags API 标签字符串。
|
||||||
|
*/
|
||||||
|
public const init(name!: ?String = None, ignore!: Bool = false, summary!: ?String = None,
|
||||||
|
groupName!: ?String = None, description!: ?String = None, tags!: ?String = None) {
|
||||||
|
_tags = tags
|
||||||
|
_name = name
|
||||||
|
_ignore = ignore
|
||||||
|
_summary = summary
|
||||||
|
_groupName = groupName
|
||||||
|
_description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 标签字符串。
|
||||||
|
* @return 当前 API 的标签字符串。
|
||||||
|
*/
|
||||||
|
public prop tags: ?String {
|
||||||
|
get() {
|
||||||
|
_tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 名称。
|
||||||
|
* @return 当前 API 的名称。
|
||||||
|
*/
|
||||||
|
public prop name: ?String {
|
||||||
|
get() {
|
||||||
|
_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回当前 API 是否应被忽略。
|
||||||
|
* @return 如果当前 API 需要从文档中忽略则返回 `true`。
|
||||||
|
*/
|
||||||
|
public prop ignore: Bool {
|
||||||
|
get() {
|
||||||
|
_ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 摘要信息。
|
||||||
|
* @return 当前 API 的摘要信息。
|
||||||
|
*/
|
||||||
|
public prop summary: ?String {
|
||||||
|
get() {
|
||||||
|
_summary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 分组名称。
|
||||||
|
* @return 当前 API 的分组名称。
|
||||||
|
*/
|
||||||
|
public prop groupName: ?String {
|
||||||
|
get() {
|
||||||
|
_groupName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 描述信息。
|
||||||
|
* @return 当前 API 的描述信息。
|
||||||
|
*/
|
||||||
|
public prop description: ?String {
|
||||||
|
get() {
|
||||||
|
_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.infrastructure
|
||||||
|
|
||||||
|
import std.reflect.*
|
||||||
|
|
||||||
|
/*
|
||||||
|
Option类型操作
|
||||||
|
*/
|
||||||
|
protected class Nullable {
|
||||||
|
/**
|
||||||
|
* @brief 返回 Option 类型的底层类型。
|
||||||
|
* @param typeInfo 要解析的类型信息。
|
||||||
|
* @return 如果是 Option 类型则返回其底层类型,否则返回 `None`。
|
||||||
|
*/
|
||||||
|
public static func getUnderlyingType(typeInfo: TypeInfo): ?TypeInfo {
|
||||||
|
let qualifiedName = typeInfo.qualifiedName
|
||||||
|
if (!(qualifiedName.startsWith("Option<") && qualifiedName.endsWith('>'))) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
let underlyingTypeName = qualifiedName[7..qualifiedName.size - 1]
|
||||||
|
return TypeInfo.get(underlyingTypeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.infrastructure
|
||||||
|
|
||||||
|
protected class OpenApiConstants {
|
||||||
|
public static let DefaultOpenApiName = "default"
|
||||||
|
public static let DefaultDocumentName = "v1"
|
||||||
|
public static let DefaultOpenApiRoute = "/openapi/{documentName}.json"
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.infrastructure
|
||||||
|
|
||||||
|
import std.reflect.*
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
protected class ReflectUtilities {
|
||||||
|
/**
|
||||||
|
* 收集给定类型在最终可见视角下的 public 实例属性。
|
||||||
|
*
|
||||||
|
* 对 class 类型会沿继承链向上收集,并在子类 override 父类同名属性时,
|
||||||
|
* 仅保留子类的最终实现;非 class 类型返回空数组。
|
||||||
|
*/
|
||||||
|
public static func collectInstanceProperties(typeInfo: TypeInfo): Array<InstancePropertyInfo> {
|
||||||
|
if (let classTypeInfo: ClassTypeInfo <- typeInfo) {
|
||||||
|
let result = ArrayList<InstancePropertyInfo>(classTypeInfo.instanceProperties.size)
|
||||||
|
collectInstanceProperties(classTypeInfo, result)
|
||||||
|
return result.toArray()
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归收集继承链上的 public 实例属性。
|
||||||
|
*
|
||||||
|
* 先处理父类,再处理当前类;如果当前类 override 了父类同名属性,
|
||||||
|
* 则用当前类属性覆盖父类项,只保留最终可见的那一个。
|
||||||
|
*/
|
||||||
|
private static func collectInstanceProperties(classTypeInfo: ClassTypeInfo, result: ArrayList<InstancePropertyInfo>): Unit {
|
||||||
|
if (let Some(superClass) <- classTypeInfo.superClass && superClass != TypeInfo.of<Object>()) {
|
||||||
|
if (let superClassTypeInfo: ClassTypeInfo <- superClass) {
|
||||||
|
collectInstanceProperties(superClassTypeInfo, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (pattern in classTypeInfo.instanceProperties) {
|
||||||
|
if (pattern.isAbstract()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addOrReplaceProperty(result, pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子类同名属性覆盖父类属性,避免 open/override 场景下重复收集。
|
||||||
|
*/
|
||||||
|
private static func addOrReplaceProperty(result: ArrayList<InstancePropertyInfo>, propertyInfo: InstancePropertyInfo): Unit {
|
||||||
|
for ((index, existingProperty) in result |> enumerate) {
|
||||||
|
if (existingProperty.name == propertyInfo.name) {
|
||||||
|
result[index] = propertyInfo
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(propertyInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集给定类型在最终可见视角下的 public 实例变量(var 字段)。
|
||||||
|
*
|
||||||
|
* std.reflect 的 instanceProperties 仅收录 prop 属性,而 DTO 常以 public var 字段定义,
|
||||||
|
* 需通过 instanceVariables 补充收集。对 class 类型会沿继承链向上收集,
|
||||||
|
* 子类覆盖父类同名变量时仅保留子类实现;非 class 类型返回空数组。
|
||||||
|
*/
|
||||||
|
public static func collectInstanceVariables(typeInfo: TypeInfo): Array<InstanceVariableInfo> {
|
||||||
|
if (let classTypeInfo: ClassTypeInfo <- typeInfo) {
|
||||||
|
let result = ArrayList<InstanceVariableInfo>(classTypeInfo.instanceVariables.size)
|
||||||
|
collectInstanceVariables(classTypeInfo, result)
|
||||||
|
return result.toArray()
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归收集继承链上的 public 实例变量(var 字段)。
|
||||||
|
*
|
||||||
|
* 先处理父类,再处理当前类;如果当前类覆盖了父类同名变量,
|
||||||
|
* 则用当前类变量覆盖父类项,只保留最终可见的那一个。
|
||||||
|
*/
|
||||||
|
private static func collectInstanceVariables(classTypeInfo: ClassTypeInfo, result: ArrayList<InstanceVariableInfo>): Unit {
|
||||||
|
if (let Some(superClass) <- classTypeInfo.superClass && superClass != TypeInfo.of<Object>()) {
|
||||||
|
if (let superClassTypeInfo: ClassTypeInfo <- superClass) {
|
||||||
|
collectInstanceVariables(superClassTypeInfo, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (pattern in classTypeInfo.instanceVariables) {
|
||||||
|
addOrReplaceVariable(result, pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子类同名变量覆盖父类变量,避免 open/override 场景下重复收集。
|
||||||
|
*/
|
||||||
|
private static func addOrReplaceVariable(result: ArrayList<InstanceVariableInfo>, variableInfo: InstanceVariableInfo): Unit {
|
||||||
|
for ((index, existingVariable) in result |> enumerate) {
|
||||||
|
if (existingVariable.name == variableInfo.name) {
|
||||||
|
result[index] = variableInfo
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(variableInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.infrastructure
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
private let Space = UInt8(32)
|
||||||
|
private let Tab = UInt8(9)
|
||||||
|
private let LineFeed = UInt8(10)
|
||||||
|
private let CarriageReturn = UInt8(13)
|
||||||
|
private let LessThan = UInt8(60)
|
||||||
|
private let GreaterThan = UInt8(62)
|
||||||
|
private let Comma = UInt8(44)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供基于类型限定名的轻量泛型解析能力。
|
||||||
|
*/
|
||||||
|
protected class TypeNameParser {
|
||||||
|
/**
|
||||||
|
* @brief 返回泛型类型的原始类型名;非泛型时返回自身。
|
||||||
|
* @param qualifiedName 类型限定名。
|
||||||
|
* @return 去掉泛型参数后的类型名。
|
||||||
|
*/
|
||||||
|
public static func getGenericBaseName(qualifiedName: String): String {
|
||||||
|
if (let Some(index) <- qualifiedName.indexOf('<')) {
|
||||||
|
return trimAsciiWhitespace(qualifiedName[0..index])
|
||||||
|
}
|
||||||
|
return trimAsciiWhitespace(qualifiedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解析限定名中的顶层泛型参数列表。
|
||||||
|
* @param qualifiedName 类型限定名,例如 `Map<String, Int64>`。
|
||||||
|
* @return 顶层泛型参数;若不是合法泛型限定名则返回 `None`。
|
||||||
|
*/
|
||||||
|
public static func getGenericArguments(qualifiedName: String): ?Array<String> {
|
||||||
|
if (!qualifiedName.endsWith('>')) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
let start = qualifiedName.indexOf('<') ?? return None
|
||||||
|
let payload = qualifiedName[start + 1..qualifiedName.size - 1]
|
||||||
|
let result = ArrayList<String>()
|
||||||
|
var depth = 0
|
||||||
|
var segmentStart = 0
|
||||||
|
var index = 0
|
||||||
|
while (index < payload.size) {
|
||||||
|
let ch = payload[index]
|
||||||
|
if (ch == LessThan) {
|
||||||
|
depth++
|
||||||
|
} else if (ch == GreaterThan) {
|
||||||
|
if (depth == 0) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
depth--
|
||||||
|
} else if (ch == Comma && depth == 0) {
|
||||||
|
let argument = trimAsciiWhitespace(payload[segmentStart..index])
|
||||||
|
if (argument.isEmpty()) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
result.add(argument)
|
||||||
|
segmentStart = index + 1
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
if (depth != 0) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
let argument = trimAsciiWhitespace(payload[segmentStart..payload.size])
|
||||||
|
if (argument.isEmpty()) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
result.add(argument)
|
||||||
|
return result.toArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func trimAsciiWhitespace(value: String): String {
|
||||||
|
var start = 0
|
||||||
|
var end = value.size
|
||||||
|
while (start < end && isAsciiWhitespace(value[start])) {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
while (end > start && isAsciiWhitespace(value[end - 1])) {
|
||||||
|
end--
|
||||||
|
}
|
||||||
|
return value[start..end]
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func isAsciiWhitespace(ch: UInt8): Bool {
|
||||||
|
ch == Space || ch == Tab || ch == LineFeed || ch == CarriageReturn
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.interfaces
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 定义 OpenAPI 对象序列化接口。
|
||||||
|
*/
|
||||||
|
public interface IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
func serializeAsV3(writer: IOpenApiWriter): Unit
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.interfaces
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 定义 OpenAPI 文档写入接口。
|
||||||
|
*/
|
||||||
|
public interface IOpenApiWriter {
|
||||||
|
/**
|
||||||
|
* @brief 刷新当前写入器缓冲区。
|
||||||
|
*/
|
||||||
|
func flush(): Unit
|
||||||
|
/**
|
||||||
|
* @brief 开始写入对象。
|
||||||
|
*/
|
||||||
|
func startObject(): Unit
|
||||||
|
/**
|
||||||
|
* @brief 结束写入对象。
|
||||||
|
*/
|
||||||
|
func endObject(): Unit
|
||||||
|
/**
|
||||||
|
* @brief 开始写入数组。
|
||||||
|
*/
|
||||||
|
func startArray(): Unit
|
||||||
|
/**
|
||||||
|
* @brief 结束写入数组。
|
||||||
|
*/
|
||||||
|
func endArray(): Unit
|
||||||
|
/**
|
||||||
|
* @brief 写入属性名称。
|
||||||
|
* @param name 要写入的属性名称。
|
||||||
|
*/
|
||||||
|
func writeName(name: String): Unit
|
||||||
|
/**
|
||||||
|
* @brief 写入整数值。
|
||||||
|
* @param value 要写入的整数值。
|
||||||
|
*/
|
||||||
|
func writeValue(value: Int64): Unit
|
||||||
|
/**
|
||||||
|
* @brief 写入布尔值。
|
||||||
|
* @param value 要写入的布尔值。
|
||||||
|
*/
|
||||||
|
func writeValue(value: Bool): Unit
|
||||||
|
/**
|
||||||
|
* @brief 写入字符串值。
|
||||||
|
* @param value 要写入的字符串值。
|
||||||
|
*/
|
||||||
|
func writeValue(value: String): Unit
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.metadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 API 描述元数据。
|
||||||
|
*/
|
||||||
|
public interface IApiDescriptionMetadata {
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 描述信息。
|
||||||
|
* @return 当前 API 的描述信息。
|
||||||
|
*/
|
||||||
|
prop description: ?String
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.metadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 API 分组名称元数据。
|
||||||
|
*/
|
||||||
|
public interface IApiGroupNameProvider {
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 分组名称。
|
||||||
|
* @return 当前 API 的分组名称。
|
||||||
|
*/
|
||||||
|
prop groupName: ?String
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.metadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 API 名称元数据。
|
||||||
|
*/
|
||||||
|
public interface IApiNameMetadata {
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 名称。
|
||||||
|
* @return 当前 API 的名称。
|
||||||
|
*/
|
||||||
|
prop name: ?String
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.metadata
|
||||||
|
|
||||||
|
import std.reflect.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 API 响应元数据。
|
||||||
|
*/
|
||||||
|
public interface IApiResponseMetadataProvider {
|
||||||
|
/**
|
||||||
|
* @brief 返回响应状态码。
|
||||||
|
* @return 当前 API 响应的状态码。
|
||||||
|
*/
|
||||||
|
prop status: Int64
|
||||||
|
/**
|
||||||
|
* @brief 返回响应类型信息。
|
||||||
|
* @return 当前 API 响应的数据类型信息。
|
||||||
|
*/
|
||||||
|
prop typeInfo: TypeInfo
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.metadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 API 摘要元数据。
|
||||||
|
*/
|
||||||
|
public interface IApiSummaryMetadata {
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 摘要信息。
|
||||||
|
* @return 当前 API 的摘要信息。
|
||||||
|
*/
|
||||||
|
prop summary: ?String
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.metadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 API 标签元数据。
|
||||||
|
*/
|
||||||
|
public interface IApiTagsMetadata {
|
||||||
|
/**
|
||||||
|
* @brief 返回 API 标签字符串。
|
||||||
|
* @return 当前 API 的标签字符串。
|
||||||
|
*/
|
||||||
|
prop tags: ?String
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.metadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 API 可见性元数据。
|
||||||
|
*/
|
||||||
|
public interface IApiVisibilityProvider {
|
||||||
|
/**
|
||||||
|
* @brief 返回当前 API 是否应被忽略。
|
||||||
|
* @return 如果当前 API 需要从 OpenAPI 文档中忽略则返回 `true`。
|
||||||
|
*/
|
||||||
|
prop ignore: Bool
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
protected import simcu::simapi.openapi.interfaces.*
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 组件集合。
|
||||||
|
*/
|
||||||
|
public class OpenApiComponents <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示组件中的架构集合。
|
||||||
|
*/
|
||||||
|
public var schemas = HashMap<String, OpenApiSchema>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 组件实例。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前组件集合。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (!schemas.isEmpty()) {
|
||||||
|
writer.writeName("schemas")
|
||||||
|
writer.startObject()
|
||||||
|
for ((key, value) in schemas) {
|
||||||
|
writer.writeName(key)
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 联系人信息。
|
||||||
|
*/
|
||||||
|
public class OpenApiContact <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示联系人名称。
|
||||||
|
*/
|
||||||
|
public var name: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示联系人地址。
|
||||||
|
*/
|
||||||
|
public var url: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示联系人邮箱。
|
||||||
|
*/
|
||||||
|
public var email: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 联系人实例。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前联系人信息。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(value) <- name) {
|
||||||
|
writer.writeName("name")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (let Some(value) <- url) {
|
||||||
|
writer.writeName("url")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (let Some(value) <- email) {
|
||||||
|
writer.writeName("email")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 文档对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiDocument <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 版本号。
|
||||||
|
*/
|
||||||
|
public var openapi: String = "3.0.1"
|
||||||
|
/**
|
||||||
|
* @brief 表示文档基本信息。
|
||||||
|
*/
|
||||||
|
public var info: ?OpenApiInfo = None
|
||||||
|
/**
|
||||||
|
* @brief 表示路径集合。
|
||||||
|
*/
|
||||||
|
public var paths: ?OpenApiPaths = None
|
||||||
|
/**
|
||||||
|
* @brief 表示组件集合。
|
||||||
|
*/
|
||||||
|
public var components: ?OpenApiComponents = None
|
||||||
|
/**
|
||||||
|
* @brief 表示标签集合。
|
||||||
|
*/
|
||||||
|
public var tags = ArrayList<OpenApiTag>()
|
||||||
|
/**
|
||||||
|
* @brief 表示服务器集合。
|
||||||
|
*/
|
||||||
|
public var servers = ArrayList<OpenApiServer>()
|
||||||
|
/**
|
||||||
|
* @brief 表示安全需求集合。
|
||||||
|
*/
|
||||||
|
public var securityRequirements = ArrayList<OpenApiSecurityRequirement>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 文档对象。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前文档对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
writer.writeName("openapi")
|
||||||
|
writer.writeValue(openapi)
|
||||||
|
if (let Some(value) <- info) {
|
||||||
|
writer.writeName("info")
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- paths) {
|
||||||
|
writer.writeName("paths")
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- components) {
|
||||||
|
writer.writeName("components")
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
if (!tags.isEmpty()) {
|
||||||
|
writer.writeName("tags")
|
||||||
|
writer.startArray()
|
||||||
|
for (tag in tags) {
|
||||||
|
tag.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
if (!servers.isEmpty()) {
|
||||||
|
writer.writeName("servers")
|
||||||
|
writer.startArray()
|
||||||
|
for (server in servers) {
|
||||||
|
server.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
if (!securityRequirements.isEmpty()) {
|
||||||
|
writer.writeName("security")
|
||||||
|
writer.startArray()
|
||||||
|
for (sec in securityRequirements) {
|
||||||
|
sec.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 可扩展字典基类。
|
||||||
|
*/
|
||||||
|
public abstract class OpenApiExtensibleDictionary<T> <: IOpenApiSerializable where T <: IOpenApiSerializable {
|
||||||
|
private let _items: HashMap<String, T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建一个空的 OpenAPI 可扩展字典。
|
||||||
|
*/
|
||||||
|
protected init() {
|
||||||
|
this(HashMap<String, T>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 使用已有项创建 OpenAPI 可扩展字典。
|
||||||
|
* @param items 初始字典项集合。
|
||||||
|
*/
|
||||||
|
protected init(items: HashMap<String, T>) {
|
||||||
|
_items = items
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回内部字典项集合。
|
||||||
|
* @return 当前字典中的所有项。
|
||||||
|
*/
|
||||||
|
protected prop items: HashMap<String, T> {
|
||||||
|
get() {
|
||||||
|
_items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加字典项。
|
||||||
|
* @param key 字典键。
|
||||||
|
* @param value 字典值。
|
||||||
|
*/
|
||||||
|
public func add(key: String, value: T): Unit {
|
||||||
|
_items.add(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否包含指定键。
|
||||||
|
* @param key 要检查的字典键。
|
||||||
|
* @return 如果包含指定键则返回 `true`。
|
||||||
|
*/
|
||||||
|
public func contains(key: String): Bool {
|
||||||
|
_items.contains(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 通过键读取字典值。
|
||||||
|
* @param key 要读取的字典键。
|
||||||
|
* @return 对应的字典值。
|
||||||
|
*/
|
||||||
|
public operator func [](key: String): T {
|
||||||
|
_items[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 通过键设置字典值。
|
||||||
|
* @param key 要设置的字典键。
|
||||||
|
* @param value 要设置的字典值。
|
||||||
|
*/
|
||||||
|
public operator func [](key: String, value!: T): Unit {
|
||||||
|
_items[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前字典对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
for ((key, value) in _items) {
|
||||||
|
writer.writeName(key)
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查当前字典是否为空。
|
||||||
|
* @return 如果当前字典没有任何项则返回 `true`。
|
||||||
|
*/
|
||||||
|
public func isEmpty(): Bool {
|
||||||
|
return _items.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 基本信息。
|
||||||
|
*/
|
||||||
|
public class OpenApiInfo <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示文档标题。
|
||||||
|
*/
|
||||||
|
public var title: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示文档描述。
|
||||||
|
*/
|
||||||
|
public var description: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示文档版本。
|
||||||
|
*/
|
||||||
|
public var version: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示联系人信息。
|
||||||
|
*/
|
||||||
|
public var contact: ?OpenApiContact = None
|
||||||
|
/**
|
||||||
|
* @brief 表示许可证信息。
|
||||||
|
*/
|
||||||
|
public var license: ?OpenApiLicense = None
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 基本信息实例。
|
||||||
|
* @param title 文档标题。
|
||||||
|
* @param version 文档版本。
|
||||||
|
*/
|
||||||
|
public init(title!: String, version!: String) {
|
||||||
|
this.title = title
|
||||||
|
this.version = version
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前基本信息。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(value) <- title) {
|
||||||
|
writer.writeName("title")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- description) {
|
||||||
|
writer.writeName("description")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- version) {
|
||||||
|
writer.writeName("version")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- contact) {
|
||||||
|
writer.writeName("contact")
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- license) {
|
||||||
|
writer.writeName("license")
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 许可证信息。
|
||||||
|
*/
|
||||||
|
public class OpenApiLicense <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示许可证名称。
|
||||||
|
*/
|
||||||
|
public var name: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示许可证地址。
|
||||||
|
*/
|
||||||
|
public var url: ?String = None
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 许可证实例。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前许可证信息。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(value) <- name) {
|
||||||
|
writer.writeName("name")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- url) {
|
||||||
|
writer.writeName("url")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 媒体类型对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiMediaType <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示媒体类型对应的架构。
|
||||||
|
*/
|
||||||
|
public var schema: ?OpenApiSchema = None
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 媒体类型实例。
|
||||||
|
* @param schema 媒体类型对应的架构。
|
||||||
|
*/
|
||||||
|
public init(schema: ?OpenApiSchema) {
|
||||||
|
this.schema = schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前媒体类型对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(value) <- schema) {
|
||||||
|
writer.writeName("schema")
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 操作对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiOperation <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示操作摘要。
|
||||||
|
*/
|
||||||
|
public var summary: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示操作描述。
|
||||||
|
*/
|
||||||
|
public var description: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示操作标识。
|
||||||
|
*/
|
||||||
|
public var operationId: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示操作响应集合。
|
||||||
|
*/
|
||||||
|
public var response = OpenApiResponses()
|
||||||
|
/**
|
||||||
|
* @brief 表示操作标签集合。
|
||||||
|
*/
|
||||||
|
public var tags = ArrayList<OpenApiTag>()
|
||||||
|
/**
|
||||||
|
* @brief 表示操作服务器集合。
|
||||||
|
*/
|
||||||
|
public var servers = ArrayList<OpenApiServer>()
|
||||||
|
/**
|
||||||
|
* @brief 表示操作请求体。
|
||||||
|
*/
|
||||||
|
public var requestBody:? OpenApiRequestBody = None
|
||||||
|
/**
|
||||||
|
* @brief 表示操作参数集合。
|
||||||
|
*/
|
||||||
|
public var parameters = ArrayList<OpenApiParameter>()
|
||||||
|
/**
|
||||||
|
* @brief 表示操作安全需求集合。
|
||||||
|
*/
|
||||||
|
public var security = ArrayList<OpenApiSecurityRequirement>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 操作对象。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前操作对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (!tags.isEmpty()) {
|
||||||
|
writer.writeName("tags")
|
||||||
|
writer.startArray()
|
||||||
|
for (tag in tags) {
|
||||||
|
tag.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
if (let Some(value) <- summary) {
|
||||||
|
writer.writeName("summary")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- description) {
|
||||||
|
writer.writeName("description")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- operationId) {
|
||||||
|
writer.writeName("operationId")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (!servers.isEmpty()) {
|
||||||
|
writer.writeName("servers")
|
||||||
|
writer.startArray()
|
||||||
|
for (server in servers) {
|
||||||
|
server.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
if (!parameters.isEmpty()) {
|
||||||
|
writer.writeName("parameters")
|
||||||
|
writer.startArray()
|
||||||
|
for (param in parameters) {
|
||||||
|
param.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
if (!security.isEmpty()) {
|
||||||
|
writer.writeName("security")
|
||||||
|
writer.startArray()
|
||||||
|
for (sec in security) {
|
||||||
|
sec.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
if (!response.isEmpty()) {
|
||||||
|
writer.writeName("responses")
|
||||||
|
response.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
if (let Some(requestBody) <- requestBody) {
|
||||||
|
writer.writeName("requestBody")
|
||||||
|
requestBody.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 参数对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiParameter <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示参数名称。
|
||||||
|
*/
|
||||||
|
public var name: ?String
|
||||||
|
/**
|
||||||
|
* @brief 表示参数是否必填。
|
||||||
|
*/
|
||||||
|
public var required: ?Bool
|
||||||
|
/**
|
||||||
|
* @brief 表示参数是否已弃用。
|
||||||
|
*/
|
||||||
|
public var deprecated: ?Bool
|
||||||
|
/**
|
||||||
|
* @brief 表示参数描述。
|
||||||
|
*/
|
||||||
|
public var description: ?String
|
||||||
|
/**
|
||||||
|
* @brief 表示参数架构。
|
||||||
|
*/
|
||||||
|
public var schema: ?OpenApiSchema = None
|
||||||
|
/**
|
||||||
|
* @brief 表示参数位置。
|
||||||
|
*/
|
||||||
|
public var location: ?ParameterLocation = None
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 参数对象。
|
||||||
|
* @param name 参数名称。
|
||||||
|
* @param location 参数位置。
|
||||||
|
* @param description 参数描述。
|
||||||
|
* @param required 参数是否必填。
|
||||||
|
* @param deprecated 参数是否已弃用。
|
||||||
|
* @param schema 参数架构。
|
||||||
|
*/
|
||||||
|
public init(name!: ?String, location!: ?ParameterLocation = None, description!: ?String = None,
|
||||||
|
required!: ?Bool = None, deprecated!: ?Bool = None, schema!: ?OpenApiSchema = None) {
|
||||||
|
this.name = name
|
||||||
|
this.schema = schema
|
||||||
|
this.location = location
|
||||||
|
this.required = required
|
||||||
|
this.deprecated = deprecated
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前参数对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(value) <- name) {
|
||||||
|
writer.writeName("name")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(loc) <- location) {
|
||||||
|
writer.writeName("in")
|
||||||
|
writer.writeValue(loc.toString())
|
||||||
|
}
|
||||||
|
if (let Some(desc) <- description) {
|
||||||
|
writer.writeName("description")
|
||||||
|
writer.writeValue(desc)
|
||||||
|
}
|
||||||
|
if (let Some(req) <- required) {
|
||||||
|
writer.writeName("required")
|
||||||
|
writer.writeValue(req)
|
||||||
|
}
|
||||||
|
if (let Some(dep) <- deprecated) {
|
||||||
|
writer.writeName("deprecated")
|
||||||
|
writer.writeValue(dep)
|
||||||
|
}
|
||||||
|
if (let Some(s) <- schema) {
|
||||||
|
writer.writeName("schema")
|
||||||
|
s.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 路径项对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiPathItem <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示路径项摘要。
|
||||||
|
*/
|
||||||
|
public var summary: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示路径项描述。
|
||||||
|
*/
|
||||||
|
public var description: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示当前路径项是否未解析引用。
|
||||||
|
*/
|
||||||
|
public var unresolvedReference: Bool = false
|
||||||
|
/**
|
||||||
|
* @brief 表示路径项引用。
|
||||||
|
*/
|
||||||
|
public var reference: ?OpenApiReference = None
|
||||||
|
/**
|
||||||
|
* @brief 表示路径项服务器集合。
|
||||||
|
*/
|
||||||
|
public var servers = ArrayList<OpenApiServer>()
|
||||||
|
/**
|
||||||
|
* @brief 表示路径项参数集合。
|
||||||
|
*/
|
||||||
|
public var parameters = ArrayList<OpenApiParameter>()
|
||||||
|
/**
|
||||||
|
* @brief 表示路径项操作集合。
|
||||||
|
*/
|
||||||
|
public var operations = HashMap<OperationType, OpenApiOperation>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 路径项对象。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前路径项对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(value) <- summary) {
|
||||||
|
writer.writeName("summary")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- description) {
|
||||||
|
writer.writeName("description")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (unresolvedReference) {
|
||||||
|
writer.writeName("$ref")
|
||||||
|
if (let Some(value) <- reference) {
|
||||||
|
writer.writeValue(value.toReferenceString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!servers.isEmpty()) {
|
||||||
|
writer.writeName("servers")
|
||||||
|
writer.startArray()
|
||||||
|
for (server in servers) {
|
||||||
|
server.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
if (!parameters.isEmpty()) {
|
||||||
|
writer.writeName("parameters")
|
||||||
|
writer.startArray()
|
||||||
|
for (param in parameters) {
|
||||||
|
param.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
for ((key, value) in operations) {
|
||||||
|
writer.writeName(key.toString())
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 路径集合。
|
||||||
|
*/
|
||||||
|
public class OpenApiPaths <: OpenApiExtensibleDictionary<OpenApiPathItem> {
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 路径集合实例。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private init(paths: HashMap<String, OpenApiPathItem>) {
|
||||||
|
super(paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 引用对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiReference <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示引用标识。
|
||||||
|
*/
|
||||||
|
public var id: String
|
||||||
|
/**
|
||||||
|
* @brief 表示引用类型。
|
||||||
|
*/
|
||||||
|
public var referenceType: ReferenceType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 引用对象。
|
||||||
|
* @param referenceType 引用类型。
|
||||||
|
* @param id 引用标识。
|
||||||
|
*/
|
||||||
|
public init(referenceType: ReferenceType, id: String) {
|
||||||
|
this.id = id
|
||||||
|
this.referenceType = referenceType
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回当前引用在 OpenAPI 文档中的字符串值。
|
||||||
|
* @return 引用字符串。
|
||||||
|
*/
|
||||||
|
public func toReferenceString(): String {
|
||||||
|
if (ReferenceType.Tag == referenceType) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return "#/components/${referenceType.toString()}/${id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前引用对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
if (ReferenceType.Tag == referenceType) {
|
||||||
|
writer.writeValue(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.startObject()
|
||||||
|
writer.writeName("$ref")
|
||||||
|
writer.writeValue(toReferenceString())
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 请求体对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiRequestBody <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示请求体是否必填。
|
||||||
|
*/
|
||||||
|
public var required: Bool = false
|
||||||
|
/**
|
||||||
|
* @brief 表示请求体描述。
|
||||||
|
*/
|
||||||
|
public var description: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示请求体内容映射。
|
||||||
|
*/
|
||||||
|
public var content = HashMap<String, OpenApiMediaType>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 请求体对象。
|
||||||
|
* @param required 请求体是否必填。
|
||||||
|
* @param description 请求体描述。
|
||||||
|
*/
|
||||||
|
public init(required!: Bool = false, description!: ?String = None) {
|
||||||
|
this.required = required
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前请求体对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(desc) <- description) {
|
||||||
|
writer.writeName("description")
|
||||||
|
writer.writeValue(desc)
|
||||||
|
}
|
||||||
|
if (!content.isEmpty()) {
|
||||||
|
writer.writeName("content")
|
||||||
|
writer.startObject()
|
||||||
|
for ((key, value) in content) {
|
||||||
|
writer.writeName(key)
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
if (required) {
|
||||||
|
writer.writeName("required")
|
||||||
|
writer.writeValue(true)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 响应对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiResponse <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示响应描述。
|
||||||
|
*/
|
||||||
|
public var description: String
|
||||||
|
/**
|
||||||
|
* @brief 表示响应内容映射。
|
||||||
|
*/
|
||||||
|
public var content = HashMap<String, OpenApiMediaType>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 响应对象。
|
||||||
|
* @param description 响应描述。
|
||||||
|
*/
|
||||||
|
public init(description: String) {
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前响应对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
writer.writeName("description")
|
||||||
|
writer.writeValue(description)
|
||||||
|
if (!content.isEmpty()) {
|
||||||
|
writer.writeName("content")
|
||||||
|
writer.startObject()
|
||||||
|
for ((key, value) in content) {
|
||||||
|
writer.writeName(key)
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 响应集合。
|
||||||
|
*/
|
||||||
|
public class OpenApiResponses <: OpenApiExtensibleDictionary<OpenApiResponse> {
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 响应集合实例。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 架构对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiSchema <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示架构标题。
|
||||||
|
*/
|
||||||
|
public var title: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示架构是否可为空。
|
||||||
|
*/
|
||||||
|
public var nullable: ?Bool = None
|
||||||
|
/**
|
||||||
|
* @brief 表示架构类型。
|
||||||
|
*/
|
||||||
|
public var `type`: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示数组元素架构。
|
||||||
|
*/
|
||||||
|
public var items: ?OpenApiSchema = None
|
||||||
|
/**
|
||||||
|
* @brief 表示架构格式。
|
||||||
|
*/
|
||||||
|
public var format: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示架构描述。
|
||||||
|
*/
|
||||||
|
public var description: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示架构引用。
|
||||||
|
*/
|
||||||
|
public var reference: ?OpenApiReference = None
|
||||||
|
/**
|
||||||
|
* @brief 表示对象属性集合。
|
||||||
|
*/
|
||||||
|
public var properties = HashMap<String, OpenApiSchema>()
|
||||||
|
/**
|
||||||
|
* @brief 表示 allOf 组合架构。
|
||||||
|
*/
|
||||||
|
public var allOf = ArrayList<OpenApiSchema>()
|
||||||
|
/**
|
||||||
|
* @brief 表示是否允许附加属性。
|
||||||
|
*/
|
||||||
|
public var additionalProperties: ?Bool = None
|
||||||
|
/**
|
||||||
|
* @brief 表示附加属性的值架构。
|
||||||
|
*/
|
||||||
|
public var additionalPropertiesSchema: ?OpenApiSchema = None
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 架构对象。
|
||||||
|
* @param title 架构标题。
|
||||||
|
* @param nullable 架构是否可为空。
|
||||||
|
* @param `type` 架构类型。
|
||||||
|
* @param items 数组元素架构。
|
||||||
|
* @param format 架构格式。
|
||||||
|
* @param reference 架构引用。
|
||||||
|
* @param description 架构描述。
|
||||||
|
*/
|
||||||
|
public init(title!: ?String = None, nullable!: ?Bool = None, `type`!: ?String = None, items!: ?OpenApiSchema = None,
|
||||||
|
format!: ?String = None, reference!: ?OpenApiReference = None, description!: ?String = None) {
|
||||||
|
this.title = title
|
||||||
|
this.format = format
|
||||||
|
this.`type` = `type`
|
||||||
|
this.items = items
|
||||||
|
this.nullable = nullable
|
||||||
|
this.reference = reference
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前架构对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
if (let Some(reference) <- reference) {
|
||||||
|
reference.serializeAsV3(writer)
|
||||||
|
} else {
|
||||||
|
serializeAsV3WithoutReference(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func serializeAsV3WithoutReference(writer: IOpenApiWriter) {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(value) <- title) {
|
||||||
|
writer.writeName("title")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- `type`) {
|
||||||
|
writer.writeName("type")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- items) {
|
||||||
|
writer.writeName("items")
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- nullable && value) {
|
||||||
|
writer.writeName("nullable")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- format) {
|
||||||
|
writer.writeName("format")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- description) {
|
||||||
|
writer.writeName("description")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (!properties.isEmpty()) {
|
||||||
|
writer.writeName("properties")
|
||||||
|
writer.startObject()
|
||||||
|
for ((key, value) in properties) {
|
||||||
|
writer.writeName(key)
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
if (!allOf.isEmpty()) {
|
||||||
|
writer.writeName("allOf")
|
||||||
|
writer.startArray()
|
||||||
|
for (schema in allOf) {
|
||||||
|
schema.serializeAsV3(writer)
|
||||||
|
}
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
|
if (let Some(value) <- additionalPropertiesSchema) {
|
||||||
|
writer.writeName("additionalProperties")
|
||||||
|
value.serializeAsV3(writer)
|
||||||
|
} else if (let Some(value) <- additionalProperties) {
|
||||||
|
writer.writeName("additionalProperties")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import std.reflect.*
|
||||||
|
|
||||||
|
|
||||||
|
private let numbers = ["Float16", "Float32", "Float64", "Decimal"]
|
||||||
|
private let integers = ["Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64"]
|
||||||
|
|
||||||
|
protected class OpenApiSchemaTypes {
|
||||||
|
public static const OBJECT = "object"
|
||||||
|
public static const STRING = "string"
|
||||||
|
public static const ARRAY = "array"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将类型信息转换为 OpenAPI Schema 类型名称。
|
||||||
|
* @param typeInfo 要转换的类型信息。
|
||||||
|
* @return 对应的 OpenAPI Schema 类型名称。
|
||||||
|
*/
|
||||||
|
public static func convert(typeInfo: TypeInfo): String {
|
||||||
|
if (typeInfo.name == "Bool") {
|
||||||
|
return "boolean"
|
||||||
|
}
|
||||||
|
if (typeInfo.name == "String" || typeInfo.name == "DateTime" || typeInfo.name == "BigInt" ||
|
||||||
|
typeInfo.name == "Rune") {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
if (numbers.contains(typeInfo.name)) {
|
||||||
|
return "number"
|
||||||
|
}
|
||||||
|
if (integers.contains(typeInfo.name)) {
|
||||||
|
return "integer"
|
||||||
|
}
|
||||||
|
return OpenApiSchemaTypes.OBJECT
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 安全需求对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiSecurityRequirement <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 安全需求对象。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前安全需求对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 服务器对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiServer <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示服务器描述。
|
||||||
|
*/
|
||||||
|
public var description: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示服务器地址。
|
||||||
|
*/
|
||||||
|
public var url: ?String = None
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 服务器对象。
|
||||||
|
*/
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前服务器对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.startObject()
|
||||||
|
if (let Some(value) <- url) {
|
||||||
|
writer.writeName("url")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
if (let Some(value) <- description) {
|
||||||
|
writer.writeName("description")
|
||||||
|
writer.writeValue(value)
|
||||||
|
}
|
||||||
|
writer.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 标签对象。
|
||||||
|
*/
|
||||||
|
public class OpenApiTag <: IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示标签名称。
|
||||||
|
*/
|
||||||
|
public var name: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示标签描述。
|
||||||
|
*/
|
||||||
|
public var description: ?String = None
|
||||||
|
/**
|
||||||
|
* @brief 表示标签引用。
|
||||||
|
*/
|
||||||
|
public var reference: ?OpenApiReference = None
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 标签对象。
|
||||||
|
* @param name 标签名称。
|
||||||
|
* @param reference 标签引用。
|
||||||
|
* @param description 标签描述。
|
||||||
|
*/
|
||||||
|
public init(name!: ?String = None, reference!: ?OpenApiReference = None, description!: ?String = None) {
|
||||||
|
this.name = name
|
||||||
|
this.reference = reference
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前标签对象。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
if (let Some(reference) <- reference) {
|
||||||
|
reference.serializeAsV3(writer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (let Some(name) <- name) {
|
||||||
|
writer.writeValue(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
import soulsoft_web_http.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 操作类型。
|
||||||
|
*/
|
||||||
|
public enum OperationType <: Equatable<OperationType> & Hashable & IOpenApiSerializable {
|
||||||
|
/**
|
||||||
|
* @brief 表示 GET 操作。
|
||||||
|
*/
|
||||||
|
Get
|
||||||
|
/**
|
||||||
|
* @brief 表示 PUT 操作。
|
||||||
|
*/
|
||||||
|
| Put
|
||||||
|
/**
|
||||||
|
* @brief 表示 POST 操作。
|
||||||
|
*/
|
||||||
|
| Post
|
||||||
|
/**
|
||||||
|
* @brief 表示 DELETE 操作。
|
||||||
|
*/
|
||||||
|
| Delete
|
||||||
|
/**
|
||||||
|
* @brief 表示 OPTIONS 操作。
|
||||||
|
*/
|
||||||
|
| Options
|
||||||
|
/**
|
||||||
|
* @brief 表示 HEAD 操作。
|
||||||
|
*/
|
||||||
|
| Head
|
||||||
|
/**
|
||||||
|
* @brief 表示 PATCH 操作。
|
||||||
|
*/
|
||||||
|
| Patch
|
||||||
|
/**
|
||||||
|
* @brief 表示 TRACE 操作。
|
||||||
|
*/
|
||||||
|
| Trace
|
||||||
|
/**
|
||||||
|
* @brief 表示未知操作。
|
||||||
|
*/
|
||||||
|
| Unkonw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 比较两个操作类型是否相等。
|
||||||
|
* @param that 要比较的操作类型。
|
||||||
|
* @return 如果两个操作类型相等则返回 `true`。
|
||||||
|
*/
|
||||||
|
public operator func ==(that: OperationType): Bool {
|
||||||
|
match ((this, that)) {
|
||||||
|
case (Get, Get) => true
|
||||||
|
case (Put, Put) => true
|
||||||
|
case (Post, Post) => true
|
||||||
|
case (Delete, Delete) => true
|
||||||
|
case (Options, Options) => true
|
||||||
|
case (Head, Head) => true
|
||||||
|
case (Patch, Patch) => true
|
||||||
|
case (Trace, Trace) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按 OpenAPI V3 格式写出当前操作类型。
|
||||||
|
* @param writer OpenAPI 写入器。
|
||||||
|
*/
|
||||||
|
public func serializeAsV3(writer: IOpenApiWriter): Unit {
|
||||||
|
writer.writeValue(this.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回当前操作类型的哈希值。
|
||||||
|
* @return 当前操作类型的哈希值。
|
||||||
|
*/
|
||||||
|
public func hashCode(): Int64 {
|
||||||
|
this.toString().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 HTTP 方法字符串解析为操作类型。
|
||||||
|
* @param str 要解析的 HTTP 方法字符串。
|
||||||
|
* @return 解析得到的操作类型。
|
||||||
|
* @throws IllegalFormatException 当字符串不是支持的 HTTP 方法时抛出。
|
||||||
|
*/
|
||||||
|
public static func parse(str: String): OperationType {
|
||||||
|
match (str) {
|
||||||
|
case x where HttpMethods.isGet(x) => Get
|
||||||
|
case x where HttpMethods.isPut(x) => Put
|
||||||
|
case x where HttpMethods.isPost(x) => Post
|
||||||
|
case x where HttpMethods.isDelete(x) => Delete
|
||||||
|
case x where HttpMethods.isOptions(x) => Options
|
||||||
|
case x where HttpMethods.isHead(x) => Head
|
||||||
|
case x where HttpMethods.isPatch(x) => Patch
|
||||||
|
case x where HttpMethods.isTrace(x) => Trace
|
||||||
|
case _ => throw IllegalFormatException("Invalid OperationType format: ${str}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回操作类型对应的名称。
|
||||||
|
* @return 当前操作类型对应的名称字符串。
|
||||||
|
*/
|
||||||
|
public func toString(): String {
|
||||||
|
match (this) {
|
||||||
|
case Get => "get"
|
||||||
|
case Put => "put"
|
||||||
|
case Post => "post"
|
||||||
|
case Delete => "delete"
|
||||||
|
case Options => "options"
|
||||||
|
case Head => "head"
|
||||||
|
case Patch => "patch"
|
||||||
|
case Trace => "trace"
|
||||||
|
case Unkonw => "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 参数位置。
|
||||||
|
*/
|
||||||
|
public enum ParameterLocation <: ToString {
|
||||||
|
/**
|
||||||
|
* @brief 表示查询字符串参数。
|
||||||
|
*/
|
||||||
|
Query
|
||||||
|
/**
|
||||||
|
* @brief 表示请求头参数。
|
||||||
|
*/
|
||||||
|
| Header
|
||||||
|
/**
|
||||||
|
* @brief 表示路径参数。
|
||||||
|
*/
|
||||||
|
| Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回参数位置对应的名称。
|
||||||
|
* @return 当前参数位置对应的名称字符串。
|
||||||
|
*/
|
||||||
|
public func toString(): String {
|
||||||
|
match (this) {
|
||||||
|
case Query => "query"
|
||||||
|
case Header => "header"
|
||||||
|
case Path => "path"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示 OpenAPI 引用类型。
|
||||||
|
*/
|
||||||
|
public enum ReferenceType <: Equatable<ReferenceType> & ToString {
|
||||||
|
/**
|
||||||
|
* @brief 表示标签引用类型。
|
||||||
|
*/
|
||||||
|
Tag
|
||||||
|
/**
|
||||||
|
* @brief 表示链接引用类型。
|
||||||
|
*/
|
||||||
|
| Link
|
||||||
|
/**
|
||||||
|
* @brief 表示架构引用类型。
|
||||||
|
*/
|
||||||
|
| Schema
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 比较两个引用类型是否相等。
|
||||||
|
* @param that 要比较的引用类型。
|
||||||
|
* @return 如果两个引用类型相等则返回 `true`。
|
||||||
|
*/
|
||||||
|
public operator func ==(that: ReferenceType): Bool {
|
||||||
|
match ((this, that)) {
|
||||||
|
case (Tag, Tag) => true
|
||||||
|
case (Link, Link) => true
|
||||||
|
case (Schema, Schema) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回引用类型对应的名称。
|
||||||
|
* @return 当前引用类型对应的名称字符串。
|
||||||
|
*/
|
||||||
|
public func toString(): String {
|
||||||
|
match (this) {
|
||||||
|
case Tag => "tags"
|
||||||
|
case Link => "links"
|
||||||
|
case Schema => "schemas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.services
|
||||||
|
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 定义 OpenAPI 文档提供器接口。
|
||||||
|
*/
|
||||||
|
public interface IDocumentProvider {
|
||||||
|
/**
|
||||||
|
* @brief 返回已注册的文档名称集合。
|
||||||
|
* @return 当前可用的文档名称集合。
|
||||||
|
*/
|
||||||
|
func getDooucmentNames(): Collection<String>
|
||||||
|
/**
|
||||||
|
* @brief 按名称创建 OpenAPI 文档。
|
||||||
|
* @param documentName 要创建的文档名称。
|
||||||
|
* @return 生成后的 OpenAPI 文档。
|
||||||
|
*/
|
||||||
|
func create(documentName: String, services: IServiceProvider): OpenApiDocument
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.services
|
||||||
|
|
||||||
|
import std.io.*
|
||||||
|
import stdx.encoding.json.stream.*
|
||||||
|
import simcu::simapi.openapi.interfaces.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供基于 JSON 的 OpenAPI 写入器实现。
|
||||||
|
*/
|
||||||
|
public class JsonOpenApiWriter <: IOpenApiWriter {
|
||||||
|
private let _writer: JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 JSON OpenAPI 写入器实例。
|
||||||
|
* @param stream 输出流。
|
||||||
|
*/
|
||||||
|
public init(stream: OutputStream) {
|
||||||
|
_writer = JsonWriter(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始写入对象。
|
||||||
|
*/
|
||||||
|
public func startObject(): Unit {
|
||||||
|
_writer.startObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束写入对象。
|
||||||
|
*/
|
||||||
|
public func endObject(): Unit {
|
||||||
|
_writer.endObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始写入数组。
|
||||||
|
*/
|
||||||
|
public func startArray(): Unit {
|
||||||
|
_writer.startArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束写入数组。
|
||||||
|
*/
|
||||||
|
public func endArray(): Unit {
|
||||||
|
_writer.endArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入属性名称。
|
||||||
|
* @param name 要写入的属性名称。
|
||||||
|
*/
|
||||||
|
public func writeName(name: String): Unit {
|
||||||
|
_writer.writeName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入字符串值。
|
||||||
|
* @param value 要写入的字符串值。
|
||||||
|
*/
|
||||||
|
public func writeValue(value: String): Unit {
|
||||||
|
_writer.writeValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入整数值。
|
||||||
|
* @param value 要写入的整数值。
|
||||||
|
*/
|
||||||
|
public func writeValue(value: Int64): Unit {
|
||||||
|
_writer.writeValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入布尔值。
|
||||||
|
* @param value 要写入的布尔值。
|
||||||
|
*/
|
||||||
|
public func writeValue(value: Bool): Unit {
|
||||||
|
_writer.writeValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 刷新当前写入器。
|
||||||
|
*/
|
||||||
|
public func flush(): Unit {
|
||||||
|
_writer.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.services
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 表示带名称的服务标识。
|
||||||
|
*/
|
||||||
|
public class NamedService {
|
||||||
|
/**
|
||||||
|
* @brief 表示服务名称。
|
||||||
|
*/
|
||||||
|
public let name: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建带名称的服务标识实例。
|
||||||
|
* @param name 服务名称。
|
||||||
|
*/
|
||||||
|
public init(name: String) {
|
||||||
|
this.name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.services
|
||||||
|
|
||||||
|
import std.collection.*
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
import soulsoft_extensions_options.*
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
import simcu::simapi.openapi.transformers.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI 文档创建能力。
|
||||||
|
*/
|
||||||
|
protected class OpenApiDocumentProvider <: IDocumentProvider {
|
||||||
|
private let _services: IServiceProvider
|
||||||
|
private let _options: IOptionsMonitor<OpenApiOptions>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 文档提供器实例。
|
||||||
|
* @param services 服务提供器。
|
||||||
|
* @param options OpenAPI 选项监视器。
|
||||||
|
* @param provider OpenAPI 文档生成服务。
|
||||||
|
*/
|
||||||
|
public init(services: IServiceProvider, options: IOptionsMonitor<OpenApiOptions>) {
|
||||||
|
_services = services
|
||||||
|
_options = options
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回已注册的文档名称集合。
|
||||||
|
* @return 当前可用的文档名称集合。
|
||||||
|
*/
|
||||||
|
public func getDooucmentNames(): Collection<String> {
|
||||||
|
_services.getAll<NamedService>() |> map {f => f.name} |> collectArray
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按名称创建 OpenAPI 文档并依次应用所有转换器。
|
||||||
|
* @param documentName 要创建的文档名称。
|
||||||
|
* @param services 请求作用域的服务提供器,传递给操作转换器上下文。
|
||||||
|
* @return 经过转换器处理后的 OpenAPI 文档。
|
||||||
|
*/
|
||||||
|
public func create(documentName: String, services: IServiceProvider): OpenApiDocument {
|
||||||
|
let options = _options.get(documentName)
|
||||||
|
let schemaService = OpenApiSchemaService(options, services, documentName)
|
||||||
|
let parameterService = OpenApiParameterService(schemaService)
|
||||||
|
let provider = ActivatorUtilities.createInstance<OpenApiDocumentService>(
|
||||||
|
services,
|
||||||
|
documentName,
|
||||||
|
schemaService,
|
||||||
|
parameterService
|
||||||
|
)
|
||||||
|
let document = provider.getOpenApiDocument(services)
|
||||||
|
let context = OpenApiDocumentTransformerContext(services, documentName)
|
||||||
|
for (transformer in options.documentTransformers) {
|
||||||
|
transformer.transform(document, context)
|
||||||
|
}
|
||||||
|
return document
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.services
|
||||||
|
|
||||||
|
import std.reflect.*
|
||||||
|
import std.collection.*
|
||||||
|
import soulsoft_web_mvc.*
|
||||||
|
import soulsoft_web_http.*
|
||||||
|
import soulsoft_web_routing.http.*
|
||||||
|
import soulsoft_web_mvc.utilities.*
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
import soulsoft_extensions_options.*
|
||||||
|
import soulsoft_web_mvc.controllers.*
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
import simcu::simapi.openapi.metadata.*
|
||||||
|
import soulsoft_web_mvc.abstractions.*
|
||||||
|
import simcu::simapi.openapi.transformers.*
|
||||||
|
import simcu::simapi.openapi.infrastructure.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI 文档生成功能。
|
||||||
|
*/
|
||||||
|
protected class OpenApiDocumentService {
|
||||||
|
private let _documentName: String
|
||||||
|
private let _endpointSource: EndpointDataSource
|
||||||
|
private let _openApiSchemaService: OpenApiSchemaService
|
||||||
|
private let _openApiParameterService: OpenApiParameterService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI 文档生成服务实例。
|
||||||
|
* @param endpointSource 终结点数据源。
|
||||||
|
* @param openApiSchemaService OpenAPI Schema 服务。
|
||||||
|
* @param openApiParameterService OpenAPI 参数服务。
|
||||||
|
* @param documentName 当前正在生成的文档名称。
|
||||||
|
*/
|
||||||
|
public init(
|
||||||
|
documentName: String,
|
||||||
|
endpointSource: EndpointDataSource,
|
||||||
|
openApiSchemaService: OpenApiSchemaService,
|
||||||
|
openApiParameterService: OpenApiParameterService
|
||||||
|
) {
|
||||||
|
_documentName = documentName
|
||||||
|
_endpointSource = endpointSource
|
||||||
|
_openApiSchemaService = openApiSchemaService
|
||||||
|
_openApiParameterService = openApiParameterService
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 生成 OpenAPI 文档。
|
||||||
|
* @param services 服务提供器,用于构建操作转换器上下文。
|
||||||
|
* @return 生成后的 OpenAPI 文档。
|
||||||
|
*/
|
||||||
|
public func getOpenApiDocument(services: IServiceProvider): OpenApiDocument {
|
||||||
|
let document = OpenApiDocument()
|
||||||
|
document.info = OpenApiInfo(title: "OpenApi | ${_documentName}", version: "1.0.0")
|
||||||
|
document.paths = createOpenApiPaths(services)
|
||||||
|
document.components = createOpenApiComponents()
|
||||||
|
return document
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createOpenApiPaths(services: IServiceProvider) {
|
||||||
|
let paths = OpenApiPaths()
|
||||||
|
let openApiOptions = services.getOrThrow<IOptionsMonitor<OpenApiOptions>>().get(_documentName)
|
||||||
|
let operationTransformers = openApiOptions.operationTransformers
|
||||||
|
for (endpoint in _endpointSource.endpoints |> filterMap {f => f as RouteEndpoint} where !isIgnore(endpoint)) {
|
||||||
|
if (!isShouldInclude(endpoint)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建用于显示的url路径
|
||||||
|
let key = if (let Some(displayText) <- endpoint.routePattern.displayText) {
|
||||||
|
"/${displayText.trimStart('/')}"
|
||||||
|
} else {
|
||||||
|
"/${endpoint.routePattern.rawText.trimStart('/')}"
|
||||||
|
}
|
||||||
|
|
||||||
|
let pathItem = if (paths.contains(key)) {
|
||||||
|
paths[key]
|
||||||
|
} else {
|
||||||
|
let item = OpenApiPathItem()
|
||||||
|
paths[key] = item
|
||||||
|
item
|
||||||
|
}
|
||||||
|
|
||||||
|
// add operations
|
||||||
|
if (let Some(metadata) <- endpoint.metadata.getMetadata<IHttpMethodMetadata>()) {
|
||||||
|
for (httpMethod in metadata.httpMethods) {
|
||||||
|
let operationType = OperationType.parse(httpMethod)
|
||||||
|
let actionDescriptor = endpoint.metadata.getMetadata<ControllerActionDescriptor>()
|
||||||
|
let operation = createOpenApiOperation(endpoint, actionDescriptor)
|
||||||
|
let context = OpenApiOperationTransformerContext(services, _documentName, actionDescriptor)
|
||||||
|
for (transformer in operationTransformers) {
|
||||||
|
transformer.transform(operation, context)
|
||||||
|
}
|
||||||
|
pathItem.operations.add(operationType, operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isShouldInclude(endpoint: RouteEndpoint) {
|
||||||
|
let groupNames = endpoint.metadata.getOrderedMetadata<IApiGroupNameProvider>() |> filterMap {f => f.groupName} |>
|
||||||
|
collectArray
|
||||||
|
if (groupNames.isEmpty() || groupNames.contains(_documentName)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createOpenApiComponents() {
|
||||||
|
let components = OpenApiComponents()
|
||||||
|
let schemas = _openApiSchemaService.getSchemas()
|
||||||
|
components.schemas = schemas
|
||||||
|
return components
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
生成每个终结点的文档
|
||||||
|
*/
|
||||||
|
private func createOpenApiOperation(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor): OpenApiOperation {
|
||||||
|
let operation = OpenApiOperation()
|
||||||
|
|
||||||
|
// tags
|
||||||
|
operation.tags.add(all: createOpenApiOperationTags(endpoint, actionDescriptor))
|
||||||
|
|
||||||
|
// parameters
|
||||||
|
if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||||
|
operation.parameters = _openApiParameterService.createParameters(actionDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestBody
|
||||||
|
if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||||
|
operation.requestBody = createOpenApiOperationRequestBody(endpoint, actionDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// response
|
||||||
|
operation.response = createOpenApiOperationResponses(actionDescriptor)
|
||||||
|
|
||||||
|
// summary
|
||||||
|
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiSummaryMetadata> {f => f.summary.isSome()}) {
|
||||||
|
if (let Some(summary) <- metadata.summary) {
|
||||||
|
operation.summary = summary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// description
|
||||||
|
if (let Some(metadata) <- endpoint
|
||||||
|
.metadata
|
||||||
|
.getLastMetadata<IApiDescriptionMetadata> {f => f.description.isSome()}) {
|
||||||
|
if (let Some(description) <- metadata.description) {
|
||||||
|
operation.description = description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// operationId
|
||||||
|
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiNameMetadata> {f => f.name.isSome()}) {
|
||||||
|
operation.operationId = metadata.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createOpenApiOperationTags(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
|
||||||
|
let result = ArrayList<OpenApiTag>()
|
||||||
|
// tags
|
||||||
|
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiTagsMetadata> {f => f.tags.isSome()}) {
|
||||||
|
if (let Some(tags) <- metadata.tags) {
|
||||||
|
for (name in tags.split(',')) {
|
||||||
|
result.add(OpenApiTag(name: name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||||
|
result.add(OpenApiTag(name: actionDescriptor.controllerName))
|
||||||
|
} else {
|
||||||
|
result.add(OpenApiTag(name: OpenApiConstants.DefaultOpenApiName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
来自请求体的参数
|
||||||
|
*/
|
||||||
|
private func createOpenApiOperationRequestBody(endpoint: RouteEndpoint, actionDescriptor: ControllerActionDescriptor): ?OpenApiRequestBody {
|
||||||
|
var requestBody = OpenApiRequestBody(required: true)
|
||||||
|
let parameters = HashMap<String, OpenApiSchema>()
|
||||||
|
for (parameter in actionDescriptor.actionFunction.parameters) {
|
||||||
|
let typeInfo = Nullable.getUnderlyingType(parameter.typeInfo) ?? parameter.typeInfo
|
||||||
|
let hasBindingSourceMetadata = parameter.annotations |> any {f => f is IBindingSourceMetadata}
|
||||||
|
if (parameter.findAnnotation<FromBody>().isSome() || !hasBindingSourceMetadata) {
|
||||||
|
let schema = _openApiSchemaService.createSchema(typeInfo)
|
||||||
|
requestBody.content.add("application/json", OpenApiMediaType(schema))
|
||||||
|
} else if (parameter.findAnnotation<FromForm>().isSome()) {
|
||||||
|
if (let classTypeInfo: ClassTypeInfo <- typeInfo) {
|
||||||
|
let schema = _openApiSchemaService.createInlineSchema(typeInfo)
|
||||||
|
for ((name, property) in schema.properties) {
|
||||||
|
parameters.add(name, property)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parameters.add(BindingNameUtilities.resolveFormName(parameter), _openApiSchemaService.createSchema(typeInfo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parameters.size > 0) {
|
||||||
|
let formSchema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT)
|
||||||
|
for ((name, schema) in parameters) {
|
||||||
|
formSchema.properties.add(name, schema)
|
||||||
|
}
|
||||||
|
requestBody.content.add("multipart/form-data", OpenApiMediaType(formSchema))
|
||||||
|
}
|
||||||
|
if (requestBody.content.size > 0) {
|
||||||
|
return requestBody
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
生成响应描述
|
||||||
|
*/
|
||||||
|
private func createOpenApiOperationResponses(actionDescriptor: ?ControllerActionDescriptor) {
|
||||||
|
let responses = OpenApiResponses()
|
||||||
|
|
||||||
|
// 获取action的返回类型
|
||||||
|
let returnType: ?TypeInfo = if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||||
|
Nullable.getUnderlyingType(actionDescriptor.actionFunction.returnType) ?? actionDescriptor
|
||||||
|
.actionFunction
|
||||||
|
.returnType
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
if (let Some(returnType) <- returnType && returnType != TypeInfo.of<Unit>()) {
|
||||||
|
let response = OpenApiResponse("OK")
|
||||||
|
let schema = _openApiSchemaService.createSchema(returnType)
|
||||||
|
response.content.add("text/plain", OpenApiMediaType(schema))
|
||||||
|
response.content.add("application/json", OpenApiMediaType(schema))
|
||||||
|
response.content.add("text/json", OpenApiMediaType(schema))
|
||||||
|
responses.add("200", response)
|
||||||
|
} else {
|
||||||
|
responses.add("200", OpenApiResponse("OK"))
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
// api ignore
|
||||||
|
private func isIgnore(endpoint: RouteEndpoint) {
|
||||||
|
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiVisibilityProvider> {f => f.ignore}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extend EndpointMetadataCollection {
|
||||||
|
/**
|
||||||
|
* @brief 返回最后一个满足条件的元数据。
|
||||||
|
* @param filter 用于筛选元数据的条件。
|
||||||
|
* @return 最后一个满足条件的元数据;如果不存在则返回 `None`。
|
||||||
|
*/
|
||||||
|
public func getLastMetadata<T>(filter: (T) -> Bool): ?T {
|
||||||
|
let metadatas = this.getOrderedMetadata<T>()
|
||||||
|
var index = metadatas.size - 1
|
||||||
|
while (index >= 0) {
|
||||||
|
let metadata = metadatas[index]
|
||||||
|
if (filter(metadata)) {
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.services
|
||||||
|
|
||||||
|
import std.reflect.*
|
||||||
|
import std.collection.*
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
import simcu::simapi.openapi.transformers.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 定义 OpenAPI 文档生成选项。
|
||||||
|
*/
|
||||||
|
public class OpenApiOptions {
|
||||||
|
private let _documentTransformers = ArrayList<IOpenApiDocumentTransformer>()
|
||||||
|
private let _operationTransformer = ArrayList<IOpenApiOperationTransformer>()
|
||||||
|
private let _schemaTransformers = ArrayList<IOpenApiSchemaTransformer>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 确定指定类型在 components/schemas 中使用的引用 ID。
|
||||||
|
* 返回 None 时该 Schema 始终内联,不生成 $ref。
|
||||||
|
*/
|
||||||
|
public var createSchemaReferenceId: (TypeInfo) -> ?String = {typeInfo =>
|
||||||
|
let name = typeInfo.name
|
||||||
|
if (let Some(ltIdx) <- name.indexOf('<') && let Some(gtIdx) <- name.lastIndexOf('>') &&
|
||||||
|
gtIdx > ltIdx) {
|
||||||
|
let baseName = name[0..ltIdx]
|
||||||
|
let argsStr = name[ltIdx + 1..gtIdx]
|
||||||
|
let simpleName = if (let Some(dotIdx) <- argsStr.lastIndexOf('.')) {
|
||||||
|
argsStr[dotIdx + 1..]
|
||||||
|
} else {
|
||||||
|
argsStr
|
||||||
|
}
|
||||||
|
"${baseName}Of${simpleName}"
|
||||||
|
} else {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回当前文档转换器集合。
|
||||||
|
* @return 已注册的文档转换器集合。
|
||||||
|
*/
|
||||||
|
protected prop documentTransformers: List<IOpenApiDocumentTransformer> {
|
||||||
|
get() {
|
||||||
|
_documentTransformers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回当前操作转换器集合。
|
||||||
|
* @return 已注册的操作转换器集合。
|
||||||
|
*/
|
||||||
|
protected prop operationTransformers: List<IOpenApiOperationTransformer> {
|
||||||
|
get() {
|
||||||
|
_operationTransformer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回当前架构转换器集合。
|
||||||
|
* @return 已注册的架构转换器集合。
|
||||||
|
*/
|
||||||
|
protected prop schemaTransformers: List<IOpenApiSchemaTransformer> {
|
||||||
|
get() {
|
||||||
|
_schemaTransformers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加文档转换器实例。
|
||||||
|
* @param transformer 要添加的 OpenAPI 文档转换器。
|
||||||
|
*/
|
||||||
|
public func addDocumentTransformer(transformer: IOpenApiDocumentTransformer): Unit {
|
||||||
|
_documentTransformers.add(transformer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 以委托形式添加文档转换器。
|
||||||
|
* @param transformer 表示文档转换逻辑的委托。
|
||||||
|
*/
|
||||||
|
public func addDocumentTransformer(transformer: (OpenApiDocument, OpenApiDocumentTransformerContext) -> Unit): Unit {
|
||||||
|
_documentTransformers.add(DelegateOpenApiDocumentTransformer(transformer))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加操作转换器实例。
|
||||||
|
* @param transformer 要添加的 OpenAPI 操作转换器。
|
||||||
|
*/
|
||||||
|
public func addOperationTransformer(transformer: IOpenApiOperationTransformer): Unit {
|
||||||
|
_operationTransformer.add(transformer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 以委托形式添加操作转换器。
|
||||||
|
* @param transformer 表示操作转换逻辑的委托。
|
||||||
|
*/
|
||||||
|
public func addOperationTransformer(transformer: (OpenApiOperation, OpenApiOperationTransformerContext) -> Unit): Unit {
|
||||||
|
_operationTransformer.add(DelegateOpenApiOperationTransformer(transformer))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加架构转换器实例。
|
||||||
|
* @param transformer 要添加的 OpenAPI 架构转换器。
|
||||||
|
*/
|
||||||
|
public func addSchemaTransformer(transformer: IOpenApiSchemaTransformer): Unit {
|
||||||
|
_schemaTransformers.add(transformer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 以委托形式添加架构转换器。
|
||||||
|
* @param transformer 表示架构转换逻辑的委托。
|
||||||
|
*/
|
||||||
|
public func addSchemaTransformer(transformer: (OpenApiSchema, OpenApiSchemaTransformerContext) -> Unit): Unit {
|
||||||
|
_schemaTransformers.add(DelegateOpenApiSchemaTransformer(transformer))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.services
|
||||||
|
|
||||||
|
import std.reflect.*
|
||||||
|
import std.collection.*
|
||||||
|
import soulsoft_web_mvc.*
|
||||||
|
import soulsoft_web_mvc.utilities.*
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
import soulsoft_web_mvc.controllers.*
|
||||||
|
import simcu::simapi.openapi.infrastructure.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI 参数生成能力。
|
||||||
|
*/
|
||||||
|
protected class OpenApiParameterService {
|
||||||
|
private let _openApiSchemaService: OpenApiSchemaService
|
||||||
|
|
||||||
|
public init(openApiSchemaService: OpenApiSchemaService) {
|
||||||
|
_openApiSchemaService = openApiSchemaService
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 为 action 参数生成 OpenAPI parameters。
|
||||||
|
* @param actionDescriptor 控制器 action 描述。
|
||||||
|
* @return operation 对应的 parameters。
|
||||||
|
*/
|
||||||
|
public func createParameters(actionDescriptor: ControllerActionDescriptor): ArrayList<OpenApiParameter> {
|
||||||
|
let parameters = ArrayList<OpenApiParameter>()
|
||||||
|
for (parameter in actionDescriptor.actionFunction.parameters) {
|
||||||
|
if (let Some(location) <- getParameterLocation(parameter)) {
|
||||||
|
parameters.add(all: createParameters(parameter, location))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createParameters(parameter: ParameterInfo, location: ParameterLocation): Array<OpenApiParameter> {
|
||||||
|
let underlyingType = Nullable.getUnderlyingType(parameter.typeInfo)
|
||||||
|
let isNullable = underlyingType.isSome()
|
||||||
|
let typeInfo = underlyingType ?? parameter.typeInfo
|
||||||
|
|
||||||
|
if (let classTypeInfo: ClassTypeInfo <- typeInfo) {
|
||||||
|
let objectSchema = _openApiSchemaService.createInlineSchema(typeInfo)
|
||||||
|
let parameters = ArrayList<OpenApiParameter>(objectSchema.properties.size)
|
||||||
|
for ((name, propertySchema) in objectSchema.properties) {
|
||||||
|
let isPropertyNullable = propertySchema.nullable ?? false
|
||||||
|
parameters.add(
|
||||||
|
OpenApiParameter(
|
||||||
|
name: name,
|
||||||
|
schema: propertySchema,
|
||||||
|
required: isRequiredParameter(location, isPropertyNullable),
|
||||||
|
location: location
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return parameters.toArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = _openApiSchemaService.createSchema(typeInfo)
|
||||||
|
if (isNullable) {
|
||||||
|
schema.nullable = true
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name: resolveParameterName(parameter, location),
|
||||||
|
schema: schema,
|
||||||
|
required: isRequiredParameter(location, isNullable),
|
||||||
|
location: location
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getParameterLocation(parameter: ParameterInfo): ?ParameterLocation {
|
||||||
|
if (parameter.findAnnotation<FromQuery>().isSome()) {
|
||||||
|
return ParameterLocation.Query
|
||||||
|
}
|
||||||
|
if (parameter.findAnnotation<FromRoute>().isSome()) {
|
||||||
|
return ParameterLocation.Path
|
||||||
|
}
|
||||||
|
if (parameter.findAnnotation<FromHeader>().isSome()) {
|
||||||
|
return ParameterLocation.Header
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resolveParameterName(parameter: ParameterInfo, location: ParameterLocation): String {
|
||||||
|
match (location) {
|
||||||
|
case ParameterLocation.Header => BindingNameUtilities.resolveHeaderName(parameter)
|
||||||
|
case ParameterLocation.Path => BindingNameUtilities.resolveRouteName(parameter)
|
||||||
|
case ParameterLocation.Query => BindingNameUtilities.resolveQueryName(parameter)
|
||||||
|
case _ => parameter.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isRequiredParameter(location: ParameterLocation, isNullable: Bool): Bool {
|
||||||
|
match (location) {
|
||||||
|
case ParameterLocation.Path => true
|
||||||
|
case _ => !isNullable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.services
|
||||||
|
|
||||||
|
import std.time.*
|
||||||
|
import std.reflect.*
|
||||||
|
import std.collection.*
|
||||||
|
import std.math.numeric.*
|
||||||
|
import stdx.encoding.json.*
|
||||||
|
import soulsoft_web_http.*
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
import soulsoft_web_mvc.IActionResult
|
||||||
|
import simcu::simapi.openapi.metadata.*
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
import simcu::simapi.openapi.transformers.*
|
||||||
|
import simcu::simapi.openapi.infrastructure.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供 OpenAPI Schema 生成功能。
|
||||||
|
*/
|
||||||
|
protected class OpenApiSchemaService {
|
||||||
|
private static let _primitiveTypes = HashSet<TypeInfo>(
|
||||||
|
[
|
||||||
|
TypeInfo.of<Rune>(),
|
||||||
|
TypeInfo.of<Bool>(),
|
||||||
|
TypeInfo.of<Int8>(),
|
||||||
|
TypeInfo.of<UInt8>(),
|
||||||
|
TypeInfo.of<Int16>(),
|
||||||
|
TypeInfo.of<UInt16>(),
|
||||||
|
TypeInfo.of<Int32>(),
|
||||||
|
TypeInfo.of<UInt32>(),
|
||||||
|
TypeInfo.of<Int64>(),
|
||||||
|
TypeInfo.of<UInt64>(),
|
||||||
|
TypeInfo.of<Float16>(),
|
||||||
|
TypeInfo.of<Float32>(),
|
||||||
|
TypeInfo.of<Float64>(),
|
||||||
|
TypeInfo.of<Any>(),
|
||||||
|
TypeInfo.of<Object>(),
|
||||||
|
TypeInfo.of<BigInt>(),
|
||||||
|
TypeInfo.of<String>(),
|
||||||
|
TypeInfo.of<Decimal>(),
|
||||||
|
TypeInfo.of<DateTime>()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
private static let _stringType = TypeInfo.of<String>()
|
||||||
|
private static let _formFileType = TypeInfo.of<IFormFile>()
|
||||||
|
private static let _jsonValueType = TypeInfo.of<JsonValue>()
|
||||||
|
private static let _actionResultType = TypeInfo.of<IActionResult>()
|
||||||
|
private let _documentName: String
|
||||||
|
private let _options: OpenApiOptions
|
||||||
|
private let _services: IServiceProvider
|
||||||
|
private let _schemas = HashMap<String, OpenApiSchema>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 OpenAPI Schema 服务实例。
|
||||||
|
* @param options 当前文档的 OpenAPI 选项。
|
||||||
|
* @param services 请求作用域的服务提供器。
|
||||||
|
*/
|
||||||
|
public init(options: OpenApiOptions, services: IServiceProvider, documentName: String) {
|
||||||
|
_options = options
|
||||||
|
_services = services
|
||||||
|
_documentName = documentName
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回当前已缓存的 Schema 集合。
|
||||||
|
* @return 以类型限定名为键的 Schema 集合。
|
||||||
|
*/
|
||||||
|
public func getSchemas(): HashMap<String, OpenApiSchema> {
|
||||||
|
return _schemas
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 为指定类型创建 OpenAPI Schema。
|
||||||
|
* @param typeInfo 要转换的类型信息。
|
||||||
|
* @return 对应的 OpenAPI Schema。
|
||||||
|
*/
|
||||||
|
public func createSchema(typeInfo: TypeInfo): OpenApiSchema {
|
||||||
|
return buildSchema(typeInfo, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 为指定类型创建内联 OpenAPI Schema。
|
||||||
|
* @param typeInfo 要转换的类型信息。
|
||||||
|
* @return 对应的 OpenAPI Schema。
|
||||||
|
*/
|
||||||
|
public func createInlineSchema(typeInfo: TypeInfo): OpenApiSchema {
|
||||||
|
return buildSchema(typeInfo, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
生成类型的 Schema。
|
||||||
|
preferReference=true 时,复杂对象优先输出为组件引用。
|
||||||
|
*/
|
||||||
|
private func buildSchema(typeInfo: TypeInfo, nullable: Bool, preferReference: Bool): OpenApiSchema {
|
||||||
|
// 处理基本类型
|
||||||
|
if (OpenApiSchemaService._primitiveTypes.contains(typeInfo)) {
|
||||||
|
let `type` = OpenApiSchemaTypes.convert(typeInfo)
|
||||||
|
let format = convertToSchemaFormat(typeInfo)
|
||||||
|
let schema = OpenApiSchema(`type`: `type`, format: format, nullable: nullable)
|
||||||
|
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// JsonValue/JsonObject/JsonArray 表示“任意 JSON 值树”,不应展开为固定 object schema。
|
||||||
|
if (typeInfo.isSubtypeOf(OpenApiSchemaService._jsonValueType)) {
|
||||||
|
let schema = OpenApiSchema(nullable: nullable)
|
||||||
|
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件上传
|
||||||
|
if (typeInfo == OpenApiSchemaService._formFileType) {
|
||||||
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, format: "binary")
|
||||||
|
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理字典类型
|
||||||
|
if (let Some((keyTypeInfo, valueTypeInfo)) <- resolveMapTypeArguments(typeInfo)) {
|
||||||
|
return createDictionarySchema(keyTypeInfo, valueTypeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理集合类型
|
||||||
|
if (let Some(elementTypeInfo) <- resolveCollectionElementType(typeInfo)) {
|
||||||
|
return createArraySchema(elementTypeInfo, preferReference)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理泛型:IActionResult 派生类型解构一层,将泛型参数当作普通类型处理
|
||||||
|
if (typeInfo.isSubtypeOf(OpenApiSchemaService._actionResultType)) {
|
||||||
|
if (let Some(resultValueType) <- resolveSingleGenericArgumentType(typeInfo)) {
|
||||||
|
return buildSchema(resultValueType, false, preferReference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (let _: EnumTypeInfo <- typeInfo) {
|
||||||
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, nullable: nullable)
|
||||||
|
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理复合类型
|
||||||
|
if (typeInfo is ClassTypeInfo || typeInfo is StructTypeInfo) {
|
||||||
|
if (preferReference) {
|
||||||
|
return createReferencedObjectSchema(typeInfo, nullable)
|
||||||
|
}
|
||||||
|
return createInlineObjectSchema(typeInfo, nullable)
|
||||||
|
}
|
||||||
|
|
||||||
|
return OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
生成对象的 Schema,引用 ID 由 createSchemaReferenceId 委托决定;
|
||||||
|
返回 None 时强制内联,不生成 $ref
|
||||||
|
*/
|
||||||
|
private func createReferencedObjectSchema(typeInfo: TypeInfo, nullable: Bool): OpenApiSchema {
|
||||||
|
let schemaId = _options.createSchemaReferenceId(typeInfo)
|
||||||
|
if (let Some(schemaId) <- schemaId) {
|
||||||
|
if (_schemas.get(schemaId).isNone()) {
|
||||||
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT)
|
||||||
|
schema.additionalProperties = false
|
||||||
|
// 先加入缓存,再处理属性,避免循环引用导致无限递归
|
||||||
|
_schemas.add(schemaId, schema)
|
||||||
|
buildObjectProperties(schema, typeInfo)
|
||||||
|
applySchemaTransformers(schema, schemaId)
|
||||||
|
}
|
||||||
|
return createReferenceSchema(schemaId, nullable)
|
||||||
|
} else {
|
||||||
|
// 强制内联:不缓存,不生成 $ref
|
||||||
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
||||||
|
buildObjectProperties(schema, typeInfo)
|
||||||
|
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createReferenceSchema(schemaId: String, nullable: Bool): OpenApiSchema {
|
||||||
|
let reference = OpenApiReference(ReferenceType.Schema, schemaId)
|
||||||
|
if (!nullable) {
|
||||||
|
return OpenApiSchema(reference: reference)
|
||||||
|
}
|
||||||
|
let schema = OpenApiSchema(nullable: true)
|
||||||
|
schema.allOf.add(OpenApiSchema(reference: reference))
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createInlineObjectSchema(typeInfo: TypeInfo, nullable: Bool): OpenApiSchema {
|
||||||
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
||||||
|
buildObjectProperties(schema, typeInfo)
|
||||||
|
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
private func buildObjectProperties(schema: OpenApiSchema, typeInfo: TypeInfo): Unit {
|
||||||
|
// prop 属性
|
||||||
|
let properties = getObjectProperties(typeInfo)
|
||||||
|
for (property in properties) {
|
||||||
|
let underlyingType = Nullable.getUnderlyingType(property.typeInfo)
|
||||||
|
let propertyTypeInfo = underlyingType ?? property.typeInfo
|
||||||
|
let propertySchema = buildSchema(propertyTypeInfo, underlyingType.isSome(), true)
|
||||||
|
for (annotation in property.annotations) {
|
||||||
|
if (let meta: IApiDescriptionMetadata <- annotation) {
|
||||||
|
propertySchema.description = meta.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schema.properties.add(property.name, propertySchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
// var 字段(DTO 常以 public var 定义,instanceProperties 不收录,需经 instanceVariables 补充)
|
||||||
|
for (variable in ReflectUtilities.collectInstanceVariables(typeInfo)) {
|
||||||
|
if (schema.properties.contains(variable.name)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let underlyingType = Nullable.getUnderlyingType(variable.typeInfo)
|
||||||
|
let variableTypeInfo = underlyingType ?? variable.typeInfo
|
||||||
|
let variableSchema = buildSchema(variableTypeInfo, underlyingType.isSome(), true)
|
||||||
|
for (annotation in variable.annotations) {
|
||||||
|
if (let meta: IApiDescriptionMetadata <- annotation) {
|
||||||
|
variableSchema.description = meta.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schema.properties.add(variable.name, variableSchema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getObjectProperties(typeInfo: TypeInfo): Array<InstancePropertyInfo> {
|
||||||
|
if (typeInfo is ClassTypeInfo) {
|
||||||
|
return ReflectUtilities.collectInstanceProperties(typeInfo)
|
||||||
|
}
|
||||||
|
return typeInfo.instanceProperties.toArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
生成数组的 Schema
|
||||||
|
*/
|
||||||
|
private func createArraySchema(elementTypeInfo: TypeInfo, preferReference: Bool): OpenApiSchema {
|
||||||
|
let itemSchema = buildSchema(elementTypeInfo, false, preferReference)
|
||||||
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.ARRAY, items: itemSchema)
|
||||||
|
// transformer 面向当前数组 schema,本处使用容器类型名最准确;现阶段退化为元素类型名。
|
||||||
|
applySchemaTransformers(schema, elementTypeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
生成字典的 Schema。
|
||||||
|
OpenAPI 3.0 的 dictionary 仅能稳定映射到 string key 的 JSON object。
|
||||||
|
*/
|
||||||
|
private func createDictionarySchema(keyTypeInfo: TypeInfo, valueTypeInfo: TypeInfo): OpenApiSchema {
|
||||||
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT)
|
||||||
|
if (keyTypeInfo != OpenApiSchemaService._stringType) {
|
||||||
|
// 非字符串 key 无法直接表达为标准 JSON object key;当前保守输出为封闭 object。
|
||||||
|
schema.additionalProperties = false
|
||||||
|
applySchemaTransformers(schema, keyTypeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
schema.additionalPropertiesSchema = buildSchema(valueTypeInfo, false, true)
|
||||||
|
applySchemaTransformers(schema, valueTypeInfo.qualifiedName)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applySchemaTransformers(schema: OpenApiSchema, schemaName: String): Unit {
|
||||||
|
if (_documentName.isEmpty() || _options.schemaTransformers.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let context = OpenApiSchemaTransformerContext(_services, _documentName, schemaName)
|
||||||
|
for (transformer in _options.schemaTransformers) {
|
||||||
|
transformer.transform(schema, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
解析 Collection<T> 中的 T
|
||||||
|
*/
|
||||||
|
private func resolveCollectionElementType(typeInfo: TypeInfo): ?TypeInfo {
|
||||||
|
for (pattern in typeInfo.superInterfaces) {
|
||||||
|
let interfaceName = pattern.qualifiedName
|
||||||
|
let baseName = TypeNameParser.getGenericBaseName(interfaceName)
|
||||||
|
if (baseName != "Collection" && baseName != "std.collection.Collection") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let genericArguments = TypeNameParser.getGenericArguments(interfaceName) ?? continue
|
||||||
|
if (genericArguments.size == 1) {
|
||||||
|
return TypeInfo.get(genericArguments[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resolveMapTypeArguments(typeInfo: TypeInfo): ?(TypeInfo, TypeInfo) {
|
||||||
|
for (pattern in typeInfo.superInterfaces) {
|
||||||
|
let interfaceName = pattern.qualifiedName
|
||||||
|
let baseName = TypeNameParser.getGenericBaseName(interfaceName)
|
||||||
|
if (!baseName.startsWith("std.collection.Map") && baseName != "Map") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let genericArguments = TypeNameParser.getGenericArguments(interfaceName) ?? continue
|
||||||
|
if (genericArguments.size == 2) {
|
||||||
|
return (TypeInfo.get(genericArguments[0]), TypeInfo.get(genericArguments[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
解析 ActionResult<T> 中的 T
|
||||||
|
*/
|
||||||
|
private func resolveSingleGenericArgumentType(typeInfo: TypeInfo): ?TypeInfo {
|
||||||
|
let genericArguments = TypeNameParser.getGenericArguments(typeInfo.qualifiedName) ?? return None
|
||||||
|
if (genericArguments.size == 1) {
|
||||||
|
return TypeInfo.get(genericArguments[0])
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func convertToSchemaFormat(typeInfo: TypeInfo): ?String {
|
||||||
|
match (typeInfo.name) {
|
||||||
|
case "Int32" => "int32"
|
||||||
|
case "Int64" => "int64"
|
||||||
|
case "DateTime" => "date-time"
|
||||||
|
case "Float32" => "float"
|
||||||
|
case "Float64" => "double"
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将委托包装为 IOpenApiDocumentTransformer 的适配器实现。
|
||||||
|
*/
|
||||||
|
protected class DelegateOpenApiDocumentTransformer <: IOpenApiDocumentTransformer {
|
||||||
|
private let _transformer: (OpenApiDocument, OpenApiDocumentTransformerContext) -> Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建委托文档转换器实例。
|
||||||
|
* @param transformer 表示文档转换逻辑的委托。
|
||||||
|
*/
|
||||||
|
public init(transformer: (OpenApiDocument, OpenApiDocumentTransformerContext) -> Unit) {
|
||||||
|
_transformer = transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 调用委托转换指定的 OpenAPI 文档。
|
||||||
|
* @param document 要转换的 OpenAPI 文档。
|
||||||
|
* @param context 文档转换器上下文。
|
||||||
|
*/
|
||||||
|
public func transform(document: OpenApiDocument, context: OpenApiDocumentTransformerContext): Unit {
|
||||||
|
_transformer(document, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
|
||||||
|
protected class DelegateOpenApiOperationTransformer <: IOpenApiOperationTransformer {
|
||||||
|
private let _transformer: (OpenApiOperation, OpenApiOperationTransformerContext) -> Unit
|
||||||
|
|
||||||
|
public init(transformer: (OpenApiOperation, OpenApiOperationTransformerContext) -> Unit) {
|
||||||
|
_transformer = transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
public func transform(operation: OpenApiOperation, context: OpenApiOperationTransformerContext): Unit {
|
||||||
|
_transformer(operation, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将委托包装为 IOpenApiSchemaTransformer 的适配器实现。
|
||||||
|
*/
|
||||||
|
protected class DelegateOpenApiSchemaTransformer <: IOpenApiSchemaTransformer {
|
||||||
|
private let _transformer: (OpenApiSchema, OpenApiSchemaTransformerContext) -> Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建委托架构转换器实例。
|
||||||
|
* @param transformer 表示架构转换逻辑的委托。
|
||||||
|
*/
|
||||||
|
public init(transformer: (OpenApiSchema, OpenApiSchemaTransformerContext) -> Unit) {
|
||||||
|
_transformer = transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 调用委托转换指定的 OpenAPI 架构。
|
||||||
|
* @param schema 要转换的 OpenAPI 架构。
|
||||||
|
* @param context 架构转换器上下文。
|
||||||
|
*/
|
||||||
|
public func transform(schema: OpenApiSchema, context: OpenApiSchemaTransformerContext): Unit {
|
||||||
|
_transformer(schema, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 定义 OpenAPI 文档转换器接口。
|
||||||
|
*/
|
||||||
|
public interface IOpenApiDocumentTransformer {
|
||||||
|
/**
|
||||||
|
* @brief 转换指定的 OpenAPI 文档。
|
||||||
|
* @param document 要转换的 OpenAPI 文档。
|
||||||
|
* @param context 文档转换器上下文。
|
||||||
|
*/
|
||||||
|
func transform(document: OpenApiDocument, context: OpenApiDocumentTransformerContext): Unit
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 定义 OpenAPI 操作转换器接口。
|
||||||
|
*/
|
||||||
|
public interface IOpenApiOperationTransformer {
|
||||||
|
/**
|
||||||
|
* @brief 转换指定的 OpenAPI 操作对象。
|
||||||
|
* @param operation 要转换的 OpenAPI 操作对象。
|
||||||
|
* @param context 操作转换器上下文。
|
||||||
|
*/
|
||||||
|
func transform(operation: OpenApiOperation, context: OpenApiOperationTransformerContext): Unit
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import simcu::simapi.openapi.models.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 定义 OpenAPI 架构转换器接口。
|
||||||
|
*/
|
||||||
|
public interface IOpenApiSchemaTransformer {
|
||||||
|
/**
|
||||||
|
* @brief 转换指定的 OpenAPI 架构。
|
||||||
|
* @param schema 要转换的 OpenAPI 架构。
|
||||||
|
* @param context 架构转换器上下文。
|
||||||
|
*/
|
||||||
|
func transform(schema: OpenApiSchema, context: OpenApiSchemaTransformerContext): Unit
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供文档转换器执行时的上下文信息。
|
||||||
|
*/
|
||||||
|
public class OpenApiDocumentTransformerContext {
|
||||||
|
/**
|
||||||
|
* @brief 当前正在生成的文档名称。
|
||||||
|
*/
|
||||||
|
public let documentName: String
|
||||||
|
/**
|
||||||
|
* @brief 请求作用域的服务提供器。
|
||||||
|
*/
|
||||||
|
public let services: IServiceProvider
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建文档转换器上下文实例。
|
||||||
|
* @param services 请求作用域的服务提供器。
|
||||||
|
* @param documentName 当前正在生成的文档名称。
|
||||||
|
*/
|
||||||
|
public init(services: IServiceProvider, documentName: String) {
|
||||||
|
this.services = services
|
||||||
|
this.documentName = documentName
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import soulsoft_web_mvc.controllers.*
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供操作转换器执行时的上下文信息。
|
||||||
|
*/
|
||||||
|
public class OpenApiOperationTransformerContext {
|
||||||
|
/**
|
||||||
|
* @brief 当前正在生成的文档名称。
|
||||||
|
*/
|
||||||
|
public let documentName: String
|
||||||
|
/**
|
||||||
|
* @brief 请求作用域的服务提供器。
|
||||||
|
*/
|
||||||
|
public let services: IServiceProvider
|
||||||
|
/**
|
||||||
|
* @brief 当前操作关联的控制器动作描述。
|
||||||
|
*/
|
||||||
|
public let description: ?ControllerActionDescriptor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建操作转换器上下文实例。
|
||||||
|
* @param services 请求作用域的服务提供器。
|
||||||
|
* @param documentName 当前正在生成的文档名称。
|
||||||
|
* @param description 当前操作关联的控制器动作描述。
|
||||||
|
*/
|
||||||
|
public init(services: IServiceProvider, documentName: String, description: ?ControllerActionDescriptor) {
|
||||||
|
this.services = services
|
||||||
|
this.documentName = documentName
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
|
* This source file is licensed under the MIT License found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package simcu::simapi.openapi.transformers
|
||||||
|
|
||||||
|
import soulsoft_extensions_injection.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提供架构转换器执行时的上下文信息。
|
||||||
|
*/
|
||||||
|
public class OpenApiSchemaTransformerContext {
|
||||||
|
/**
|
||||||
|
* @brief 当前正在生成的文档名称。
|
||||||
|
*/
|
||||||
|
public let documentName: String
|
||||||
|
/**
|
||||||
|
* @brief 请求作用域的服务提供器。
|
||||||
|
*/
|
||||||
|
public let services: IServiceProvider
|
||||||
|
/**
|
||||||
|
* @brief 当前架构在 components/schemas 中的键名(即类型限定名)。
|
||||||
|
*/
|
||||||
|
public let schemaName: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建架构转换器上下文实例。
|
||||||
|
* @param services 请求作用域的服务提供器。
|
||||||
|
* @param documentName 当前正在生成的文档名称。
|
||||||
|
* @param schemaName 当前架构在 components/schemas 中的键名。
|
||||||
|
*/
|
||||||
|
public init(services: IServiceProvider, documentName: String, schemaName: String) {
|
||||||
|
this.services = services
|
||||||
|
this.documentName = documentName
|
||||||
|
this.schemaName = schemaName
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
-52
@@ -42,6 +42,8 @@ import simcu::simapi.helpers.*
|
|||||||
import simcu::simapi.interfaces.*
|
import simcu::simapi.interfaces.*
|
||||||
import simcu::simapi.logger.*
|
import simcu::simapi.logger.*
|
||||||
import simcu::simapi.middlewares.*
|
import simcu::simapi.middlewares.*
|
||||||
|
import simcu::simapi.openapi.*
|
||||||
|
import simcu::simapi.openapi.annotations.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SimApi 扩展入口。
|
* SimApi 扩展入口。
|
||||||
@@ -160,69 +162,58 @@ public class SimApiExtensions {
|
|||||||
host.use<SimApiAuthMiddleware>()
|
host.use<SimApiAuthMiddleware>()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内置路由:RouteOptions 自定义路径时真实注册(默认路径已由内置控制器特性路由覆盖,
|
// 内置路由:全部动态注册(路径由 SimApiRouteOptions 决定,不写死在控制器特性注解上)
|
||||||
// 因 soulsoft 无约定路由 defaults,用 mapGet/mapPost 委托实现)
|
|
||||||
let routeOptions = options.simApiRouteOptions
|
let routeOptions = options.simApiRouteOptions
|
||||||
if (let Some(route) <- routeOptions.userInfoRoute) {
|
if (let Some(route) <- routeOptions.userInfoRoute) {
|
||||||
if (route != "/user/info") {
|
host.mapPost(route, { context =>
|
||||||
host.mapPost(route, { context =>
|
let controller = ActivatorUtilities.createInstance(context.services,
|
||||||
let controller = ActivatorUtilities.createInstance(context.services,
|
TypeInfo.of<SimApiCommonController>())
|
||||||
TypeInfo.of<SimApiCommonController>())
|
if (let c: SimApiBaseController <- controller) {
|
||||||
if (let c: SimApiBaseController <- controller) {
|
c.bindRequestContext(context)
|
||||||
c.bindRequestContext(context)
|
}
|
||||||
}
|
if (let c: SimApiCommonController <- controller) {
|
||||||
if (let c: SimApiCommonController <- controller) {
|
SimApiResultWriter.write(context, c.userInfo())
|
||||||
SimApiResultWriter.write(context, c.userInfo())
|
}
|
||||||
}
|
}).withOpenApi(SimApiDoc(tags: "认证", summary: "获取登录用户信息"))
|
||||||
})
|
|
||||||
}
|
|
||||||
logger.info("注册内置Route: UserInfo => ${route}")
|
logger.info("注册内置Route: UserInfo => ${route}")
|
||||||
}
|
}
|
||||||
if (let Some(route) <- routeOptions.logoutRoute) {
|
if (let Some(route) <- routeOptions.logoutRoute) {
|
||||||
if (route != "/auth/logout") {
|
host.mapPost(route, { context =>
|
||||||
host.mapPost(route, { context =>
|
let controller = ActivatorUtilities.createInstance(context.services,
|
||||||
let controller = ActivatorUtilities.createInstance(context.services,
|
TypeInfo.of<SimApiAuthController>())
|
||||||
TypeInfo.of<SimApiAuthController>())
|
if (let c: SimApiBaseController <- controller) {
|
||||||
if (let c: SimApiBaseController <- controller) {
|
c.bindRequestContext(context)
|
||||||
c.bindRequestContext(context)
|
}
|
||||||
}
|
if (let c: SimApiAuthController <- controller) {
|
||||||
if (let c: SimApiAuthController <- controller) {
|
SimApiResultWriter.write(context, c.logout())
|
||||||
SimApiResultWriter.write(context, c.logout())
|
}
|
||||||
}
|
}).withOpenApi(SimApiDoc(tags: "认证", summary: "退出登录"))
|
||||||
})
|
|
||||||
}
|
|
||||||
logger.info("注册内置Route: Logout => ${route}")
|
logger.info("注册内置Route: Logout => ${route}")
|
||||||
}
|
}
|
||||||
if (let Some(route) <- routeOptions.webConfigRoute) {
|
if (let Some(route) <- routeOptions.webConfigRoute) {
|
||||||
if (route != "/config") {
|
host.mapGet(route, { context =>
|
||||||
host.mapGet(route, { context =>
|
let controller = ActivatorUtilities.createInstance(context.services,
|
||||||
let controller = ActivatorUtilities.createInstance(context.services,
|
TypeInfo.of<SimApiCommonController>())
|
||||||
TypeInfo.of<SimApiCommonController>())
|
if (let c: SimApiBaseController <- controller) {
|
||||||
if (let c: SimApiBaseController <- controller) {
|
c.bindRequestContext(context)
|
||||||
c.bindRequestContext(context)
|
}
|
||||||
}
|
if (let c: SimApiCommonController <- controller) {
|
||||||
if (let c: SimApiCommonController <- controller) {
|
SimApiResultWriter.write(context, c.webConfig())
|
||||||
SimApiResultWriter.write(context, c.webConfig())
|
}
|
||||||
}
|
}).withOpenApi(SimApiDoc(tags: "公共", summary: "获取公共系统配置"))
|
||||||
})
|
host.mapPost(route, { context =>
|
||||||
host.mapPost(route, { context =>
|
let controller = ActivatorUtilities.createInstance(context.services,
|
||||||
let controller = ActivatorUtilities.createInstance(context.services,
|
TypeInfo.of<SimApiCommonController>())
|
||||||
TypeInfo.of<SimApiCommonController>())
|
if (let c: SimApiBaseController <- controller) {
|
||||||
if (let c: SimApiBaseController <- controller) {
|
c.bindRequestContext(context)
|
||||||
c.bindRequestContext(context)
|
}
|
||||||
}
|
if (let c: SimApiCommonController <- controller) {
|
||||||
if (let c: SimApiCommonController <- controller) {
|
SimApiResultWriter.write(context, c.webConfig())
|
||||||
SimApiResultWriter.write(context, c.webConfigPost())
|
}
|
||||||
}
|
}).withOpenApi(SimApiDoc(tags: "公共", summary: "获取公共系统配置"))
|
||||||
})
|
|
||||||
}
|
|
||||||
logger.info("注册内置Route: WebConfig => ${route}")
|
logger.info("注册内置Route: WebConfig => ${route}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimApiDoc(占位:soulsoft 暂无内置 Swagger 文档页)
|
|
||||||
if (options.enableSimApiDoc) {
|
|
||||||
logger.info("开始配置 SimApiDoc...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 请求日志中间件
|
// 请求日志中间件
|
||||||
if (options.enableRequestLog) {
|
if (options.enableRequestLog) {
|
||||||
@@ -257,6 +248,13 @@ public class SimApiExtensions {
|
|||||||
if (callSiteFactory.isService<ApplicationPartManager>()) {
|
if (callSiteFactory.isService<ApplicationPartManager>()) {
|
||||||
host.mapControllers()
|
host.mapControllers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SimApiDoc(OpenAPI 文档 JSON 路由 + Swagger UI 静态资源,最内层挂载)
|
||||||
|
if (options.enableSimApiDoc) {
|
||||||
|
logger.info("开始配置 SimApiDoc...")
|
||||||
|
host.mapOpenApi()
|
||||||
|
host.useOpenApiUI()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 私有辅助 =====
|
// ===== 私有辅助 =====
|
||||||
@@ -279,6 +277,11 @@ public class SimApiExtensions {
|
|||||||
// 中间件无需注册:挂载时由 ActivatorUtilities 从 DI 解析构造参数创建
|
// 中间件无需注册:挂载时由 ActivatorUtilities 从 DI 解析构造参数创建
|
||||||
|
|
||||||
|
|
||||||
|
// API 文档(OpenAPI)
|
||||||
|
if (options.enableSimApiDoc) {
|
||||||
|
builder.services.addOpenApi()
|
||||||
|
}
|
||||||
|
|
||||||
// 认证(DI 自动注入 SimApiOptions)
|
// 认证(DI 自动注入 SimApiOptions)
|
||||||
if (options.enableSimApiAuth) {
|
if (options.enableSimApiAuth) {
|
||||||
builder.services.addSingleton<SimApiAuth, SimApiAuth>()
|
builder.services.addSingleton<SimApiAuth, SimApiAuth>()
|
||||||
|
|||||||
Reference in New Issue
Block a user