Compare commits

...
2 Commits
Author SHA1 Message Date
xrain cbe763ebb6 精简了openapi相关 2026-09-02 00:08:33 +08:00
xrain 06884a920c 精简了openapi相关 2026-09-01 23:36:07 +08:00
74 changed files with 466 additions and 1215 deletions

No files matched your search

+12 -12
View File
@@ -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_extensions_logging_console = {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_web_routing = {version = "1.0.20260528"}
soulsoft_extensions_logging = {version = "1.0.20260528"}
soulsoft_identity_claims = {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"} "simcu::serialization" = {version = "1.2.1"}
soulsoft_web_hosting = {version = "1.0.20260528"}
soulsoft_web_http = {version = "1.0.20260528"}
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
soulsoft_extensions_configuration = {version = "1.0.20260528"}
soulsoft_extensions_logging_configuration = {version = "1.0.20260528"}
soulsoft_serialization = {version = "1.0.20260528"}
soulsoft_extensions_injection = {version = "1.0.20260528"}
redis = {version = "1.0.20260627"}
soulsoft_extensions_options = {version = "1.0.20260528"}
soulsoft_web_mvc = {version = "1.0.20260528"}
soulsoft_web_cors = {version = "1.0.20260528"}
+1 -1
View File
@@ -3,7 +3,7 @@ cjc-version = "1.1.3"
name = "simapi" name = "simapi"
organization = "simcu" organization = "simcu"
description = "SimApi 仓颉版:ASP.NET Core 风格 API 基础框架(统一响应/异常拦截/Token认证/缓存/工具集/HTTP客户端)" description = "SimApi 仓颉版:ASP.NET Core 风格 API 基础框架(统一响应/异常拦截/Token认证/缓存/工具集/HTTP客户端)"
version = "1.1.2" version = "1.1.5"
target-dir = "" target-dir = ""
output-type = "static" output-type = "static"
override-compile-option = "" override-compile-option = ""
+63
View File
@@ -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
}
}
+1 -2
View File
@@ -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、用户信息。
-9
View File
@@ -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
}
}
-107
View File
@@ -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
}
}
}
-28
View File
@@ -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"
}
+97
View File
@@ -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
}
-21
View File
@@ -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
}
-21
View File
@@ -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
}
-9
View File
@@ -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.*
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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 文档写入接口。
@@ -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
} }
} }
@@ -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
}
}
+152
View File
@@ -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
}
}
+46
View File
@@ -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
}
}
}
@@ -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 参数生成能力。
@@ -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)
}
}
File renamed without changes.
@@ -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.*
-30
View File
@@ -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
}
-28
View File
@@ -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
}
}
-129
View File
@@ -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))
}
}
File renamed without changes.
@@ -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.*
+1 -2
View File
@@ -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 扩展入口。
+2 -2
View File
@@ -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" },