精简了openapi相关
This commit is contained in:
@@ -3,18 +3,18 @@ version = 0
|
|||||||
[requires]
|
[requires]
|
||||||
soulsoft_extensions_hosting = {version = "1.0.20260528"}
|
soulsoft_extensions_hosting = {version = "1.0.20260528"}
|
||||||
soulsoft_extensions_options_configuration = {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_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_extensions_logging = {version = "1.0.20260528"}
|
||||||
soulsoft_serialization = {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_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_hosting = {version = "1.0.20260528"}
|
||||||
soulsoft_web_mvc = {version = "1.0.20260528"}
|
soulsoft_extensions_configuration = {version = "1.0.20260528"}
|
||||||
redis = {version = "1.0.20260627"}
|
soulsoft_extensions_injection = {version = "1.0.20260528"}
|
||||||
"simcu::serialization" = {version = "1.2.1"}
|
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.communications.*
|
||||||
import simcu::simapi.configurations.*
|
import simcu::simapi.configurations.*
|
||||||
import simcu::simapi.helpers.*
|
import simcu::simapi.helpers.*
|
||||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAttribute}
|
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAttribute, SimApiDoc}
|
||||||
import simcu::simapi.openapi.annotations.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用控制器:错误反馈、WebConfig、用户信息。
|
* 通用控制器:错误反馈、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.
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
* This source file is licensed under the MIT License found in the
|
* This source file is licensed under the MIT License found in the
|
||||||
* LICENSE file in the root directory of this source tree.
|
* 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 文档写入接口。
|
* @brief 定义 OpenAPI 文档写入接口。
|
||||||
+5
-11
@@ -7,13 +7,12 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::simapi.openapi.services
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
import std.collection.*
|
import std.collection.*
|
||||||
import simcu::simapi.openapi.models.*
|
import simcu::simapi.openapi.models.*
|
||||||
import soulsoft_extensions_options.*
|
import soulsoft_extensions_options.*
|
||||||
import soulsoft_extensions_injection.*
|
import soulsoft_extensions_injection.*
|
||||||
import simcu::simapi.openapi.transformers.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 提供 OpenAPI 文档创建能力。
|
* @brief 提供 OpenAPI 文档创建能力。
|
||||||
@@ -42,10 +41,10 @@ protected class OpenApiDocumentProvider <: IDocumentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 按名称创建 OpenAPI 文档并依次应用所有转换器。
|
* @brief 按名称创建 OpenAPI 文档。
|
||||||
* @param documentName 要创建的文档名称。
|
* @param documentName 要创建的文档名称。
|
||||||
* @param services 请求作用域的服务提供器,传递给操作转换器上下文。
|
* @param services 请求作用域的服务提供器。
|
||||||
* @return 经过转换器处理后的 OpenAPI 文档。
|
* @return OpenAPI 文档。
|
||||||
*/
|
*/
|
||||||
public func create(documentName: String, services: IServiceProvider): OpenApiDocument {
|
public func create(documentName: String, services: IServiceProvider): OpenApiDocument {
|
||||||
let options = _options.get(documentName)
|
let options = _options.get(documentName)
|
||||||
@@ -57,11 +56,6 @@ protected class OpenApiDocumentProvider <: IDocumentProvider {
|
|||||||
schemaService,
|
schemaService,
|
||||||
parameterService
|
parameterService
|
||||||
)
|
)
|
||||||
let document = provider.getOpenApiDocument(services)
|
return provider.getOpenApiDocument(services)
|
||||||
let context = OpenApiDocumentTransformerContext(services, documentName)
|
|
||||||
for (transformer in options.documentTransformers) {
|
|
||||||
transformer.transform(document, context)
|
|
||||||
}
|
|
||||||
return document
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10
-39
@@ -7,7 +7,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::simapi.openapi.services
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
import std.reflect.*
|
import std.reflect.*
|
||||||
import std.collection.*
|
import std.collection.*
|
||||||
@@ -20,10 +20,7 @@ import simcu::simapi.openapi.models.*
|
|||||||
import soulsoft_extensions_options.*
|
import soulsoft_extensions_options.*
|
||||||
import soulsoft_web_mvc.controllers.*
|
import soulsoft_web_mvc.controllers.*
|
||||||
import soulsoft_extensions_injection.*
|
import soulsoft_extensions_injection.*
|
||||||
import simcu::simapi.openapi.metadata.*
|
|
||||||
import soulsoft_web_mvc.abstractions.*
|
import soulsoft_web_mvc.abstractions.*
|
||||||
import simcu::simapi.openapi.transformers.*
|
|
||||||
import simcu::simapi.openapi.infrastructure.*
|
|
||||||
import simcu::simapi.configurations.*
|
import simcu::simapi.configurations.*
|
||||||
import simcu::simapi.annotations.*
|
import simcu::simapi.annotations.*
|
||||||
|
|
||||||
@@ -105,7 +102,6 @@ protected class OpenApiDocumentService {
|
|||||||
private func createOpenApiPaths(services: IServiceProvider) {
|
private func createOpenApiPaths(services: IServiceProvider) {
|
||||||
let paths = OpenApiPaths()
|
let paths = OpenApiPaths()
|
||||||
let openApiOptions = services.getOrThrow<IOptionsMonitor<OpenApiOptions>>().get(_documentName)
|
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)) {
|
for (endpoint in _endpointSource.endpoints |> filterMap {f => f as RouteEndpoint} where !isIgnore(endpoint)) {
|
||||||
if (!isShouldInclude(endpoint, openApiOptions.includeUnGrouped)) {
|
if (!isShouldInclude(endpoint, openApiOptions.includeUnGrouped)) {
|
||||||
continue
|
continue
|
||||||
@@ -132,10 +128,6 @@ protected class OpenApiDocumentService {
|
|||||||
let operationType = OperationType.parse(httpMethod)
|
let operationType = OperationType.parse(httpMethod)
|
||||||
let actionDescriptor = endpoint.metadata.getMetadata<ControllerActionDescriptor>()
|
let actionDescriptor = endpoint.metadata.getMetadata<ControllerActionDescriptor>()
|
||||||
let operation = createOpenApiOperation(endpoint, actionDescriptor)
|
let operation = createOpenApiOperation(endpoint, actionDescriptor)
|
||||||
let context = OpenApiOperationTransformerContext(services, _documentName, actionDescriptor)
|
|
||||||
for (transformer in operationTransformers) {
|
|
||||||
transformer.transform(operation, context)
|
|
||||||
}
|
|
||||||
pathItem.operations.add(operationType, operation)
|
pathItem.operations.add(operationType, operation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +136,7 @@ protected class OpenApiDocumentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func isShouldInclude(endpoint: RouteEndpoint, includeUnGrouped: Bool) {
|
private func isShouldInclude(endpoint: RouteEndpoint, includeUnGrouped: Bool) {
|
||||||
let rawGroupNames = endpoint.metadata.getOrderedMetadata<IApiGroupNamesProvider>() |>
|
let rawGroupNames = endpoint.metadata.getOrderedMetadata<SimApiDoc>() |>
|
||||||
filterMap { f => f.groupNames } |> collectArray
|
filterMap { f => f.groupNames } |> collectArray
|
||||||
if (rawGroupNames.isEmpty()) {
|
if (rawGroupNames.isEmpty()) {
|
||||||
// 未标注 groupNames 的接口:仅进入默认文档(includeUnGrouped=true 的文档)
|
// 未标注 groupNames 的接口:仅进入默认文档(includeUnGrouped=true 的文档)
|
||||||
@@ -204,7 +196,7 @@ protected class OpenApiDocumentService {
|
|||||||
operation.response = createOpenApiOperationResponses(endpoint, actionDescriptor)
|
operation.response = createOpenApiOperationResponses(endpoint, actionDescriptor)
|
||||||
|
|
||||||
// summary
|
// 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) {
|
if (let Some(summary) <- metadata.summary) {
|
||||||
operation.summary = summary
|
operation.summary = summary
|
||||||
}
|
}
|
||||||
@@ -213,14 +205,14 @@ protected class OpenApiDocumentService {
|
|||||||
// description
|
// description
|
||||||
if (let Some(metadata) <- endpoint
|
if (let Some(metadata) <- endpoint
|
||||||
.metadata
|
.metadata
|
||||||
.getLastMetadata<IApiDescriptionMetadata> {f => f.description.isSome()}) {
|
.getLastMetadata<SimApiDoc> {f => f.description.isSome()}) {
|
||||||
if (let Some(description) <- metadata.description) {
|
if (let Some(description) <- metadata.description) {
|
||||||
operation.description = description
|
operation.description = description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// operationId
|
// 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
|
operation.operationId = metadata.name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +229,7 @@ protected class OpenApiDocumentService {
|
|||||||
private func createOpenApiOperationTags(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
|
private func createOpenApiOperationTags(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
|
||||||
let result = ArrayList<OpenApiTag>()
|
let result = ArrayList<OpenApiTag>()
|
||||||
// tags
|
// 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) {
|
if (let Some(tags) <- metadata.tags) {
|
||||||
for (name in tags.split(',')) {
|
for (name in tags.split(',')) {
|
||||||
result.add(OpenApiTag(name: name))
|
result.add(OpenApiTag(name: name))
|
||||||
@@ -251,7 +243,7 @@ protected class OpenApiDocumentService {
|
|||||||
if (let Some(actionDescriptor) <- actionDescriptor) {
|
if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||||
result.add(OpenApiTag(name: actionDescriptor.controllerName))
|
result.add(OpenApiTag(name: actionDescriptor.controllerName))
|
||||||
} else {
|
} else {
|
||||||
result.add(OpenApiTag(name: OpenApiConstants.DefaultOpenApiName))
|
result.add(OpenApiTag(name: "default"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -299,12 +291,12 @@ protected class OpenApiDocumentService {
|
|||||||
private func createOpenApiOperationResponses(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
|
private func createOpenApiOperationResponses(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
|
||||||
let responses = OpenApiResponses()
|
let responses = OpenApiResponses()
|
||||||
|
|
||||||
// 获取action的返回类型:优先从 actionDescriptor,其次从 IApiResponseTypeMetadata(动态路由)
|
// 获取action的返回类型:优先从 actionDescriptor,其次从 ApiResponseTypeMetadata(动态路由)
|
||||||
let returnType: ?TypeInfo = if (let Some(actionDescriptor) <- actionDescriptor) {
|
let returnType: ?TypeInfo = if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||||
Nullable.getUnderlyingType(actionDescriptor.actionFunction.returnType) ?? actionDescriptor
|
Nullable.getUnderlyingType(actionDescriptor.actionFunction.returnType) ?? actionDescriptor
|
||||||
.actionFunction
|
.actionFunction
|
||||||
.returnType
|
.returnType
|
||||||
} else if (let Some(meta) <- endpoint.metadata.getMetadata<IApiResponseTypeMetadata>()) {
|
} else if (let Some(meta) <- endpoint.metadata.getMetadata<ApiResponseTypeMetadata>()) {
|
||||||
meta.responseType
|
meta.responseType
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -357,30 +349,9 @@ protected class OpenApiDocumentService {
|
|||||||
|
|
||||||
// api ignore
|
// api ignore
|
||||||
private func isIgnore(endpoint: RouteEndpoint) {
|
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 true
|
||||||
}
|
}
|
||||||
return false
|
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.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::simapi.openapi.services
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
import std.reflect.*
|
import std.reflect.*
|
||||||
import std.collection.*
|
import std.collection.*
|
||||||
@@ -12,7 +12,6 @@ import soulsoft_web_mvc.*
|
|||||||
import soulsoft_web_mvc.utilities.*
|
import soulsoft_web_mvc.utilities.*
|
||||||
import simcu::simapi.openapi.models.*
|
import simcu::simapi.openapi.models.*
|
||||||
import soulsoft_web_mvc.controllers.*
|
import soulsoft_web_mvc.controllers.*
|
||||||
import simcu::simapi.openapi.infrastructure.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 提供 OpenAPI 参数生成能力。
|
* @brief 提供 OpenAPI 参数生成能力。
|
||||||
+4
-27
@@ -7,7 +7,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::simapi.openapi.services
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
import std.time.*
|
import std.time.*
|
||||||
import std.reflect.*
|
import std.reflect.*
|
||||||
@@ -17,10 +17,8 @@ import stdx.encoding.json.*
|
|||||||
import soulsoft_web_http.*
|
import soulsoft_web_http.*
|
||||||
import simcu::simapi.openapi.models.*
|
import simcu::simapi.openapi.models.*
|
||||||
import soulsoft_web_mvc.IActionResult
|
import soulsoft_web_mvc.IActionResult
|
||||||
import simcu::simapi.openapi.metadata.*
|
import simcu::simapi.annotations.*
|
||||||
import soulsoft_extensions_injection.*
|
import soulsoft_extensions_injection.*
|
||||||
import simcu::simapi.openapi.transformers.*
|
|
||||||
import simcu::simapi.openapi.infrastructure.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 提供 OpenAPI Schema 生成功能。
|
* @brief 提供 OpenAPI Schema 生成功能。
|
||||||
@@ -105,21 +103,18 @@ protected class OpenApiSchemaService {
|
|||||||
let `type` = OpenApiSchemaTypes.convert(typeInfo)
|
let `type` = OpenApiSchemaTypes.convert(typeInfo)
|
||||||
let format = convertToSchemaFormat(typeInfo)
|
let format = convertToSchemaFormat(typeInfo)
|
||||||
let schema = OpenApiSchema(`type`: `type`, format: format, nullable: nullable)
|
let schema = OpenApiSchema(`type`: `type`, format: format, nullable: nullable)
|
||||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsonValue/JsonObject/JsonArray 表示“任意 JSON 值树”,不应展开为固定 object schema。
|
// JsonValue/JsonObject/JsonArray 表示“任意 JSON 值树”,不应展开为固定 object schema。
|
||||||
if (typeInfo.isSubtypeOf(OpenApiSchemaService._jsonValueType)) {
|
if (typeInfo.isSubtypeOf(OpenApiSchemaService._jsonValueType)) {
|
||||||
let schema = OpenApiSchema(nullable: nullable)
|
let schema = OpenApiSchema(nullable: nullable)
|
||||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理文件上传
|
// 处理文件上传
|
||||||
if (typeInfo == OpenApiSchemaService._formFileType) {
|
if (typeInfo == OpenApiSchemaService._formFileType) {
|
||||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, format: "binary")
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, format: "binary")
|
||||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +137,6 @@ protected class OpenApiSchemaService {
|
|||||||
|
|
||||||
if (let _: EnumTypeInfo <- typeInfo) {
|
if (let _: EnumTypeInfo <- typeInfo) {
|
||||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, nullable: nullable)
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, nullable: nullable)
|
||||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,14 +164,12 @@ protected class OpenApiSchemaService {
|
|||||||
// 先加入缓存,再处理属性,避免循环引用导致无限递归
|
// 先加入缓存,再处理属性,避免循环引用导致无限递归
|
||||||
_schemas.add(schemaId, schema)
|
_schemas.add(schemaId, schema)
|
||||||
buildObjectProperties(schema, typeInfo)
|
buildObjectProperties(schema, typeInfo)
|
||||||
applySchemaTransformers(schema, schemaId)
|
|
||||||
}
|
}
|
||||||
return createReferenceSchema(schemaId, nullable)
|
return createReferenceSchema(schemaId, nullable)
|
||||||
} else {
|
} else {
|
||||||
// 强制内联:不缓存,不生成 $ref
|
// 强制内联:不缓存,不生成 $ref
|
||||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
||||||
buildObjectProperties(schema, typeInfo)
|
buildObjectProperties(schema, typeInfo)
|
||||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +187,6 @@ protected class OpenApiSchemaService {
|
|||||||
private func createInlineObjectSchema(typeInfo: TypeInfo, nullable: Bool): OpenApiSchema {
|
private func createInlineObjectSchema(typeInfo: TypeInfo, nullable: Bool): OpenApiSchema {
|
||||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
||||||
buildObjectProperties(schema, typeInfo)
|
buildObjectProperties(schema, typeInfo)
|
||||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +198,7 @@ protected class OpenApiSchemaService {
|
|||||||
let propertyTypeInfo = underlyingType ?? property.typeInfo
|
let propertyTypeInfo = underlyingType ?? property.typeInfo
|
||||||
let propertySchema = buildSchema(propertyTypeInfo, underlyingType.isSome(), true)
|
let propertySchema = buildSchema(propertyTypeInfo, underlyingType.isSome(), true)
|
||||||
for (annotation in property.annotations) {
|
for (annotation in property.annotations) {
|
||||||
if (let meta: IApiDescriptionMetadata <- annotation) {
|
if (let meta: SimApiDoc <- annotation) {
|
||||||
propertySchema.description = meta.description
|
propertySchema.description = meta.description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,7 +214,7 @@ protected class OpenApiSchemaService {
|
|||||||
let variableTypeInfo = underlyingType ?? variable.typeInfo
|
let variableTypeInfo = underlyingType ?? variable.typeInfo
|
||||||
let variableSchema = buildSchema(variableTypeInfo, underlyingType.isSome(), true)
|
let variableSchema = buildSchema(variableTypeInfo, underlyingType.isSome(), true)
|
||||||
for (annotation in variable.annotations) {
|
for (annotation in variable.annotations) {
|
||||||
if (let meta: IApiDescriptionMetadata <- annotation) {
|
if (let meta: SimApiDoc <- annotation) {
|
||||||
variableSchema.description = meta.description
|
variableSchema.description = meta.description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,8 +235,6 @@ protected class OpenApiSchemaService {
|
|||||||
private func createArraySchema(elementTypeInfo: TypeInfo, preferReference: Bool): OpenApiSchema {
|
private func createArraySchema(elementTypeInfo: TypeInfo, preferReference: Bool): OpenApiSchema {
|
||||||
let itemSchema = buildSchema(elementTypeInfo, false, preferReference)
|
let itemSchema = buildSchema(elementTypeInfo, false, preferReference)
|
||||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.ARRAY, items: itemSchema)
|
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.ARRAY, items: itemSchema)
|
||||||
// transformer 面向当前数组 schema,本处使用容器类型名最准确;现阶段退化为元素类型名。
|
|
||||||
applySchemaTransformers(schema, elementTypeInfo.qualifiedName)
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,24 +247,12 @@ protected class OpenApiSchemaService {
|
|||||||
if (keyTypeInfo != OpenApiSchemaService._stringType) {
|
if (keyTypeInfo != OpenApiSchemaService._stringType) {
|
||||||
// 非字符串 key 无法直接表达为标准 JSON object key;当前保守输出为封闭 object。
|
// 非字符串 key 无法直接表达为标准 JSON object key;当前保守输出为封闭 object。
|
||||||
schema.additionalProperties = false
|
schema.additionalProperties = false
|
||||||
applySchemaTransformers(schema, keyTypeInfo.qualifiedName)
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
schema.additionalPropertiesSchema = buildSchema(valueTypeInfo, false, true)
|
schema.additionalPropertiesSchema = buildSchema(valueTypeInfo, false, true)
|
||||||
applySchemaTransformers(schema, valueTypeInfo.qualifiedName)
|
|
||||||
return schema
|
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
|
解析 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.
|
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||||
* This source file is licensed under the MIT License found in the
|
* This source file is licensed under the MIT License found in the
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::simapi.openapi.services
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
import std.io.*
|
import std.io.*
|
||||||
|
import std.reflect.*
|
||||||
import stdx.encoding.json.stream.*
|
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 写入器实现。
|
* @brief 提供基于 JSON 的 OpenAPI 写入器实现。
|
||||||
@@ -94,3 +109,40 @@ public class JsonOpenApiWriter <: IOpenApiWriter {
|
|||||||
_writer.flush()
|
_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.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::simapi.openapi.infrastructure
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
import std.reflect.*
|
import std.reflect.*
|
||||||
import std.collection.*
|
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.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package simcu::simapi.openapi.infrastructure
|
package simcu::simapi.openapi
|
||||||
|
|
||||||
import std.collection.*
|
import std.collection.*
|
||||||
|
|
||||||
@@ -44,8 +44,7 @@ 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.*
|
||||||
import simcu::simapi.openapi.annotations.*
|
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAnnotation, SimApiDoc}
|
||||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAnnotation}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SimApi 扩展入口。
|
* 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
|
# 用法:pwsh tools/gen-swagger-ui-resources.ps1
|
||||||
# 资源更新后重新运行此脚本即可。
|
# 资源更新后重新运行此脚本即可。
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
$root = Split-Path -Parent $PSScriptRoot
|
$root = Split-Path -Parent $PSScriptRoot
|
||||||
$inDir = Join-Path $root "resources\openapi"
|
$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 = @(
|
$files = @(
|
||||||
@{ Name = "allHtml"; File = "all.html" },
|
@{ Name = "allHtml"; File = "all.html" },
|
||||||
|
|||||||
Reference in New Issue
Block a user