精简了openapi相关
This commit is contained in:
@@ -3,18 +3,18 @@ version = 0
|
||||
[requires]
|
||||
soulsoft_extensions_hosting = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_options_configuration = {version = "1.0.20260528"}
|
||||
soulsoft_web_mvc = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
|
||||
soulsoft_web_http = {version = "1.0.20260528"}
|
||||
redis = {version = "1.0.20260627"}
|
||||
soulsoft_web_cors = {version = "1.0.20260528"}
|
||||
"simcu::serialization" = {version = "1.2.1"}
|
||||
soulsoft_web_routing = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_logging = {version = "1.0.20260528"}
|
||||
soulsoft_serialization = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_options = {version = "1.0.20260528"}
|
||||
soulsoft_web_routing = {version = "1.0.20260528"}
|
||||
soulsoft_identity_claims = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_logging_configuration = {version = "1.0.20260528"}
|
||||
soulsoft_web_http = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_configuration = {version = "1.0.20260528"}
|
||||
soulsoft_web_cors = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_injection = {version = "1.0.20260528"}
|
||||
soulsoft_web_hosting = {version = "1.0.20260528"}
|
||||
soulsoft_web_mvc = {version = "1.0.20260528"}
|
||||
redis = {version = "1.0.20260627"}
|
||||
"simcu::serialization" = {version = "1.2.1"}
|
||||
soulsoft_extensions_configuration = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_injection = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_options = {version = "1.0.20260528"}
|
||||
soulsoft_identity_claims = {version = "1.0.20260528"}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.annotations
|
||||
|
||||
/**
|
||||
* @brief 为控制器或动作提供 API 文档元数据(对齐 C# [SimApiDoc] 特性)。
|
||||
*
|
||||
* 用法:
|
||||
* @SimApiDoc[tags: "登录", summary: "用户登录相关接口"]
|
||||
* @SimApiDoc[tags: "认证", summary: "后台登录", groupNames: "admin"]
|
||||
* @SimApiDoc[tags: "公共", groupNames: "api,admin"] // 同时出现在 api 和 admin 文档
|
||||
* @SimApiDoc[tags: "公共", groupNames: "*"] // 出现在所有文档
|
||||
*/
|
||||
@Annotation[target: [MemberFunction, Type, MemberProperty, MemberVariable, Parameter]]
|
||||
public class SimApiDoc {
|
||||
/**
|
||||
* @brief API 名称。
|
||||
*/
|
||||
public let name: ?String
|
||||
/**
|
||||
* @brief 当前 API 是否应被忽略。
|
||||
*/
|
||||
public let ignore: Bool
|
||||
/**
|
||||
* @brief API 摘要信息。
|
||||
*/
|
||||
public let summary: ?String
|
||||
/**
|
||||
* @brief API 分组名称(逗号分隔,如 "api,admin");"*" 表示出现在所有文档;空串表示未分组(仅进默认文档)。
|
||||
*/
|
||||
public let groupNames: String
|
||||
/**
|
||||
* @brief API 描述信息。
|
||||
*/
|
||||
public let description: ?String
|
||||
/**
|
||||
* @brief API 标签字符串。
|
||||
*/
|
||||
public let tags: ?String
|
||||
|
||||
/**
|
||||
* @brief 创建 SimApiDoc 注解实例。
|
||||
* @param name API 名称。
|
||||
* @param ignore 是否忽略当前 API。
|
||||
* @param summary API 摘要。
|
||||
* @param groupNames API 分组名称(逗号分隔,如 "api,admin");"*" 表示出现在所有文档;空串表示未分组(仅进默认文档)。
|
||||
* @param description API 描述。
|
||||
* @param tags API 标签字符串。
|
||||
*/
|
||||
public const init(name!: ?String = None, ignore!: Bool = false, summary!: ?String = None, groupNames!: String = "",
|
||||
description!: ?String = None, tags!: ?String = None) {
|
||||
this.tags = tags
|
||||
this.name = name
|
||||
this.ignore = ignore
|
||||
this.summary = summary
|
||||
this.groupNames = groupNames
|
||||
this.description = description
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,7 @@ import soulsoft_web_mvc.annotations.*
|
||||
import simcu::simapi.communications.*
|
||||
import simcu::simapi.configurations.*
|
||||
import simcu::simapi.helpers.*
|
||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAttribute}
|
||||
import simcu::simapi.openapi.annotations.*
|
||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAttribute, SimApiDoc}
|
||||
|
||||
/**
|
||||
* 通用控制器:错误反馈、WebConfig、用户信息。
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* 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.*
|
||||
@@ -1,38 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// 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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// 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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimCuTeam. All rights reserved.
|
||||
* 遵循 MIT 许可证。
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import soulsoft_web_http.*
|
||||
import simcu::simapi.annotations.*
|
||||
import simcu::simapi.openapi.metadata.*
|
||||
|
||||
/**
|
||||
* @brief 响应类型元数据实现。
|
||||
*/
|
||||
public class ApiResponseTypeMetadata <: IApiResponseTypeMetadata {
|
||||
private let _responseType: ?TypeInfo
|
||||
public init(responseType: ?TypeInfo) {
|
||||
_responseType = responseType
|
||||
}
|
||||
public prop responseType: ?TypeInfo {
|
||||
get() {
|
||||
_responseType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 为动态注册的路由提供认证和响应类型元数据扩展。
|
||||
*/
|
||||
public interface SimApiEndpointMetadataExtensions {
|
||||
func withSimApiAuth(auth: SimApiAuth): EndpointConventionBuilder
|
||||
func withResponseType(typeInfo: TypeInfo): EndpointConventionBuilder
|
||||
}
|
||||
|
||||
extend EndpointConventionBuilder <: SimApiEndpointMetadataExtensions {
|
||||
/**
|
||||
* @brief 为端点附加 SimApiAuth 认证元数据(使 OpenAPI 文档显示锁图标)。
|
||||
*/
|
||||
public func withSimApiAuth(auth: SimApiAuth): EndpointConventionBuilder {
|
||||
this.add {
|
||||
builder => builder.metadata.add(auth)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 为端点附加响应类型元数据(使动态路由也能生成 response schema)。
|
||||
*/
|
||||
public func withResponseType(typeInfo: TypeInfo): EndpointConventionBuilder {
|
||||
this.add {
|
||||
builder => builder.metadata.add(ApiResponseTypeMetadata(Some(typeInfo)))
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* 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: "用户登录相关接口"]
|
||||
* @SimApiDoc[tags: "认证", summary: "后台登录", groupNames: "admin"]
|
||||
* @SimApiDoc[tags: "公共", groupNames: "api,admin"] // 同时出现在 api 和 admin 文档
|
||||
* @SimApiDoc[tags: "公共", groupNames: "*"] // 出现在所有文档
|
||||
*/
|
||||
@Annotation[target: [MemberFunction, Type, MemberProperty, MemberVariable, Parameter]]
|
||||
public class SimApiDoc <: IApiTagsMetadata & IApiNameMetadata & IApiGroupNamesProvider & IApiDescriptionMetadata & IApiSummaryMetadata & IApiVisibilityProvider {
|
||||
private let _ignore: Bool
|
||||
private let _name: ?String
|
||||
private let _tags: ?String
|
||||
private let _summary: ?String
|
||||
private let _groupNames: String
|
||||
private let _description: ?String
|
||||
|
||||
/**
|
||||
* @brief 创建 SimApiDoc 注解实例。
|
||||
* @param name API 名称。
|
||||
* @param ignore 是否忽略当前 API。
|
||||
* @param summary API 摘要。
|
||||
* @param groupNames API 分组名称(逗号分隔,如 "api,admin");"*" 表示出现在所有文档;空串表示未分组(仅进默认文档)。
|
||||
* @param description API 描述。
|
||||
* @param tags API 标签字符串。
|
||||
*/
|
||||
public const init(name!: ?String = None, ignore!: Bool = false, summary!: ?String = None,
|
||||
groupNames!: String = "", description!: ?String = None, tags!: ?String = None) {
|
||||
_tags = tags
|
||||
_name = name
|
||||
_ignore = ignore
|
||||
_summary = summary
|
||||
_groupNames = groupNames
|
||||
_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 逗号分隔的分组名称字符串,空串表示未分组。
|
||||
*/
|
||||
public prop groupNames: String {
|
||||
get() {
|
||||
_groupNames
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 返回 API 描述信息。
|
||||
* @return 当前 API 的描述信息。
|
||||
*/
|
||||
public prop description: ?String {
|
||||
get() {
|
||||
_description
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* 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: String = "default"
|
||||
public static let DefaultDocumentName: String = "v1"
|
||||
public static let DefaultOpenApiRoute: String = "/openapi/{documentName}.json"
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.reflect.*
|
||||
import std.collection.*
|
||||
import soulsoft_web_http.*
|
||||
import soulsoft_web_routing.*
|
||||
import soulsoft_extensions_options.*
|
||||
import soulsoft_extensions_injection.*
|
||||
import simcu::simapi.annotations.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI 端点约定扩展。
|
||||
*/
|
||||
public interface OpenApiEndpointConventionBuilderExtensions {
|
||||
/**
|
||||
* @brief 为端点附加 OpenAPI 元数据。
|
||||
* @param metadata 要附加的 OpenAPI 元数据。
|
||||
* @return 当前端点约定构建器实例。
|
||||
*/
|
||||
func withOpenApi(metadata: SimApiDoc): EndpointConventionBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI 文档路由映射扩展。
|
||||
*/
|
||||
public interface OpenApiEndpointRouteBuilderExtension {
|
||||
/**
|
||||
* @brief 映射指定模式的 OpenAPI 文档路由。
|
||||
* @param pattern OpenAPI 文档路由模式。
|
||||
* @return 当前端点路由构建器实例。
|
||||
*/
|
||||
func mapOpenApi(pattern: String): EndpointRouteBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI 服务注册扩展。
|
||||
*/
|
||||
public interface OpenApiServiceCollectionExtensions {
|
||||
/**
|
||||
* @brief 注册默认文档名称的 OpenAPI 服务并配置选项。
|
||||
* @param configureOptions 用于配置 OpenAPI 选项的回调。
|
||||
* @return 当前服务集合实例。
|
||||
*/
|
||||
func addOpenApi(configureOptions: (OpenApiOptions) -> Unit): ServiceCollection {
|
||||
addOpenApi("v1", configureOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册指定文档名称的 OpenAPI 服务并配置选项。
|
||||
* @param documentName OpenAPI 文档名称。
|
||||
* @param configureOptions 用于配置 OpenAPI 选项的回调。
|
||||
* @return 当前服务集合实例。
|
||||
*/
|
||||
func addOpenApi(documentName: String, configureOptions: (OpenApiOptions) -> Unit): ServiceCollection
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI UI 中间件扩展。
|
||||
*/
|
||||
public interface OpenApiUIMiddlewareExtensions {
|
||||
/**
|
||||
* @brief 启用 OpenAPI UI 中间件。
|
||||
*/
|
||||
func useOpenApiUI(): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 为动态注册的路由提供认证和响应类型元数据扩展。
|
||||
*/
|
||||
public interface SimApiEndpointMetadataExtensions {
|
||||
func withSimApiAuth(auth: SimApiAuth): EndpointConventionBuilder
|
||||
func withResponseType(typeInfo: TypeInfo): EndpointConventionBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 文档提供器接口。
|
||||
*/
|
||||
public interface IDocumentProvider {
|
||||
/**
|
||||
* @brief 返回已注册的文档名称集合。
|
||||
* @return 当前可用的文档名称集合。
|
||||
*/
|
||||
func getDooucmentNames(): Collection<String>
|
||||
/**
|
||||
* @brief 按名称创建 OpenAPI 文档。
|
||||
* @param documentName 要创建的文档名称。
|
||||
* @return 生成后的 OpenAPI 文档。
|
||||
*/
|
||||
func create(documentName: String, services: IServiceProvider): OpenApiDocument
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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 多分组名称元数据(逗号分隔,如 "api,admin")。
|
||||
*/
|
||||
public interface IApiGroupNamesProvider {
|
||||
/**
|
||||
* @brief 返回 API 所属的多个分组名称(逗号分隔)。
|
||||
* @return 逗号分隔的分组名称字符串,空串表示未指定。
|
||||
*/
|
||||
prop groupNames: String
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimCuTeam. All rights reserved.
|
||||
* 遵循 MIT 许可证。
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.metadata
|
||||
|
||||
import std.reflect.*
|
||||
|
||||
/**
|
||||
* @brief 提供 API 响应类型元数据(供动态注册的路由指定返回类型)。
|
||||
*/
|
||||
public interface IApiResponseTypeMetadata {
|
||||
/**
|
||||
* @brief 返回 API 响应的类型信息。
|
||||
*/
|
||||
prop responseType: ?TypeInfo
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* 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.*
|
||||
@@ -1,13 +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
|
||||
|
||||
package simcu::simapi.openapi.models
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 对象序列化接口。
|
||||
*/
|
||||
public interface IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 按 OpenAPI V3 格式写出当前对象。
|
||||
* @param writer OpenAPI 写入器。
|
||||
*/
|
||||
func serializeAsV3(writer: IOpenApiWriter): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 文档写入接口。
|
||||
+5
-11
@@ -7,13 +7,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.collection.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
import soulsoft_extensions_options.*
|
||||
import soulsoft_extensions_injection.*
|
||||
import simcu::simapi.openapi.transformers.*
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI 文档创建能力。
|
||||
@@ -42,10 +41,10 @@ protected class OpenApiDocumentProvider <: IDocumentProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名称创建 OpenAPI 文档并依次应用所有转换器。
|
||||
* @brief 按名称创建 OpenAPI 文档。
|
||||
* @param documentName 要创建的文档名称。
|
||||
* @param services 请求作用域的服务提供器,传递给操作转换器上下文。
|
||||
* @return 经过转换器处理后的 OpenAPI 文档。
|
||||
* @param services 请求作用域的服务提供器。
|
||||
* @return OpenAPI 文档。
|
||||
*/
|
||||
public func create(documentName: String, services: IServiceProvider): OpenApiDocument {
|
||||
let options = _options.get(documentName)
|
||||
@@ -57,11 +56,6 @@ protected class OpenApiDocumentProvider <: IDocumentProvider {
|
||||
schemaService,
|
||||
parameterService
|
||||
)
|
||||
let document = provider.getOpenApiDocument(services)
|
||||
let context = OpenApiDocumentTransformerContext(services, documentName)
|
||||
for (transformer in options.documentTransformers) {
|
||||
transformer.transform(document, context)
|
||||
}
|
||||
return document
|
||||
return provider.getOpenApiDocument(services)
|
||||
}
|
||||
}
|
||||
+10
-39
@@ -7,7 +7,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
@@ -20,10 +20,7 @@ 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.*
|
||||
import simcu::simapi.configurations.*
|
||||
import simcu::simapi.annotations.*
|
||||
|
||||
@@ -105,7 +102,6 @@ protected class OpenApiDocumentService {
|
||||
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, openApiOptions.includeUnGrouped)) {
|
||||
continue
|
||||
@@ -132,10 +128,6 @@ protected class OpenApiDocumentService {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -144,7 +136,7 @@ protected class OpenApiDocumentService {
|
||||
}
|
||||
|
||||
private func isShouldInclude(endpoint: RouteEndpoint, includeUnGrouped: Bool) {
|
||||
let rawGroupNames = endpoint.metadata.getOrderedMetadata<IApiGroupNamesProvider>() |>
|
||||
let rawGroupNames = endpoint.metadata.getOrderedMetadata<SimApiDoc>() |>
|
||||
filterMap { f => f.groupNames } |> collectArray
|
||||
if (rawGroupNames.isEmpty()) {
|
||||
// 未标注 groupNames 的接口:仅进入默认文档(includeUnGrouped=true 的文档)
|
||||
@@ -204,7 +196,7 @@ protected class OpenApiDocumentService {
|
||||
operation.response = createOpenApiOperationResponses(endpoint, actionDescriptor)
|
||||
|
||||
// summary
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiSummaryMetadata> {f => f.summary.isSome()}) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.summary.isSome()}) {
|
||||
if (let Some(summary) <- metadata.summary) {
|
||||
operation.summary = summary
|
||||
}
|
||||
@@ -213,14 +205,14 @@ protected class OpenApiDocumentService {
|
||||
// description
|
||||
if (let Some(metadata) <- endpoint
|
||||
.metadata
|
||||
.getLastMetadata<IApiDescriptionMetadata> {f => f.description.isSome()}) {
|
||||
.getLastMetadata<SimApiDoc> {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()}) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.name.isSome()}) {
|
||||
operation.operationId = metadata.name
|
||||
}
|
||||
|
||||
@@ -237,7 +229,7 @@ protected class OpenApiDocumentService {
|
||||
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(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.tags.isSome()}) {
|
||||
if (let Some(tags) <- metadata.tags) {
|
||||
for (name in tags.split(',')) {
|
||||
result.add(OpenApiTag(name: name))
|
||||
@@ -251,7 +243,7 @@ protected class OpenApiDocumentService {
|
||||
if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||
result.add(OpenApiTag(name: actionDescriptor.controllerName))
|
||||
} else {
|
||||
result.add(OpenApiTag(name: OpenApiConstants.DefaultOpenApiName))
|
||||
result.add(OpenApiTag(name: "default"))
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -299,12 +291,12 @@ protected class OpenApiDocumentService {
|
||||
private func createOpenApiOperationResponses(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
|
||||
let responses = OpenApiResponses()
|
||||
|
||||
// 获取action的返回类型:优先从 actionDescriptor,其次从 IApiResponseTypeMetadata(动态路由)
|
||||
// 获取action的返回类型:优先从 actionDescriptor,其次从 ApiResponseTypeMetadata(动态路由)
|
||||
let returnType: ?TypeInfo = if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||
Nullable.getUnderlyingType(actionDescriptor.actionFunction.returnType) ?? actionDescriptor
|
||||
.actionFunction
|
||||
.returnType
|
||||
} else if (let Some(meta) <- endpoint.metadata.getMetadata<IApiResponseTypeMetadata>()) {
|
||||
} else if (let Some(meta) <- endpoint.metadata.getMetadata<ApiResponseTypeMetadata>()) {
|
||||
meta.responseType
|
||||
} else {
|
||||
None
|
||||
@@ -357,30 +349,9 @@ protected class OpenApiDocumentService {
|
||||
|
||||
// api ignore
|
||||
private func isIgnore(endpoint: RouteEndpoint) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiVisibilityProvider> {f => f.ignore}) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {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,152 @@
|
||||
/*
|
||||
* 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.reflect.*
|
||||
import soulsoft_web_http.*
|
||||
import soulsoft_web_routing.*
|
||||
import soulsoft_extensions_options.*
|
||||
import soulsoft_extensions_injection.*
|
||||
import simcu::simapi.annotations.*
|
||||
|
||||
/**
|
||||
* @brief 为端点附加 OpenAPI 元数据。
|
||||
*/
|
||||
extend EndpointConventionBuilder <: OpenApiEndpointConventionBuilderExtensions {
|
||||
/**
|
||||
* @brief 为端点附加 API 文档元数据。
|
||||
* @param metadata 要附加的 API 文档元数据。
|
||||
* @return 当前端点约定构建器实例。
|
||||
*/
|
||||
public func withOpenApi(metadata: SimApiDoc): EndpointConventionBuilder {
|
||||
this.add {
|
||||
builder => builder.metadata.add(metadata)
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 映射默认 OpenAPI 文档路由。
|
||||
*/
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册默认名称的 OpenAPI 服务。
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用 OpenAPI UI 中间件。
|
||||
*/
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 为端点附加认证与响应类型元数据。
|
||||
*/
|
||||
extend EndpointConventionBuilder <: SimApiEndpointMetadataExtensions {
|
||||
/**
|
||||
* @brief 为端点附加 SimApiAuth 认证元数据(使 OpenAPI 文档显示锁图标)。
|
||||
*/
|
||||
public func withSimApiAuth(auth: SimApiAuth): EndpointConventionBuilder {
|
||||
this.add {
|
||||
builder => builder.metadata.add(auth)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 为端点附加响应类型元数据(使动态路由也能生成 response schema)。
|
||||
*/
|
||||
public func withResponseType(typeInfo: TypeInfo): EndpointConventionBuilder {
|
||||
this.add {
|
||||
builder => builder.metadata.add(ApiResponseTypeMetadata(Some(typeInfo)))
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提供元数据集合扩展。
|
||||
*/
|
||||
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,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
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 文档生成选项。
|
||||
*/
|
||||
public class OpenApiOptions {
|
||||
/**
|
||||
* 未标注 groupName 的接口是否进入本文档。
|
||||
* 多文档分组时,仅默认组(第一个 apiGroup 或 isDefault 的组)应设为 true。
|
||||
*/
|
||||
public var includeUnGrouped: Bool = false
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -4,7 +4,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
@@ -12,7 +12,6 @@ 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 参数生成能力。
|
||||
+4
-27
@@ -7,7 +7,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.time.*
|
||||
import std.reflect.*
|
||||
@@ -17,10 +17,8 @@ import stdx.encoding.json.*
|
||||
import soulsoft_web_http.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
import soulsoft_web_mvc.IActionResult
|
||||
import simcu::simapi.openapi.metadata.*
|
||||
import simcu::simapi.annotations.*
|
||||
import soulsoft_extensions_injection.*
|
||||
import simcu::simapi.openapi.transformers.*
|
||||
import simcu::simapi.openapi.infrastructure.*
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI Schema 生成功能。
|
||||
@@ -105,21 +103,18 @@ protected class OpenApiSchemaService {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -142,7 +137,6 @@ protected class OpenApiSchemaService {
|
||||
|
||||
if (let _: EnumTypeInfo <- typeInfo) {
|
||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, nullable: nullable)
|
||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
|
||||
@@ -170,14 +164,12 @@ protected class OpenApiSchemaService {
|
||||
// 先加入缓存,再处理属性,避免循环引用导致无限递归
|
||||
_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
|
||||
}
|
||||
}
|
||||
@@ -195,7 +187,6 @@ protected class OpenApiSchemaService {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -207,7 +198,7 @@ protected class OpenApiSchemaService {
|
||||
let propertyTypeInfo = underlyingType ?? property.typeInfo
|
||||
let propertySchema = buildSchema(propertyTypeInfo, underlyingType.isSome(), true)
|
||||
for (annotation in property.annotations) {
|
||||
if (let meta: IApiDescriptionMetadata <- annotation) {
|
||||
if (let meta: SimApiDoc <- annotation) {
|
||||
propertySchema.description = meta.description
|
||||
}
|
||||
}
|
||||
@@ -223,7 +214,7 @@ protected class OpenApiSchemaService {
|
||||
let variableTypeInfo = underlyingType ?? variable.typeInfo
|
||||
let variableSchema = buildSchema(variableTypeInfo, underlyingType.isSome(), true)
|
||||
for (annotation in variable.annotations) {
|
||||
if (let meta: IApiDescriptionMetadata <- annotation) {
|
||||
if (let meta: SimApiDoc <- annotation) {
|
||||
variableSchema.description = meta.description
|
||||
}
|
||||
}
|
||||
@@ -244,8 +235,6 @@ protected class OpenApiSchemaService {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -258,24 +247,12 @@ protected class OpenApiSchemaService {
|
||||
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
|
||||
*/
|
||||
@@ -1,17 +1,32 @@
|
||||
// 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
|
||||
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.io.*
|
||||
import std.reflect.*
|
||||
import stdx.encoding.json.stream.*
|
||||
import simcu::simapi.openapi.interfaces.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 响应类型元数据(供动态注册的路由指定返回类型)。
|
||||
*/
|
||||
public class ApiResponseTypeMetadata {
|
||||
/**
|
||||
* @brief 响应类型的类型信息。
|
||||
*/
|
||||
public let responseType: ?TypeInfo
|
||||
/**
|
||||
* @brief 创建响应类型元数据实例。
|
||||
* @param responseType 响应类型的类型信息。
|
||||
*/
|
||||
public init(responseType: ?TypeInfo) {
|
||||
this.responseType = responseType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提供基于 JSON 的 OpenAPI 写入器实现。
|
||||
@@ -94,3 +109,40 @@ public class JsonOpenApiWriter <: IOpenApiWriter {
|
||||
_writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 表示带名称的服务标识。
|
||||
*/
|
||||
public class NamedService {
|
||||
/**
|
||||
* @brief 表示服务名称。
|
||||
*/
|
||||
public let name: String
|
||||
|
||||
/**
|
||||
* @brief 创建带名称的服务标识实例。
|
||||
* @param name 服务名称。
|
||||
*/
|
||||
public init(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.infrastructure
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
@@ -1,30 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
// 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>()
|
||||
|
||||
/**
|
||||
* 未标注 groupName 的接口是否进入本文档。
|
||||
* 多文档分组时,仅默认组(第一个 apiGroup 或 isDefault 的组)应设为 true。
|
||||
*/
|
||||
public var includeUnGrouped: Bool = false
|
||||
|
||||
/**
|
||||
* @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))
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.infrastructure
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.collection.*
|
||||
|
||||
@@ -44,8 +44,7 @@ import simcu::simapi.interfaces.*
|
||||
import simcu::simapi.logger.*
|
||||
import simcu::simapi.middlewares.*
|
||||
import simcu::simapi.openapi.*
|
||||
import simcu::simapi.openapi.annotations.*
|
||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAnnotation}
|
||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAnnotation, SimApiDoc}
|
||||
|
||||
/**
|
||||
* SimApi 扩展入口。
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# 生成 SwaggerUIResources.cj:将 resources/openapi/ 下静态资源以 Base64 内联为 CJ 源常量。
|
||||
# 生成 swagger_ui_resources.cj:将 resources/openapi/ 下静态资源以 Base64 内联为 CJ 源常量。
|
||||
# 用法:pwsh tools/gen-swagger-ui-resources.ps1
|
||||
# 资源更新后重新运行此脚本即可。
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$inDir = Join-Path $root "resources\openapi"
|
||||
$outFile = Join-Path $root "src\openapi\SwaggerUIResources.cj"
|
||||
$outFile = Join-Path $root "src\openapi\swagger_ui_resources.cj"
|
||||
|
||||
$files = @(
|
||||
@{ Name = "allHtml"; File = "all.html" },
|
||||
|
||||
Reference in New Issue
Block a user