Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbe763ebb6 | ||
|
|
06884a920c | ||
|
|
eed230aab0 |
@@ -369,7 +369,7 @@ SimApiExtensions.addSimApi(builder) { options =>
|
||||
```
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| ------------------------------------------------- | ------------------------------------------------------------------------- |
|
||||
| `getUploadUrl(path, expire=7200)` | 上传预签名 URL,返回 `GetUploadUrlResponse(UploadUrl, DownloadUrl, Path)` |
|
||||
| `getDownloadUrl(path, expire=600)` | 下载预签名 URL |
|
||||
| `uploadFile(path, data, contentType="image/png")` | 直接 PUT 上传(字节数组) |
|
||||
@@ -383,7 +383,7 @@ SimApiExtensions.addSimApi(builder) { options =>
|
||||
### 6. 内置路由(UseSimApi 自动注册)
|
||||
|
||||
| 路由 | 方法 | 条件 | 说明 |
|
||||
| ----------------- | -------- | ---------------------------- | -------------------------- |
|
||||
| ------------------- | ---- | ------------------ | ---------------------------------------------- |
|
||||
| `/user/info` | POST | `enableSimApiAuth` | 需登录,返回 LoginInfo |
|
||||
| `/auth/logout` | POST | `enableSimApiAuth` | 退出登录 |
|
||||
| `/exception/{code}` | GET | 始终 | 错误反馈(抛 SimApiException,不出现在文档中) |
|
||||
@@ -428,7 +428,7 @@ SimApiExtensions.addSimApi(builder) { options =>
|
||||
```
|
||||
|
||||
| 类 | 说明 |
|
||||
|----|------|
|
||||
| ---------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `SimApiAuthClient` | `SimApiHttpClient` 子类,凭证取 AuthCenterOptions |
|
||||
| `SimApiAuthCenter` | 群组/Profile/内部应用/系统登录/安全验证等 12 个接口 + `VerifySign` |
|
||||
| `SimApiAuthIam` | 注册权限 / 获取权限标识 / 校验权限(无权限抛 403) |
|
||||
@@ -492,7 +492,7 @@ options.configureSimApiDoc { doc =>
|
||||
```
|
||||
|
||||
| 路由 | 说明 |
|
||||
|------|------|
|
||||
| --------------------- | ------------------------------------------ |
|
||||
| `/{prefix}/all.html` | 多文档切换页(顶部栏下拉选择所有文档) |
|
||||
| `/{prefix}/{id}.html` | 单文档页(无顶部栏,自动加载 `{id}.json`) |
|
||||
| `/{prefix}/urls` | 文档列表 JSON(供 Swagger UI 下拉) |
|
||||
@@ -505,7 +505,7 @@ options.configureSimApiDoc { doc =>
|
||||
接口返回值自动封装为统一响应格式,**文档中 response schema 也体现封装**:
|
||||
|
||||
| 返回类型 | `@OriginResponse` | 文档 response schema |
|
||||
|----------|-------------------|---------------------|
|
||||
| ------------------------------------------------------------- | ----------------- | --------------------------------------- |
|
||||
| `SimApiBaseResponse`/`SimApiResponse<T>`/`SimApiDataResponse` | - | 原样不封装 |
|
||||
| 任意类型 | ✓ | 原样不封装 |
|
||||
| `Unit` (void) | ✗ | `{code: int64, message: string}` |
|
||||
@@ -626,7 +626,7 @@ SimApiExtensions.addSimApi(builder) { options =>
|
||||
simapi 提供 Spire MVC 控制器(继承 `SimApiBaseController`),`addSimApi` 自动注册内置控制器 + 自动扫描调用者包中的控制器:
|
||||
|
||||
| 控制器 | 路由 | 说明 |
|
||||
|--------|------|------|
|
||||
| ------------------------ | -------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `SimApiCommonController` | `/exception/{code}`、`/config`、`/user/info` | 通用内置路由 |
|
||||
| `SimApiAuthController` | `/auth/logout` | 退出登录 |
|
||||
| `SimApiBaseController` | — | 基类:`loginInfo` / `loginToken`(访问时自动校验登录,未登录抛 401) |
|
||||
@@ -657,7 +657,7 @@ public class MyController <: SimApiBaseController {
|
||||
以下原包功能因仓颉生态暂无对应库(Hangfire/MQTT),**选项保留但未实现**:
|
||||
|
||||
| 选项 | 原功能 | 状态 |
|
||||
|------|--------|------|
|
||||
| --------------- | ----------------- | -------- |
|
||||
| `enableSynapse` | MQTT 通信 | ❌ 未实现 |
|
||||
| `enableJob` | Hangfire 任务调度 | ❌ 未实现 |
|
||||
|
||||
@@ -668,7 +668,7 @@ public class MyController <: SimApiBaseController {
|
||||
## 依赖
|
||||
|
||||
| 依赖 | 用途 |
|
||||
|------|------|
|
||||
| --------------------------------------- | -------------------------------------- |
|
||||
| `soulsoft_web_http / routing / hosting` | Web 框架 |
|
||||
| `soulsoft_extensions_logging` 系列 | 日志 |
|
||||
| `soulsoft_extensions_injection` | 依赖注入 |
|
||||
|
||||
@@ -3,18 +3,18 @@ version = 0
|
||||
[requires]
|
||||
soulsoft_extensions_hosting = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_options_configuration = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
|
||||
redis = {version = "1.0.20260627"}
|
||||
soulsoft_extensions_logging_configuration = {version = "1.0.20260528"}
|
||||
soulsoft_identity_claims = {version = "1.0.20260528"}
|
||||
soulsoft_web_http = {version = "1.0.20260528"}
|
||||
"simcu::serialization" = {version = "1.2.1"}
|
||||
soulsoft_extensions_configuration = {version = "1.0.20260528"}
|
||||
soulsoft_web_routing = {version = "1.0.20260528"}
|
||||
soulsoft_web_cors = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_injection = {version = "1.0.20260528"}
|
||||
soulsoft_web_mvc = {version = "1.0.20260528"}
|
||||
soulsoft_serialization = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_options = {version = "1.0.20260528"}
|
||||
soulsoft_extensions_logging = {version = "1.0.20260528"}
|
||||
soulsoft_identity_claims = {version = "1.0.20260528"}
|
||||
"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"}
|
||||
|
||||
@@ -3,7 +3,7 @@ cjc-version = "1.1.3"
|
||||
name = "simapi"
|
||||
organization = "simcu"
|
||||
description = "SimApi 仓颉版:ASP.NET Core 风格 API 基础框架(统一响应/异常拦截/Token认证/缓存/工具集/HTTP客户端)"
|
||||
version = "1.1.0"
|
||||
version = "1.1.5"
|
||||
target-dir = ""
|
||||
output-type = "static"
|
||||
override-compile-option = ""
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.annotations
|
||||
|
||||
/**
|
||||
* @brief 为控制器或动作提供 API 文档元数据(对齐 C# [SimApiDoc] 特性)。
|
||||
*
|
||||
* 用法:
|
||||
* @SimApiDoc[tags: "登录", summary: "用户登录相关接口"]
|
||||
* @SimApiDoc[tags: "认证", summary: "后台登录", groupNames: "admin"]
|
||||
* @SimApiDoc[tags: "公共", groupNames: "api,admin"] // 同时出现在 api 和 admin 文档
|
||||
* @SimApiDoc[tags: "公共", groupNames: "*"] // 出现在所有文档
|
||||
*/
|
||||
@Annotation[target: [MemberFunction, Type, MemberProperty, MemberVariable, Parameter]]
|
||||
public class SimApiDoc {
|
||||
/**
|
||||
* @brief API 名称。
|
||||
*/
|
||||
public let name: ?String
|
||||
/**
|
||||
* @brief 当前 API 是否应被忽略。
|
||||
*/
|
||||
public let ignore: Bool
|
||||
/**
|
||||
* @brief API 摘要信息。
|
||||
*/
|
||||
public let summary: ?String
|
||||
/**
|
||||
* @brief API 分组名称(逗号分隔,如 "api,admin");"*" 表示出现在所有文档;空串表示未分组(仅进默认文档)。
|
||||
*/
|
||||
public let groupNames: String
|
||||
/**
|
||||
* @brief API 描述信息。
|
||||
*/
|
||||
public let description: ?String
|
||||
/**
|
||||
* @brief API 标签字符串。
|
||||
*/
|
||||
public let tags: ?String
|
||||
|
||||
/**
|
||||
* @brief 创建 SimApiDoc 注解实例。
|
||||
* @param name API 名称。
|
||||
* @param ignore 是否忽略当前 API。
|
||||
* @param summary API 摘要。
|
||||
* @param groupNames API 分组名称(逗号分隔,如 "api,admin");"*" 表示出现在所有文档;空串表示未分组(仅进默认文档)。
|
||||
* @param description API 描述。
|
||||
* @param tags API 标签字符串。
|
||||
*/
|
||||
public const init(name!: ?String = None, ignore!: Bool = false, summary!: ?String = None, groupNames!: String = "",
|
||||
description!: ?String = None, tags!: ?String = None) {
|
||||
this.tags = tags
|
||||
this.name = name
|
||||
this.ignore = ignore
|
||||
this.summary = summary
|
||||
this.groupNames = groupNames
|
||||
this.description = description
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,7 @@ import soulsoft_web_mvc.annotations.*
|
||||
import simcu::simapi.communications.*
|
||||
import simcu::simapi.configurations.*
|
||||
import simcu::simapi.helpers.*
|
||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAttribute}
|
||||
import simcu::simapi.openapi.annotations.*
|
||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAttribute, SimApiDoc}
|
||||
|
||||
/**
|
||||
* 通用控制器:错误反馈、WebConfig、用户信息。
|
||||
@@ -31,7 +30,7 @@ public class SimApiCommonController <: SimApiBaseController {
|
||||
*/
|
||||
@HttpGet["exception/{code}"]
|
||||
@SimApiDoc[ignore: true]
|
||||
public func exceptionHandler(@FromRoute[] code: Int64) { // cjlint-ignore !G.FUN.02 注解绑定参数误报
|
||||
public func exceptionHandler(@FromRoute[] code: Int64): Unit { // cjlint-ignore !G.FUN.02 注解绑定参数误报
|
||||
SimApiError.error(code: code)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = "default"
|
||||
public static let DefaultDocumentName = "v1"
|
||||
public static let DefaultOpenApiRoute = "/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.*
|
||||
@@ -18,12 +18,12 @@ public class OpenApiComponents <: IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 表示组件中的架构集合。
|
||||
*/
|
||||
public var schemas = HashMap<String, OpenApiSchema>()
|
||||
public var schemas: HashMap<String, OpenApiSchema> = HashMap<String, OpenApiSchema>()
|
||||
|
||||
/**
|
||||
* @brief 表示组件中的安全方案集合。
|
||||
*/
|
||||
public var securitySchemes = HashMap<String, OpenApiSecurityScheme>()
|
||||
public var securitySchemes: HashMap<String, OpenApiSecurityScheme> = HashMap<String, OpenApiSecurityScheme>()
|
||||
|
||||
/**
|
||||
* @brief 创建 OpenAPI 组件实例。
|
||||
@@ -34,15 +34,15 @@ public class OpenApiDocument <: IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 表示标签集合。
|
||||
*/
|
||||
public var tags = ArrayList<OpenApiTag>()
|
||||
public var tags: ArrayList<OpenApiTag> = ArrayList<OpenApiTag>()
|
||||
/**
|
||||
* @brief 表示服务器集合。
|
||||
*/
|
||||
public var servers = ArrayList<OpenApiServer>()
|
||||
public var servers: ArrayList<OpenApiServer> = ArrayList<OpenApiServer>()
|
||||
/**
|
||||
* @brief 表示安全需求集合。
|
||||
*/
|
||||
public var securityRequirements = ArrayList<OpenApiSecurityRequirement>()
|
||||
public var securityRequirements: ArrayList<OpenApiSecurityRequirement> = ArrayList<OpenApiSecurityRequirement>()
|
||||
|
||||
/**
|
||||
* @brief 创建 OpenAPI 文档对象。
|
||||
@@ -30,15 +30,15 @@ public class OpenApiOperation <: IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 表示操作响应集合。
|
||||
*/
|
||||
public var response = OpenApiResponses()
|
||||
public var response: OpenApiResponses = OpenApiResponses()
|
||||
/**
|
||||
* @brief 表示操作标签集合。
|
||||
*/
|
||||
public var tags = ArrayList<OpenApiTag>()
|
||||
public var tags: ArrayList<OpenApiTag> = ArrayList<OpenApiTag>()
|
||||
/**
|
||||
* @brief 表示操作服务器集合。
|
||||
*/
|
||||
public var servers = ArrayList<OpenApiServer>()
|
||||
public var servers: ArrayList<OpenApiServer> = ArrayList<OpenApiServer>()
|
||||
/**
|
||||
* @brief 表示操作请求体。
|
||||
*/
|
||||
@@ -46,11 +46,11 @@ public class OpenApiOperation <: IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 表示操作参数集合。
|
||||
*/
|
||||
public var parameters = ArrayList<OpenApiParameter>()
|
||||
public var parameters: ArrayList<OpenApiParameter> = ArrayList<OpenApiParameter>()
|
||||
/**
|
||||
* @brief 表示操作安全需求集合。
|
||||
*/
|
||||
public var security = ArrayList<OpenApiSecurityRequirement>()
|
||||
public var security: ArrayList<OpenApiSecurityRequirement> = ArrayList<OpenApiSecurityRequirement>()
|
||||
|
||||
/**
|
||||
* @brief 创建 OpenAPI 操作对象。
|
||||
@@ -34,15 +34,15 @@ public class OpenApiPathItem <: IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 表示路径项服务器集合。
|
||||
*/
|
||||
public var servers = ArrayList<OpenApiServer>()
|
||||
public var servers: ArrayList<OpenApiServer> = ArrayList<OpenApiServer>()
|
||||
/**
|
||||
* @brief 表示路径项参数集合。
|
||||
*/
|
||||
public var parameters = ArrayList<OpenApiParameter>()
|
||||
public var parameters: ArrayList<OpenApiParameter> = ArrayList<OpenApiParameter>()
|
||||
/**
|
||||
* @brief 表示路径项操作集合。
|
||||
*/
|
||||
public var operations = HashMap<OperationType, OpenApiOperation>()
|
||||
public var operations: HashMap<OperationType, OpenApiOperation> = HashMap<OperationType, OpenApiOperation>()
|
||||
|
||||
/**
|
||||
* @brief 创建 OpenAPI 路径项对象。
|
||||
+1
-1
@@ -26,7 +26,7 @@ public class OpenApiRequestBody <: IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 表示请求体内容映射。
|
||||
*/
|
||||
public var content = HashMap<String, OpenApiMediaType>()
|
||||
public var content: HashMap<String, OpenApiMediaType> = HashMap<String, OpenApiMediaType>()
|
||||
|
||||
/**
|
||||
* @brief 创建 OpenAPI 请求体对象。
|
||||
@@ -22,7 +22,7 @@ public class OpenApiResponse <: IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 表示响应内容映射。
|
||||
*/
|
||||
public var content = HashMap<String, OpenApiMediaType>()
|
||||
public var content: HashMap<String, OpenApiMediaType> = HashMap<String, OpenApiMediaType>()
|
||||
|
||||
/**
|
||||
* @brief 创建 OpenAPI 响应对象。
|
||||
@@ -46,11 +46,11 @@ public class OpenApiSchema <: IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 表示对象属性集合。
|
||||
*/
|
||||
public var properties = HashMap<String, OpenApiSchema>()
|
||||
public var properties: HashMap<String, OpenApiSchema> = HashMap<String, OpenApiSchema>()
|
||||
/**
|
||||
* @brief 表示 allOf 组合架构。
|
||||
*/
|
||||
public var allOf = ArrayList<OpenApiSchema>()
|
||||
public var allOf: ArrayList<OpenApiSchema> = ArrayList<OpenApiSchema>()
|
||||
/**
|
||||
* @brief 表示是否允许附加属性。
|
||||
*/
|
||||
@@ -70,7 +70,7 @@ public class OpenApiSchema <: IOpenApiSerializable {
|
||||
* @param reference 架构引用。
|
||||
* @param description 架构描述。
|
||||
*/
|
||||
public init(title!: ?String = None, nullable!: ?Bool = None, `type`!: ?String = None, items!: ?OpenApiSchema = None,
|
||||
public init(title!: ?String = None, nullable!: ?Bool = None, `type`!: ?String = None, items!: ?OpenApiSchema = None, // cjlint-ignore !G.FUN.02
|
||||
format!: ?String = None, reference!: ?OpenApiReference = None, description!: ?String = None) {
|
||||
this.title = title
|
||||
this.format = format
|
||||
+3
-3
@@ -13,9 +13,9 @@ private let numbers = ["Float16", "Float32", "Float64", "Decimal"]
|
||||
private let integers = ["Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64"]
|
||||
|
||||
protected class OpenApiSchemaTypes {
|
||||
public static const OBJECT = "object"
|
||||
public static const STRING = "string"
|
||||
public static const ARRAY = "array"
|
||||
public static const OBJECT: String = "object"
|
||||
public static const STRING: String = "string"
|
||||
public static const ARRAY: String = "array"
|
||||
|
||||
/**
|
||||
* @brief 将类型信息转换为 OpenAPI Schema 类型名称。
|
||||
@@ -1,13 +1,21 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.interfaces
|
||||
package simcu::simapi.openapi.models
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 对象序列化接口。
|
||||
*/
|
||||
public interface IOpenApiSerializable {
|
||||
/**
|
||||
* @brief 按 OpenAPI V3 格式写出当前对象。
|
||||
* @param writer OpenAPI 写入器。
|
||||
*/
|
||||
func serializeAsV3(writer: IOpenApiWriter): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 文档写入接口。
|
||||
+5
-11
@@ -7,13 +7,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.collection.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
import soulsoft_extensions_options.*
|
||||
import soulsoft_extensions_injection.*
|
||||
import simcu::simapi.openapi.transformers.*
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI 文档创建能力。
|
||||
@@ -42,10 +41,10 @@ protected class OpenApiDocumentProvider <: IDocumentProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名称创建 OpenAPI 文档并依次应用所有转换器。
|
||||
* @brief 按名称创建 OpenAPI 文档。
|
||||
* @param documentName 要创建的文档名称。
|
||||
* @param services 请求作用域的服务提供器,传递给操作转换器上下文。
|
||||
* @return 经过转换器处理后的 OpenAPI 文档。
|
||||
* @param services 请求作用域的服务提供器。
|
||||
* @return OpenAPI 文档。
|
||||
*/
|
||||
public func create(documentName: String, services: IServiceProvider): OpenApiDocument {
|
||||
let options = _options.get(documentName)
|
||||
@@ -57,11 +56,6 @@ protected class OpenApiDocumentProvider <: IDocumentProvider {
|
||||
schemaService,
|
||||
parameterService
|
||||
)
|
||||
let document = provider.getOpenApiDocument(services)
|
||||
let context = OpenApiDocumentTransformerContext(services, documentName)
|
||||
for (transformer in options.documentTransformers) {
|
||||
transformer.transform(document, context)
|
||||
}
|
||||
return document
|
||||
return provider.getOpenApiDocument(services)
|
||||
}
|
||||
}
|
||||
+11
-40
@@ -7,7 +7,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
@@ -20,10 +20,7 @@ import simcu::simapi.openapi.models.*
|
||||
import soulsoft_extensions_options.*
|
||||
import soulsoft_web_mvc.controllers.*
|
||||
import soulsoft_extensions_injection.*
|
||||
import simcu::simapi.openapi.metadata.*
|
||||
import soulsoft_web_mvc.abstractions.*
|
||||
import simcu::simapi.openapi.transformers.*
|
||||
import simcu::simapi.openapi.infrastructure.*
|
||||
import simcu::simapi.configurations.*
|
||||
import simcu::simapi.annotations.*
|
||||
|
||||
@@ -105,7 +102,6 @@ protected class OpenApiDocumentService {
|
||||
private func createOpenApiPaths(services: IServiceProvider) {
|
||||
let paths = OpenApiPaths()
|
||||
let openApiOptions = services.getOrThrow<IOptionsMonitor<OpenApiOptions>>().get(_documentName)
|
||||
let operationTransformers = openApiOptions.operationTransformers
|
||||
for (endpoint in _endpointSource.endpoints |> filterMap {f => f as RouteEndpoint} where !isIgnore(endpoint)) {
|
||||
if (!isShouldInclude(endpoint, openApiOptions.includeUnGrouped)) {
|
||||
continue
|
||||
@@ -132,10 +128,6 @@ protected class OpenApiDocumentService {
|
||||
let operationType = OperationType.parse(httpMethod)
|
||||
let actionDescriptor = endpoint.metadata.getMetadata<ControllerActionDescriptor>()
|
||||
let operation = createOpenApiOperation(endpoint, actionDescriptor)
|
||||
let context = OpenApiOperationTransformerContext(services, _documentName, actionDescriptor)
|
||||
for (transformer in operationTransformers) {
|
||||
transformer.transform(operation, context)
|
||||
}
|
||||
pathItem.operations.add(operationType, operation)
|
||||
}
|
||||
}
|
||||
@@ -144,7 +136,7 @@ protected class OpenApiDocumentService {
|
||||
}
|
||||
|
||||
private func isShouldInclude(endpoint: RouteEndpoint, includeUnGrouped: Bool) {
|
||||
let rawGroupNames = endpoint.metadata.getOrderedMetadata<IApiGroupNamesProvider>() |>
|
||||
let rawGroupNames = endpoint.metadata.getOrderedMetadata<SimApiDoc>() |>
|
||||
filterMap { f => f.groupNames } |> collectArray
|
||||
if (rawGroupNames.isEmpty()) {
|
||||
// 未标注 groupNames 的接口:仅进入默认文档(includeUnGrouped=true 的文档)
|
||||
@@ -204,7 +196,7 @@ protected class OpenApiDocumentService {
|
||||
operation.response = createOpenApiOperationResponses(endpoint, actionDescriptor)
|
||||
|
||||
// summary
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiSummaryMetadata> {f => f.summary.isSome()}) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.summary.isSome()}) {
|
||||
if (let Some(summary) <- metadata.summary) {
|
||||
operation.summary = summary
|
||||
}
|
||||
@@ -213,14 +205,14 @@ protected class OpenApiDocumentService {
|
||||
// description
|
||||
if (let Some(metadata) <- endpoint
|
||||
.metadata
|
||||
.getLastMetadata<IApiDescriptionMetadata> {f => f.description.isSome()}) {
|
||||
.getLastMetadata<SimApiDoc> {f => f.description.isSome()}) {
|
||||
if (let Some(description) <- metadata.description) {
|
||||
operation.description = description
|
||||
}
|
||||
}
|
||||
|
||||
// operationId
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiNameMetadata> {f => f.name.isSome()}) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.name.isSome()}) {
|
||||
operation.operationId = metadata.name
|
||||
}
|
||||
|
||||
@@ -237,7 +229,7 @@ protected class OpenApiDocumentService {
|
||||
private func createOpenApiOperationTags(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
|
||||
let result = ArrayList<OpenApiTag>()
|
||||
// tags
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiTagsMetadata> {f => f.tags.isSome()}) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.tags.isSome()}) {
|
||||
if (let Some(tags) <- metadata.tags) {
|
||||
for (name in tags.split(',')) {
|
||||
result.add(OpenApiTag(name: name))
|
||||
@@ -251,7 +243,7 @@ protected class OpenApiDocumentService {
|
||||
if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||
result.add(OpenApiTag(name: actionDescriptor.controllerName))
|
||||
} else {
|
||||
result.add(OpenApiTag(name: OpenApiConstants.DefaultOpenApiName))
|
||||
result.add(OpenApiTag(name: "default"))
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -260,7 +252,7 @@ protected class OpenApiDocumentService {
|
||||
/*
|
||||
来自请求体的参数
|
||||
*/
|
||||
private func createOpenApiOperationRequestBody(endpoint: RouteEndpoint, actionDescriptor: ControllerActionDescriptor): ?OpenApiRequestBody {
|
||||
private func createOpenApiOperationRequestBody(endpoint: RouteEndpoint, actionDescriptor: ControllerActionDescriptor): ?OpenApiRequestBody { // cjlint-ignore !G.FUN.02 endpoint 未使用
|
||||
var requestBody = OpenApiRequestBody(required: true)
|
||||
let parameters = HashMap<String, OpenApiSchema>()
|
||||
for (parameter in actionDescriptor.actionFunction.parameters) {
|
||||
@@ -299,12 +291,12 @@ protected class OpenApiDocumentService {
|
||||
private func createOpenApiOperationResponses(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
|
||||
let responses = OpenApiResponses()
|
||||
|
||||
// 获取action的返回类型:优先从 actionDescriptor,其次从 IApiResponseTypeMetadata(动态路由)
|
||||
// 获取action的返回类型:优先从 actionDescriptor,其次从 ApiResponseTypeMetadata(动态路由)
|
||||
let returnType: ?TypeInfo = if (let Some(actionDescriptor) <- actionDescriptor) {
|
||||
Nullable.getUnderlyingType(actionDescriptor.actionFunction.returnType) ?? actionDescriptor
|
||||
.actionFunction
|
||||
.returnType
|
||||
} else if (let Some(meta) <- endpoint.metadata.getMetadata<IApiResponseTypeMetadata>()) {
|
||||
} else if (let Some(meta) <- endpoint.metadata.getMetadata<ApiResponseTypeMetadata>()) {
|
||||
meta.responseType
|
||||
} else {
|
||||
None
|
||||
@@ -357,30 +349,9 @@ protected class OpenApiDocumentService {
|
||||
|
||||
// api ignore
|
||||
private func isIgnore(endpoint: RouteEndpoint) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<IApiVisibilityProvider> {f => f.ignore}) {
|
||||
if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.ignore}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extend EndpointMetadataCollection {
|
||||
/**
|
||||
* @brief 返回最后一个满足条件的元数据。
|
||||
* @param filter 用于筛选元数据的条件。
|
||||
* @return 最后一个满足条件的元数据;如果不存在则返回 `None`。
|
||||
*/
|
||||
public func getLastMetadata<T>(filter: (T) -> Bool): ?T {
|
||||
let metadatas = this.getOrderedMetadata<T>()
|
||||
var index = metadatas.size - 1
|
||||
while (index >= 0) {
|
||||
let metadata = metadatas[index]
|
||||
if (filter(metadata)) {
|
||||
return metadata
|
||||
}
|
||||
index--
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import soulsoft_web_http.*
|
||||
import soulsoft_web_routing.*
|
||||
import soulsoft_extensions_options.*
|
||||
import soulsoft_extensions_injection.*
|
||||
import simcu::simapi.annotations.*
|
||||
|
||||
/**
|
||||
* @brief 为端点附加 OpenAPI 元数据。
|
||||
*/
|
||||
extend EndpointConventionBuilder <: OpenApiEndpointConventionBuilderExtensions {
|
||||
/**
|
||||
* @brief 为端点附加 API 文档元数据。
|
||||
* @param metadata 要附加的 API 文档元数据。
|
||||
* @return 当前端点约定构建器实例。
|
||||
*/
|
||||
public func withOpenApi(metadata: SimApiDoc): EndpointConventionBuilder {
|
||||
this.add {
|
||||
builder => builder.metadata.add(metadata)
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 映射默认 OpenAPI 文档路由。
|
||||
*/
|
||||
extend EndpointRouteBuilder <: OpenApiEndpointRouteBuilderExtension {
|
||||
/**
|
||||
* @brief 映射指定模式的 OpenAPI 文档路由。
|
||||
* @param pattern OpenAPI 文档路由模式。
|
||||
* @return 当前端点路由构建器实例。
|
||||
*/
|
||||
public func mapOpenApi(pattern: String): EndpointRouteBuilder {
|
||||
verifyOpenApiServicesAreRegistered()
|
||||
this.mapGet(pattern) {
|
||||
context =>
|
||||
let documentName = context.request.routeValues.get("documentName").flatMap {f => f} ?? "v1"
|
||||
let documentFactory = context.services.getOrThrow<IDocumentProvider>()
|
||||
let document = documentFactory.create(documentName, context.services)
|
||||
context.response.contentType = "application/json; charset=utf-8"
|
||||
let writer = JsonOpenApiWriter(context.response.body)
|
||||
document.serializeAsV3(writer)
|
||||
writer.flush()
|
||||
}.withOpenApi(SimApiDoc(ignore: true))
|
||||
return this
|
||||
}
|
||||
|
||||
private func verifyOpenApiServicesAreRegistered() {
|
||||
let callSiteFactory = this.services.getOrThrow<IServiceProviderIsService>()
|
||||
if (!callSiteFactory.isService<IDocumentProvider>()) {
|
||||
throw Exception(
|
||||
"Unable to find the required services. Please add all the required services by calling 'ServiceCollection.addOpenApi' inside the call to 'configureServices(...)' in the application startup code.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册默认名称的 OpenAPI 服务。
|
||||
*/
|
||||
extend ServiceCollection <: OpenApiServiceCollectionExtensions {
|
||||
/**
|
||||
* @brief 注册指定文档名称的 OpenAPI 服务并配置选项。
|
||||
* @param documentName OpenAPI 文档名称。
|
||||
* @param configureOptions 用于配置 OpenAPI 选项的回调。
|
||||
* @return 当前服务集合实例。
|
||||
*/
|
||||
public func addOpenApi(documentName: String, configureOptions: (OpenApiOptions) -> Unit): ServiceCollection {
|
||||
this.addRouting()
|
||||
this.configure<OpenApiOptions>(documentName, configureOptions)
|
||||
this.addSingleton(NamedService(documentName))
|
||||
this.addSingleton<IDocumentProvider, OpenApiDocumentProvider>()
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用 OpenAPI UI 中间件。
|
||||
*/
|
||||
extend ApplicationBuilder <: OpenApiUIMiddlewareExtensions{
|
||||
/**
|
||||
* @brief 启用 OpenAPI UI 中间件。
|
||||
*/
|
||||
public func useOpenApiUI(): Unit {
|
||||
verifyOpenApiServicesAreRegistered()
|
||||
use<OpenApiUIMiddleware>()
|
||||
}
|
||||
|
||||
private func verifyOpenApiServicesAreRegistered() {
|
||||
let callSiteFactory = this.services.getOrThrow<IServiceProviderIsService>()
|
||||
if (!callSiteFactory.isService<IDocumentProvider>()) {
|
||||
throw Exception("Unable to find the required services. Please add all the required services by calling 'ServiceCollection.addOpenApi' inside the call to 'configureServices(...)' in the application startup code.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 为端点附加认证与响应类型元数据。
|
||||
*/
|
||||
extend EndpointConventionBuilder <: SimApiEndpointMetadataExtensions {
|
||||
/**
|
||||
* @brief 为端点附加 SimApiAuth 认证元数据(使 OpenAPI 文档显示锁图标)。
|
||||
*/
|
||||
public func withSimApiAuth(auth: SimApiAuth): EndpointConventionBuilder {
|
||||
this.add {
|
||||
builder => builder.metadata.add(auth)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 为端点附加响应类型元数据(使动态路由也能生成 response schema)。
|
||||
*/
|
||||
public func withResponseType(typeInfo: TypeInfo): EndpointConventionBuilder {
|
||||
this.add {
|
||||
builder => builder.metadata.add(ApiResponseTypeMetadata(Some(typeInfo)))
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提供元数据集合扩展。
|
||||
*/
|
||||
extend EndpointMetadataCollection {
|
||||
/**
|
||||
* @brief 返回最后一个满足条件的元数据。
|
||||
* @param filter 用于筛选元数据的条件。
|
||||
* @return 最后一个满足条件的元数据;如果不存在则返回 `None`。
|
||||
*/
|
||||
public func getLastMetadata<T>(filter: (T) -> Bool): ?T {
|
||||
let metadatas = this.getOrderedMetadata<T>()
|
||||
var index = metadatas.size - 1
|
||||
while (index >= 0) {
|
||||
let metadata = metadatas[index]
|
||||
if (filter(metadata)) {
|
||||
return metadata
|
||||
}
|
||||
index--
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 文档生成选项。
|
||||
*/
|
||||
public class OpenApiOptions {
|
||||
/**
|
||||
* 未标注 groupName 的接口是否进入本文档。
|
||||
* 多文档分组时,仅默认组(第一个 apiGroup 或 isDefault 的组)应设为 true。
|
||||
*/
|
||||
public var includeUnGrouped: Bool = false
|
||||
|
||||
/**
|
||||
* @brief 确定指定类型在 components/schemas 中使用的引用 ID。
|
||||
* 返回 None 时该 Schema 始终内联,不生成 $ref。
|
||||
*/
|
||||
public var createSchemaReferenceId: (TypeInfo) -> ?String = {typeInfo =>
|
||||
let name = typeInfo.name
|
||||
if (let Some(ltIdx) <- name.indexOf('<') && let Some(gtIdx) <- name.lastIndexOf('>') &&
|
||||
gtIdx > ltIdx) {
|
||||
let baseName = name[0..ltIdx]
|
||||
let argsStr = name[ltIdx + 1..gtIdx]
|
||||
let simpleName = if (let Some(dotIdx) <- argsStr.lastIndexOf('.')) {
|
||||
argsStr[dotIdx + 1..]
|
||||
} else {
|
||||
argsStr
|
||||
}
|
||||
"${baseName}Of${simpleName}"
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -4,7 +4,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
@@ -12,7 +12,6 @@ import soulsoft_web_mvc.*
|
||||
import soulsoft_web_mvc.utilities.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
import soulsoft_web_mvc.controllers.*
|
||||
import simcu::simapi.openapi.infrastructure.*
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI 参数生成能力。
|
||||
+4
-27
@@ -7,7 +7,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.time.*
|
||||
import std.reflect.*
|
||||
@@ -17,10 +17,8 @@ import stdx.encoding.json.*
|
||||
import soulsoft_web_http.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
import soulsoft_web_mvc.IActionResult
|
||||
import simcu::simapi.openapi.metadata.*
|
||||
import simcu::simapi.annotations.*
|
||||
import soulsoft_extensions_injection.*
|
||||
import simcu::simapi.openapi.transformers.*
|
||||
import simcu::simapi.openapi.infrastructure.*
|
||||
|
||||
/**
|
||||
* @brief 提供 OpenAPI Schema 生成功能。
|
||||
@@ -105,21 +103,18 @@ protected class OpenApiSchemaService {
|
||||
let `type` = OpenApiSchemaTypes.convert(typeInfo)
|
||||
let format = convertToSchemaFormat(typeInfo)
|
||||
let schema = OpenApiSchema(`type`: `type`, format: format, nullable: nullable)
|
||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
|
||||
// JsonValue/JsonObject/JsonArray 表示“任意 JSON 值树”,不应展开为固定 object schema。
|
||||
if (typeInfo.isSubtypeOf(OpenApiSchemaService._jsonValueType)) {
|
||||
let schema = OpenApiSchema(nullable: nullable)
|
||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
|
||||
// 处理文件上传
|
||||
if (typeInfo == OpenApiSchemaService._formFileType) {
|
||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, format: "binary")
|
||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
|
||||
@@ -142,7 +137,6 @@ protected class OpenApiSchemaService {
|
||||
|
||||
if (let _: EnumTypeInfo <- typeInfo) {
|
||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING, nullable: nullable)
|
||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
|
||||
@@ -170,14 +164,12 @@ protected class OpenApiSchemaService {
|
||||
// 先加入缓存,再处理属性,避免循环引用导致无限递归
|
||||
_schemas.add(schemaId, schema)
|
||||
buildObjectProperties(schema, typeInfo)
|
||||
applySchemaTransformers(schema, schemaId)
|
||||
}
|
||||
return createReferenceSchema(schemaId, nullable)
|
||||
} else {
|
||||
// 强制内联:不缓存,不生成 $ref
|
||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
||||
buildObjectProperties(schema, typeInfo)
|
||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
}
|
||||
@@ -195,7 +187,6 @@ protected class OpenApiSchemaService {
|
||||
private func createInlineObjectSchema(typeInfo: TypeInfo, nullable: Bool): OpenApiSchema {
|
||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT, nullable: nullable)
|
||||
buildObjectProperties(schema, typeInfo)
|
||||
applySchemaTransformers(schema, typeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
|
||||
@@ -207,7 +198,7 @@ protected class OpenApiSchemaService {
|
||||
let propertyTypeInfo = underlyingType ?? property.typeInfo
|
||||
let propertySchema = buildSchema(propertyTypeInfo, underlyingType.isSome(), true)
|
||||
for (annotation in property.annotations) {
|
||||
if (let meta: IApiDescriptionMetadata <- annotation) {
|
||||
if (let meta: SimApiDoc <- annotation) {
|
||||
propertySchema.description = meta.description
|
||||
}
|
||||
}
|
||||
@@ -223,7 +214,7 @@ protected class OpenApiSchemaService {
|
||||
let variableTypeInfo = underlyingType ?? variable.typeInfo
|
||||
let variableSchema = buildSchema(variableTypeInfo, underlyingType.isSome(), true)
|
||||
for (annotation in variable.annotations) {
|
||||
if (let meta: IApiDescriptionMetadata <- annotation) {
|
||||
if (let meta: SimApiDoc <- annotation) {
|
||||
variableSchema.description = meta.description
|
||||
}
|
||||
}
|
||||
@@ -244,8 +235,6 @@ protected class OpenApiSchemaService {
|
||||
private func createArraySchema(elementTypeInfo: TypeInfo, preferReference: Bool): OpenApiSchema {
|
||||
let itemSchema = buildSchema(elementTypeInfo, false, preferReference)
|
||||
let schema = OpenApiSchema(`type`: OpenApiSchemaTypes.ARRAY, items: itemSchema)
|
||||
// transformer 面向当前数组 schema,本处使用容器类型名最准确;现阶段退化为元素类型名。
|
||||
applySchemaTransformers(schema, elementTypeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
|
||||
@@ -258,24 +247,12 @@ protected class OpenApiSchemaService {
|
||||
if (keyTypeInfo != OpenApiSchemaService._stringType) {
|
||||
// 非字符串 key 无法直接表达为标准 JSON object key;当前保守输出为封闭 object。
|
||||
schema.additionalProperties = false
|
||||
applySchemaTransformers(schema, keyTypeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
schema.additionalPropertiesSchema = buildSchema(valueTypeInfo, false, true)
|
||||
applySchemaTransformers(schema, valueTypeInfo.qualifiedName)
|
||||
return schema
|
||||
}
|
||||
|
||||
private func applySchemaTransformers(schema: OpenApiSchema, schemaName: String): Unit {
|
||||
if (_documentName.isEmpty() || _options.schemaTransformers.isEmpty()) {
|
||||
return
|
||||
}
|
||||
let context = OpenApiSchemaTransformerContext(_services, _documentName, schemaName)
|
||||
for (transformer in _options.schemaTransformers) {
|
||||
transformer.transform(schema, context)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
解析 Collection<T> 中的 T
|
||||
*/
|
||||
@@ -1,17 +1,32 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.io.*
|
||||
import std.reflect.*
|
||||
import stdx.encoding.json.stream.*
|
||||
import simcu::simapi.openapi.interfaces.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 响应类型元数据(供动态注册的路由指定返回类型)。
|
||||
*/
|
||||
public class ApiResponseTypeMetadata {
|
||||
/**
|
||||
* @brief 响应类型的类型信息。
|
||||
*/
|
||||
public let responseType: ?TypeInfo
|
||||
/**
|
||||
* @brief 创建响应类型元数据实例。
|
||||
* @param responseType 响应类型的类型信息。
|
||||
*/
|
||||
public init(responseType: ?TypeInfo) {
|
||||
this.responseType = responseType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 提供基于 JSON 的 OpenAPI 写入器实现。
|
||||
@@ -94,3 +109,40 @@ public class JsonOpenApiWriter <: IOpenApiWriter {
|
||||
_writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 表示带名称的服务标识。
|
||||
*/
|
||||
public class NamedService {
|
||||
/**
|
||||
* @brief 表示服务名称。
|
||||
*/
|
||||
public let name: String
|
||||
|
||||
/**
|
||||
* @brief 创建带名称的服务标识实例。
|
||||
* @param name 服务名称。
|
||||
*/
|
||||
public init(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Option类型操作
|
||||
*/
|
||||
protected class Nullable {
|
||||
/**
|
||||
* @brief 返回 Option 类型的底层类型。
|
||||
* @param typeInfo 要解析的类型信息。
|
||||
* @return 如果是 Option 类型则返回其底层类型,否则返回 `None`。
|
||||
*/
|
||||
public static func getUnderlyingType(typeInfo: TypeInfo): ?TypeInfo {
|
||||
let qualifiedName = typeInfo.qualifiedName
|
||||
if (!(qualifiedName.startsWith("Option<") && qualifiedName.endsWith('>'))) {
|
||||
return None
|
||||
}
|
||||
let underlyingTypeName = qualifiedName[7..qualifiedName.size - 1]
|
||||
return TypeInfo.get(underlyingTypeName)
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,10 @@ public class OpenApiUIMiddleware <: IMiddleware {
|
||||
private func serveFromWebRoot(context: HttpContext, relativePath: String): Bool {
|
||||
let openApiRoot = Path(_env.webRootPath).join(_wwwRootSubDir)
|
||||
let path = openApiRoot.join(relativePath).normalize()
|
||||
if (!exists(path) || !isPathWithinRoot(path)) {
|
||||
if (!exists(path)) {
|
||||
return false
|
||||
}
|
||||
if (!isPathWithinRoot(path)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.infrastructure
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
@@ -1,30 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
|
||||
import simcu::simapi.openapi.models.*
|
||||
import soulsoft_extensions_injection.*
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 文档提供器接口。
|
||||
*/
|
||||
public interface IDocumentProvider {
|
||||
/**
|
||||
* @brief 返回已注册的文档名称集合。
|
||||
* @return 当前可用的文档名称集合。
|
||||
*/
|
||||
func getDooucmentNames(): Collection<String>
|
||||
/**
|
||||
* @brief 按名称创建 OpenAPI 文档。
|
||||
* @param documentName 要创建的文档名称。
|
||||
* @return 生成后的 OpenAPI 文档。
|
||||
*/
|
||||
func create(documentName: String, services: IServiceProvider): OpenApiDocument
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
|
||||
/**
|
||||
* @brief 表示带名称的服务标识。
|
||||
*/
|
||||
public class NamedService {
|
||||
/**
|
||||
* @brief 表示服务名称。
|
||||
*/
|
||||
public let name: String
|
||||
|
||||
/**
|
||||
* @brief 创建带名称的服务标识实例。
|
||||
* @param name 服务名称。
|
||||
*/
|
||||
public init(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.services
|
||||
|
||||
import std.reflect.*
|
||||
import std.collection.*
|
||||
import simcu::simapi.openapi.models.*
|
||||
import simcu::simapi.openapi.transformers.*
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 文档生成选项。
|
||||
*/
|
||||
public class OpenApiOptions {
|
||||
private let _documentTransformers = ArrayList<IOpenApiDocumentTransformer>()
|
||||
private let _operationTransformer = ArrayList<IOpenApiOperationTransformer>()
|
||||
private let _schemaTransformers = ArrayList<IOpenApiSchemaTransformer>()
|
||||
|
||||
/**
|
||||
* 未标注 groupName 的接口是否进入本文档。
|
||||
* 多文档分组时,仅默认组(第一个 apiGroup 或 isDefault 的组)应设为 true。
|
||||
*/
|
||||
public var includeUnGrouped: Bool = false
|
||||
|
||||
/**
|
||||
* @brief 确定指定类型在 components/schemas 中使用的引用 ID。
|
||||
* 返回 None 时该 Schema 始终内联,不生成 $ref。
|
||||
*/
|
||||
public var createSchemaReferenceId: (TypeInfo) -> ?String = {typeInfo =>
|
||||
let name = typeInfo.name
|
||||
if (let Some(ltIdx) <- name.indexOf('<') && let Some(gtIdx) <- name.lastIndexOf('>') &&
|
||||
gtIdx > ltIdx) {
|
||||
let baseName = name[0..ltIdx]
|
||||
let argsStr = name[ltIdx + 1..gtIdx]
|
||||
let simpleName = if (let Some(dotIdx) <- argsStr.lastIndexOf('.')) {
|
||||
argsStr[dotIdx + 1..]
|
||||
} else {
|
||||
argsStr
|
||||
}
|
||||
"${baseName}Of${simpleName}"
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 返回当前文档转换器集合。
|
||||
* @return 已注册的文档转换器集合。
|
||||
*/
|
||||
protected prop documentTransformers: List<IOpenApiDocumentTransformer> {
|
||||
get() {
|
||||
_documentTransformers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 返回当前操作转换器集合。
|
||||
* @return 已注册的操作转换器集合。
|
||||
*/
|
||||
protected prop operationTransformers: List<IOpenApiOperationTransformer> {
|
||||
get() {
|
||||
_operationTransformer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 返回当前架构转换器集合。
|
||||
* @return 已注册的架构转换器集合。
|
||||
*/
|
||||
protected prop schemaTransformers: List<IOpenApiSchemaTransformer> {
|
||||
get() {
|
||||
_schemaTransformers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加文档转换器实例。
|
||||
* @param transformer 要添加的 OpenAPI 文档转换器。
|
||||
*/
|
||||
public func addDocumentTransformer(transformer: IOpenApiDocumentTransformer): Unit {
|
||||
_documentTransformers.add(transformer)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 以委托形式添加文档转换器。
|
||||
* @param transformer 表示文档转换逻辑的委托。
|
||||
*/
|
||||
public func addDocumentTransformer(transformer: (OpenApiDocument, OpenApiDocumentTransformerContext) -> Unit): Unit {
|
||||
_documentTransformers.add(DelegateOpenApiDocumentTransformer(transformer))
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加操作转换器实例。
|
||||
* @param transformer 要添加的 OpenAPI 操作转换器。
|
||||
*/
|
||||
public func addOperationTransformer(transformer: IOpenApiOperationTransformer): Unit {
|
||||
_operationTransformer.add(transformer)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 以委托形式添加操作转换器。
|
||||
* @param transformer 表示操作转换逻辑的委托。
|
||||
*/
|
||||
public func addOperationTransformer(transformer: (OpenApiOperation, OpenApiOperationTransformerContext) -> Unit): Unit {
|
||||
_operationTransformer.add(DelegateOpenApiOperationTransformer(transformer))
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加架构转换器实例。
|
||||
* @param transformer 要添加的 OpenAPI 架构转换器。
|
||||
*/
|
||||
public func addSchemaTransformer(transformer: IOpenApiSchemaTransformer): Unit {
|
||||
_schemaTransformers.add(transformer)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 以委托形式添加架构转换器。
|
||||
* @param transformer 表示架构转换逻辑的委托。
|
||||
*/
|
||||
public func addSchemaTransformer(transformer: (OpenApiSchema, OpenApiSchemaTransformerContext) -> Unit): Unit {
|
||||
_schemaTransformers.add(DelegateOpenApiSchemaTransformer(transformer))
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 将委托包装为 IOpenApiDocumentTransformer 的适配器实现。
|
||||
*/
|
||||
protected class DelegateOpenApiDocumentTransformer <: IOpenApiDocumentTransformer {
|
||||
private let _transformer: (OpenApiDocument, OpenApiDocumentTransformerContext) -> Unit
|
||||
|
||||
/**
|
||||
* @brief 创建委托文档转换器实例。
|
||||
* @param transformer 表示文档转换逻辑的委托。
|
||||
*/
|
||||
public init(transformer: (OpenApiDocument, OpenApiDocumentTransformerContext) -> Unit) {
|
||||
_transformer = transformer
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调用委托转换指定的 OpenAPI 文档。
|
||||
* @param document 要转换的 OpenAPI 文档。
|
||||
* @param context 文档转换器上下文。
|
||||
*/
|
||||
public func transform(document: OpenApiDocument, context: OpenApiDocumentTransformerContext): Unit {
|
||||
_transformer(document, context)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
protected class DelegateOpenApiOperationTransformer <: IOpenApiOperationTransformer {
|
||||
private let _transformer: (OpenApiOperation, OpenApiOperationTransformerContext) -> Unit
|
||||
|
||||
public init(transformer: (OpenApiOperation, OpenApiOperationTransformerContext) -> Unit) {
|
||||
_transformer = transformer
|
||||
}
|
||||
|
||||
public func transform(operation: OpenApiOperation, context: OpenApiOperationTransformerContext): Unit {
|
||||
_transformer(operation, context)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 将委托包装为 IOpenApiSchemaTransformer 的适配器实现。
|
||||
*/
|
||||
protected class DelegateOpenApiSchemaTransformer <: IOpenApiSchemaTransformer {
|
||||
private let _transformer: (OpenApiSchema, OpenApiSchemaTransformerContext) -> Unit
|
||||
|
||||
/**
|
||||
* @brief 创建委托架构转换器实例。
|
||||
* @param transformer 表示架构转换逻辑的委托。
|
||||
*/
|
||||
public init(transformer: (OpenApiSchema, OpenApiSchemaTransformerContext) -> Unit) {
|
||||
_transformer = transformer
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调用委托转换指定的 OpenAPI 架构。
|
||||
* @param schema 要转换的 OpenAPI 架构。
|
||||
* @param context 架构转换器上下文。
|
||||
*/
|
||||
public func transform(schema: OpenApiSchema, context: OpenApiSchemaTransformerContext): Unit {
|
||||
_transformer(schema, context)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 文档转换器接口。
|
||||
*/
|
||||
public interface IOpenApiDocumentTransformer {
|
||||
/**
|
||||
* @brief 转换指定的 OpenAPI 文档。
|
||||
* @param document 要转换的 OpenAPI 文档。
|
||||
* @param context 文档转换器上下文。
|
||||
*/
|
||||
func transform(document: OpenApiDocument, context: OpenApiDocumentTransformerContext): Unit
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 操作转换器接口。
|
||||
*/
|
||||
public interface IOpenApiOperationTransformer {
|
||||
/**
|
||||
* @brief 转换指定的 OpenAPI 操作对象。
|
||||
* @param operation 要转换的 OpenAPI 操作对象。
|
||||
* @param context 操作转换器上下文。
|
||||
*/
|
||||
func transform(operation: OpenApiOperation, context: OpenApiOperationTransformerContext): Unit
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import simcu::simapi.openapi.models.*
|
||||
|
||||
/**
|
||||
* @brief 定义 OpenAPI 架构转换器接口。
|
||||
*/
|
||||
public interface IOpenApiSchemaTransformer {
|
||||
/**
|
||||
* @brief 转换指定的 OpenAPI 架构。
|
||||
* @param schema 要转换的 OpenAPI 架构。
|
||||
* @param context 架构转换器上下文。
|
||||
*/
|
||||
func transform(schema: OpenApiSchema, context: OpenApiSchemaTransformerContext): Unit
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import soulsoft_extensions_injection.*
|
||||
|
||||
/**
|
||||
* @brief 提供文档转换器执行时的上下文信息。
|
||||
*/
|
||||
public class OpenApiDocumentTransformerContext {
|
||||
/**
|
||||
* @brief 当前正在生成的文档名称。
|
||||
*/
|
||||
public let documentName: String
|
||||
/**
|
||||
* @brief 请求作用域的服务提供器。
|
||||
*/
|
||||
public let services: IServiceProvider
|
||||
|
||||
/**
|
||||
* @brief 创建文档转换器上下文实例。
|
||||
* @param services 请求作用域的服务提供器。
|
||||
* @param documentName 当前正在生成的文档名称。
|
||||
*/
|
||||
public init(services: IServiceProvider, documentName: String) {
|
||||
this.services = services
|
||||
this.documentName = documentName
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import soulsoft_web_mvc.controllers.*
|
||||
import soulsoft_extensions_injection.*
|
||||
|
||||
/**
|
||||
* @brief 提供操作转换器执行时的上下文信息。
|
||||
*/
|
||||
public class OpenApiOperationTransformerContext {
|
||||
/**
|
||||
* @brief 当前正在生成的文档名称。
|
||||
*/
|
||||
public let documentName: String
|
||||
/**
|
||||
* @brief 请求作用域的服务提供器。
|
||||
*/
|
||||
public let services: IServiceProvider
|
||||
/**
|
||||
* @brief 当前操作关联的控制器动作描述。
|
||||
*/
|
||||
public let description: ?ControllerActionDescriptor
|
||||
|
||||
/**
|
||||
* @brief 创建操作转换器上下文实例。
|
||||
* @param services 请求作用域的服务提供器。
|
||||
* @param documentName 当前正在生成的文档名称。
|
||||
* @param description 当前操作关联的控制器动作描述。
|
||||
*/
|
||||
public init(services: IServiceProvider, documentName: String, description: ?ControllerActionDescriptor) {
|
||||
this.services = services
|
||||
this.documentName = documentName
|
||||
this.description = description
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
/*
|
||||
* Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
|
||||
* This source file is licensed under the MIT License found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.transformers
|
||||
|
||||
import soulsoft_extensions_injection.*
|
||||
|
||||
/**
|
||||
* @brief 提供架构转换器执行时的上下文信息。
|
||||
*/
|
||||
public class OpenApiSchemaTransformerContext {
|
||||
/**
|
||||
* @brief 当前正在生成的文档名称。
|
||||
*/
|
||||
public let documentName: String
|
||||
/**
|
||||
* @brief 请求作用域的服务提供器。
|
||||
*/
|
||||
public let services: IServiceProvider
|
||||
/**
|
||||
* @brief 当前架构在 components/schemas 中的键名(即类型限定名)。
|
||||
*/
|
||||
public let schemaName: String
|
||||
|
||||
/**
|
||||
* @brief 创建架构转换器上下文实例。
|
||||
* @param services 请求作用域的服务提供器。
|
||||
* @param documentName 当前正在生成的文档名称。
|
||||
* @param schemaName 当前架构在 components/schemas 中的键名。
|
||||
*/
|
||||
public init(services: IServiceProvider, documentName: String, schemaName: String) {
|
||||
this.services = services
|
||||
this.documentName = documentName
|
||||
this.schemaName = schemaName
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package simcu::simapi.openapi.infrastructure
|
||||
package simcu::simapi.openapi
|
||||
|
||||
import std.collection.*
|
||||
|
||||
@@ -44,8 +44,7 @@ import simcu::simapi.interfaces.*
|
||||
import simcu::simapi.logger.*
|
||||
import simcu::simapi.middlewares.*
|
||||
import simcu::simapi.openapi.*
|
||||
import simcu::simapi.openapi.annotations.*
|
||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAnnotation}
|
||||
import simcu::simapi.annotations.{SimApiAuth as SimApiAuthAnnotation, SimApiDoc}
|
||||
|
||||
/**
|
||||
* SimApi 扩展入口。
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# 生成 SwaggerUIResources.cj:将 resources/openapi/ 下静态资源以 Base64 内联为 CJ 源常量。
|
||||
# 生成 swagger_ui_resources.cj:将 resources/openapi/ 下静态资源以 Base64 内联为 CJ 源常量。
|
||||
# 用法:pwsh tools/gen-swagger-ui-resources.ps1
|
||||
# 资源更新后重新运行此脚本即可。
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$inDir = Join-Path $root "resources\openapi"
|
||||
$outFile = Join-Path $root "src\openapi\SwaggerUIResources.cj"
|
||||
$outFile = Join-Path $root "src\openapi\swagger_ui_resources.cj"
|
||||
|
||||
$files = @(
|
||||
@{ Name = "allHtml"; File = "all.html" },
|
||||
|
||||
Reference in New Issue
Block a user