From 06884a920ce9c8856ddd705a171384b9732638be Mon Sep 17 00:00:00 2001 From: xRain Date: Tue, 1 Sep 2026 23:36:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B2=BE=E7=AE=80=E4=BA=86openapi=E7=9B=B8?= =?UTF-8?q?=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cjpm.lock | 20 +-- src/annotations/simapi_doc.cj | 63 ++++++++ src/controllers/simapi_common_controller.cj | 3 +- src/openapi/Modules.cj | 9 -- .../OpenApiEndpointConventionBuilder.cj | 38 ----- .../OpenApiEndpointRouteBuilderExtension.cj | 65 -------- .../OpenApiServiceCollectionExtensions.cj | 71 -------- src/openapi/OpenApiUIMiddlewareExtensions.cj | 41 ----- .../SimApiEndpointMetadataExtensions.cj | 56 ------- src/openapi/annotations/simapi_doc.cj | 107 ------------ src/openapi/infrastructure/Nullable.cj | 28 ---- .../infrastructure/OpenApiConstants.cj | 13 -- src/openapi/interfaces.cj | 97 +++++++++++ .../interfaces/IOpenApiSerializable.cj | 21 --- .../metadata/IApiDescriptionMetadata.cj | 21 --- .../metadata/IApiGroupNamesProvider.cj | 21 --- src/openapi/metadata/IApiNameMetadata.cj | 21 --- .../metadata/IApiResponseMetadataProvider.cj | 28 ---- .../metadata/IApiResponseTypeMetadata.cj | 18 --- src/openapi/metadata/IApiSummaryMetadata.cj | 21 --- src/openapi/metadata/IApiTagsMetadata.cj | 21 --- .../metadata/IApiVisibilityProvider.cj | 21 --- src/openapi/models/Modules.cj | 9 -- ...piComponents.cj => open_api_components.cj} | 0 ...{OpenApiContact.cj => open_api_contact.cj} | 0 ...penApiDocument.cj => open_api_document.cj} | 0 ...y.cj => open_api_extensible_dictionary.cj} | 0 .../{OpenApiInfo.cj => open_api_info.cj} | 0 ...{OpenApiLicense.cj => open_api_license.cj} | 0 ...ApiMediaType.cj => open_api_media_type.cj} | 0 ...nApiOperation.cj => open_api_operation.cj} | 0 ...nApiParameter.cj => open_api_parameter.cj} | 0 ...enApiPathItem.cj => open_api_path_item.cj} | 0 .../{OpenApiPaths.cj => open_api_paths.cj} | 0 ...nApiReference.cj => open_api_reference.cj} | 0 ...equestBody.cj => open_api_request_body.cj} | 0 ...penApiResponse.cj => open_api_response.cj} | 0 ...nApiResponses.cj => open_api_responses.cj} | 0 .../{OpenApiSchema.cj => open_api_schema.cj} | 0 ...chemaTypes.cj => open_api_schema_types.cj} | 0 ...nt.cj => open_api_security_requirement.cj} | 0 ...yScheme.cj => open_api_security_scheme.cj} | 0 .../{OpenApiServer.cj => open_api_server.cj} | 0 .../models/{OpenApiTag.cj => open_api_tag.cj} | 0 .../{OperationType.cj => operation_type.cj} | 0 ...meterLocation.cj => parameter_location.cj} | 0 .../{ReferenceType.cj => reference_type.cj} | 0 .../IOpenApiWriter.cj => models/serialize.cj} | 18 ++- ...vider.cj => open_api_document_provider.cj} | 16 +- ...ervice.cj => open_api_document_service.cj} | 49 ++---- src/openapi/open_api_extensions.cj | 152 ++++++++++++++++++ src/openapi/open_api_options.cj | 46 ++++++ ...rvice.cj => open_api_parameter_service.cj} | 3 +- ...aService.cj => open_api_schema_service.cj} | 31 +--- ...onOpenApiWriter.cj => open_api_support.cj} | 64 +++++++- ...iddleware.cj => open_api_ui_middleware.cj} | 0 ...flectUtilities.cj => reflect_utilities.cj} | 2 +- src/openapi/services/IDocumentProvider.cj | 30 ---- src/openapi/services/NamedService.cj | 28 ---- src/openapi/services/OpenApiOptions.cj | 129 --------------- ...UIResources.cj => swagger_ui_resources.cj} | 0 .../DelegateOpenApiDocumentTransformer.cj | 36 ----- .../DelegateOpenApiOperationTransformer.cj | 24 --- .../DelegateOpenApiSchemaTransformer.cj | 36 ----- .../IOpenApiDocumentTransformer.cj | 24 --- .../IOpenApiOperationTransformer.cj | 24 --- .../transformers/IOpenApiSchemaTransformer.cj | 24 --- .../OpenApiDocumentTransformerContext.cj | 36 ----- .../OpenApiOperationTransformerContext.cj | 43 ----- .../OpenApiSchemaTransformerContext.cj | 42 ----- .../TypeNameParser.cj => type_name_parser.cj} | 2 +- src/simapi_extensions.cj | 3 +- tools/gen-swagger-ui-resources.ps1 | 4 +- 73 files changed, 465 insertions(+), 1214 deletions(-) create mode 100644 src/annotations/simapi_doc.cj delete mode 100644 src/openapi/Modules.cj delete mode 100644 src/openapi/OpenApiEndpointConventionBuilder.cj delete mode 100644 src/openapi/OpenApiEndpointRouteBuilderExtension.cj delete mode 100644 src/openapi/OpenApiServiceCollectionExtensions.cj delete mode 100644 src/openapi/OpenApiUIMiddlewareExtensions.cj delete mode 100644 src/openapi/SimApiEndpointMetadataExtensions.cj delete mode 100644 src/openapi/annotations/simapi_doc.cj delete mode 100644 src/openapi/infrastructure/Nullable.cj delete mode 100644 src/openapi/infrastructure/OpenApiConstants.cj create mode 100644 src/openapi/interfaces.cj delete mode 100644 src/openapi/interfaces/IOpenApiSerializable.cj delete mode 100644 src/openapi/metadata/IApiDescriptionMetadata.cj delete mode 100644 src/openapi/metadata/IApiGroupNamesProvider.cj delete mode 100644 src/openapi/metadata/IApiNameMetadata.cj delete mode 100644 src/openapi/metadata/IApiResponseMetadataProvider.cj delete mode 100644 src/openapi/metadata/IApiResponseTypeMetadata.cj delete mode 100644 src/openapi/metadata/IApiSummaryMetadata.cj delete mode 100644 src/openapi/metadata/IApiTagsMetadata.cj delete mode 100644 src/openapi/metadata/IApiVisibilityProvider.cj delete mode 100644 src/openapi/models/Modules.cj rename src/openapi/models/{OpenApiComponents.cj => open_api_components.cj} (100%) rename src/openapi/models/{OpenApiContact.cj => open_api_contact.cj} (100%) rename src/openapi/models/{OpenApiDocument.cj => open_api_document.cj} (100%) rename src/openapi/models/{OpenApiExtensibleDictionary.cj => open_api_extensible_dictionary.cj} (100%) rename src/openapi/models/{OpenApiInfo.cj => open_api_info.cj} (100%) rename src/openapi/models/{OpenApiLicense.cj => open_api_license.cj} (100%) rename src/openapi/models/{OpenApiMediaType.cj => open_api_media_type.cj} (100%) rename src/openapi/models/{OpenApiOperation.cj => open_api_operation.cj} (100%) rename src/openapi/models/{OpenApiParameter.cj => open_api_parameter.cj} (100%) rename src/openapi/models/{OpenApiPathItem.cj => open_api_path_item.cj} (100%) rename src/openapi/models/{OpenApiPaths.cj => open_api_paths.cj} (100%) rename src/openapi/models/{OpenApiReference.cj => open_api_reference.cj} (100%) rename src/openapi/models/{OpenApiRequestBody.cj => open_api_request_body.cj} (100%) rename src/openapi/models/{OpenApiResponse.cj => open_api_response.cj} (100%) rename src/openapi/models/{OpenApiResponses.cj => open_api_responses.cj} (100%) rename src/openapi/models/{OpenApiSchema.cj => open_api_schema.cj} (100%) rename src/openapi/models/{OpenApiSchemaTypes.cj => open_api_schema_types.cj} (100%) rename src/openapi/models/{OpenApiSecurityRequirement.cj => open_api_security_requirement.cj} (100%) rename src/openapi/models/{OpenApiSecurityScheme.cj => open_api_security_scheme.cj} (100%) rename src/openapi/models/{OpenApiServer.cj => open_api_server.cj} (100%) rename src/openapi/models/{OpenApiTag.cj => open_api_tag.cj} (100%) rename src/openapi/models/{OperationType.cj => operation_type.cj} (100%) rename src/openapi/models/{ParameterLocation.cj => parameter_location.cj} (100%) rename src/openapi/models/{ReferenceType.cj => reference_type.cj} (100%) rename src/openapi/{interfaces/IOpenApiWriter.cj => models/serialize.cj} (80%) rename src/openapi/{services/OpenApiDocumentProvider.cj => open_api_document_provider.cj} (75%) rename src/openapi/{services/OpenApiDocumentService.cj => open_api_document_service.cj} (89%) create mode 100644 src/openapi/open_api_extensions.cj create mode 100644 src/openapi/open_api_options.cj rename src/openapi/{services/OpenApiParameterService.cj => open_api_parameter_service.cj} (97%) rename src/openapi/{services/OpenApiSchemaService.cj => open_api_schema_service.cj} (88%) rename src/openapi/{services/JsonOpenApiWriter.cj => open_api_support.cj} (56%) rename src/openapi/{OpenApiUIMiddleware.cj => open_api_ui_middleware.cj} (100%) rename src/openapi/{infrastructure/ReflectUtilities.cj => reflect_utilities.cj} (98%) delete mode 100644 src/openapi/services/IDocumentProvider.cj delete mode 100644 src/openapi/services/NamedService.cj delete mode 100644 src/openapi/services/OpenApiOptions.cj rename src/openapi/{SwaggerUIResources.cj => swagger_ui_resources.cj} (100%) delete mode 100644 src/openapi/transformers/DelegateOpenApiDocumentTransformer.cj delete mode 100644 src/openapi/transformers/DelegateOpenApiOperationTransformer.cj delete mode 100644 src/openapi/transformers/DelegateOpenApiSchemaTransformer.cj delete mode 100644 src/openapi/transformers/IOpenApiDocumentTransformer.cj delete mode 100644 src/openapi/transformers/IOpenApiOperationTransformer.cj delete mode 100644 src/openapi/transformers/IOpenApiSchemaTransformer.cj delete mode 100644 src/openapi/transformers/OpenApiDocumentTransformerContext.cj delete mode 100644 src/openapi/transformers/OpenApiOperationTransformerContext.cj delete mode 100644 src/openapi/transformers/OpenApiSchemaTransformerContext.cj rename src/openapi/{infrastructure/TypeNameParser.cj => type_name_parser.cj} (98%) diff --git a/cjpm.lock b/cjpm.lock index 2c3c156..46ae02f 100644 --- a/cjpm.lock +++ b/cjpm.lock @@ -3,18 +3,18 @@ version = 0 [requires] soulsoft_extensions_hosting = {version = "1.0.20260528"} soulsoft_extensions_options_configuration = {version = "1.0.20260528"} + soulsoft_web_mvc = {version = "1.0.20260528"} soulsoft_extensions_logging_console = {version = "1.0.20260528"} + soulsoft_web_http = {version = "1.0.20260528"} + redis = {version = "1.0.20260627"} + soulsoft_web_cors = {version = "1.0.20260528"} + "simcu::serialization" = {version = "1.2.1"} + soulsoft_web_routing = {version = "1.0.20260528"} soulsoft_extensions_logging = {version = "1.0.20260528"} soulsoft_serialization = {version = "1.0.20260528"} - soulsoft_extensions_options = {version = "1.0.20260528"} - soulsoft_web_routing = {version = "1.0.20260528"} - soulsoft_identity_claims = {version = "1.0.20260528"} soulsoft_extensions_logging_configuration = {version = "1.0.20260528"} - soulsoft_web_http = {version = "1.0.20260528"} - soulsoft_extensions_configuration = {version = "1.0.20260528"} - soulsoft_web_cors = {version = "1.0.20260528"} - soulsoft_extensions_injection = {version = "1.0.20260528"} soulsoft_web_hosting = {version = "1.0.20260528"} - soulsoft_web_mvc = {version = "1.0.20260528"} - redis = {version = "1.0.20260627"} - "simcu::serialization" = {version = "1.2.1"} + soulsoft_extensions_configuration = {version = "1.0.20260528"} + soulsoft_extensions_injection = {version = "1.0.20260528"} + soulsoft_extensions_options = {version = "1.0.20260528"} + soulsoft_identity_claims = {version = "1.0.20260528"} diff --git a/src/annotations/simapi_doc.cj b/src/annotations/simapi_doc.cj new file mode 100644 index 0000000..126047f --- /dev/null +++ b/src/annotations/simapi_doc.cj @@ -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 + } +} diff --git a/src/controllers/simapi_common_controller.cj b/src/controllers/simapi_common_controller.cj index a479b54..99d63bd 100644 --- a/src/controllers/simapi_common_controller.cj +++ b/src/controllers/simapi_common_controller.cj @@ -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、用户信息。 diff --git a/src/openapi/Modules.cj b/src/openapi/Modules.cj deleted file mode 100644 index 0218bc3..0000000 --- a/src/openapi/Modules.cj +++ /dev/null @@ -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.* \ No newline at end of file diff --git a/src/openapi/OpenApiEndpointConventionBuilder.cj b/src/openapi/OpenApiEndpointConventionBuilder.cj deleted file mode 100644 index 68e97b3..0000000 --- a/src/openapi/OpenApiEndpointConventionBuilder.cj +++ /dev/null @@ -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 - } -} diff --git a/src/openapi/OpenApiEndpointRouteBuilderExtension.cj b/src/openapi/OpenApiEndpointRouteBuilderExtension.cj deleted file mode 100644 index 3d53473..0000000 --- a/src/openapi/OpenApiEndpointRouteBuilderExtension.cj +++ /dev/null @@ -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() - 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() - if (!callSiteFactory.isService()) { - 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.") - } - } -} diff --git a/src/openapi/OpenApiServiceCollectionExtensions.cj b/src/openapi/OpenApiServiceCollectionExtensions.cj deleted file mode 100644 index 87823c0..0000000 --- a/src/openapi/OpenApiServiceCollectionExtensions.cj +++ /dev/null @@ -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(documentName, configureOptions) - this.addSingleton(NamedService(documentName)) - this.addSingleton() - return this - } -} diff --git a/src/openapi/OpenApiUIMiddlewareExtensions.cj b/src/openapi/OpenApiUIMiddlewareExtensions.cj deleted file mode 100644 index 736313c..0000000 --- a/src/openapi/OpenApiUIMiddlewareExtensions.cj +++ /dev/null @@ -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() - } - - private func verifyOpenApiServicesAreRegistered() { - let callSiteFactory = this.services.getOrThrow() - if (!callSiteFactory.isService()) { - 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.") - } - } -} diff --git a/src/openapi/SimApiEndpointMetadataExtensions.cj b/src/openapi/SimApiEndpointMetadataExtensions.cj deleted file mode 100644 index 7d722e6..0000000 --- a/src/openapi/SimApiEndpointMetadataExtensions.cj +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/src/openapi/annotations/simapi_doc.cj b/src/openapi/annotations/simapi_doc.cj deleted file mode 100644 index 731fffa..0000000 --- a/src/openapi/annotations/simapi_doc.cj +++ /dev/null @@ -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 - } - } -} diff --git a/src/openapi/infrastructure/Nullable.cj b/src/openapi/infrastructure/Nullable.cj deleted file mode 100644 index 264e922..0000000 --- a/src/openapi/infrastructure/Nullable.cj +++ /dev/null @@ -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) - } -} diff --git a/src/openapi/infrastructure/OpenApiConstants.cj b/src/openapi/infrastructure/OpenApiConstants.cj deleted file mode 100644 index 5aa23e5..0000000 --- a/src/openapi/infrastructure/OpenApiConstants.cj +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/src/openapi/interfaces.cj b/src/openapi/interfaces.cj new file mode 100644 index 0000000..aa265b4 --- /dev/null +++ b/src/openapi/interfaces.cj @@ -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 + /** + * @brief 按名称创建 OpenAPI 文档。 + * @param documentName 要创建的文档名称。 + * @return 生成后的 OpenAPI 文档。 + */ + func create(documentName: String, services: IServiceProvider): OpenApiDocument +} diff --git a/src/openapi/interfaces/IOpenApiSerializable.cj b/src/openapi/interfaces/IOpenApiSerializable.cj deleted file mode 100644 index d54f334..0000000 --- a/src/openapi/interfaces/IOpenApiSerializable.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/metadata/IApiDescriptionMetadata.cj b/src/openapi/metadata/IApiDescriptionMetadata.cj deleted file mode 100644 index 9b801e9..0000000 --- a/src/openapi/metadata/IApiDescriptionMetadata.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/metadata/IApiGroupNamesProvider.cj b/src/openapi/metadata/IApiGroupNamesProvider.cj deleted file mode 100644 index 7352a0c..0000000 --- a/src/openapi/metadata/IApiGroupNamesProvider.cj +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/openapi/metadata/IApiNameMetadata.cj b/src/openapi/metadata/IApiNameMetadata.cj deleted file mode 100644 index e0bc44c..0000000 --- a/src/openapi/metadata/IApiNameMetadata.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/metadata/IApiResponseMetadataProvider.cj b/src/openapi/metadata/IApiResponseMetadataProvider.cj deleted file mode 100644 index 3fe2d44..0000000 --- a/src/openapi/metadata/IApiResponseMetadataProvider.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/metadata/IApiResponseTypeMetadata.cj b/src/openapi/metadata/IApiResponseTypeMetadata.cj deleted file mode 100644 index 0d36389..0000000 --- a/src/openapi/metadata/IApiResponseTypeMetadata.cj +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/openapi/metadata/IApiSummaryMetadata.cj b/src/openapi/metadata/IApiSummaryMetadata.cj deleted file mode 100644 index bf298a7..0000000 --- a/src/openapi/metadata/IApiSummaryMetadata.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/metadata/IApiTagsMetadata.cj b/src/openapi/metadata/IApiTagsMetadata.cj deleted file mode 100644 index c21c7ad..0000000 --- a/src/openapi/metadata/IApiTagsMetadata.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/metadata/IApiVisibilityProvider.cj b/src/openapi/metadata/IApiVisibilityProvider.cj deleted file mode 100644 index f73e64e..0000000 --- a/src/openapi/metadata/IApiVisibilityProvider.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/models/Modules.cj b/src/openapi/models/Modules.cj deleted file mode 100644 index 8b32e7c..0000000 --- a/src/openapi/models/Modules.cj +++ /dev/null @@ -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.* \ No newline at end of file diff --git a/src/openapi/models/OpenApiComponents.cj b/src/openapi/models/open_api_components.cj similarity index 100% rename from src/openapi/models/OpenApiComponents.cj rename to src/openapi/models/open_api_components.cj diff --git a/src/openapi/models/OpenApiContact.cj b/src/openapi/models/open_api_contact.cj similarity index 100% rename from src/openapi/models/OpenApiContact.cj rename to src/openapi/models/open_api_contact.cj diff --git a/src/openapi/models/OpenApiDocument.cj b/src/openapi/models/open_api_document.cj similarity index 100% rename from src/openapi/models/OpenApiDocument.cj rename to src/openapi/models/open_api_document.cj diff --git a/src/openapi/models/OpenApiExtensibleDictionary.cj b/src/openapi/models/open_api_extensible_dictionary.cj similarity index 100% rename from src/openapi/models/OpenApiExtensibleDictionary.cj rename to src/openapi/models/open_api_extensible_dictionary.cj diff --git a/src/openapi/models/OpenApiInfo.cj b/src/openapi/models/open_api_info.cj similarity index 100% rename from src/openapi/models/OpenApiInfo.cj rename to src/openapi/models/open_api_info.cj diff --git a/src/openapi/models/OpenApiLicense.cj b/src/openapi/models/open_api_license.cj similarity index 100% rename from src/openapi/models/OpenApiLicense.cj rename to src/openapi/models/open_api_license.cj diff --git a/src/openapi/models/OpenApiMediaType.cj b/src/openapi/models/open_api_media_type.cj similarity index 100% rename from src/openapi/models/OpenApiMediaType.cj rename to src/openapi/models/open_api_media_type.cj diff --git a/src/openapi/models/OpenApiOperation.cj b/src/openapi/models/open_api_operation.cj similarity index 100% rename from src/openapi/models/OpenApiOperation.cj rename to src/openapi/models/open_api_operation.cj diff --git a/src/openapi/models/OpenApiParameter.cj b/src/openapi/models/open_api_parameter.cj similarity index 100% rename from src/openapi/models/OpenApiParameter.cj rename to src/openapi/models/open_api_parameter.cj diff --git a/src/openapi/models/OpenApiPathItem.cj b/src/openapi/models/open_api_path_item.cj similarity index 100% rename from src/openapi/models/OpenApiPathItem.cj rename to src/openapi/models/open_api_path_item.cj diff --git a/src/openapi/models/OpenApiPaths.cj b/src/openapi/models/open_api_paths.cj similarity index 100% rename from src/openapi/models/OpenApiPaths.cj rename to src/openapi/models/open_api_paths.cj diff --git a/src/openapi/models/OpenApiReference.cj b/src/openapi/models/open_api_reference.cj similarity index 100% rename from src/openapi/models/OpenApiReference.cj rename to src/openapi/models/open_api_reference.cj diff --git a/src/openapi/models/OpenApiRequestBody.cj b/src/openapi/models/open_api_request_body.cj similarity index 100% rename from src/openapi/models/OpenApiRequestBody.cj rename to src/openapi/models/open_api_request_body.cj diff --git a/src/openapi/models/OpenApiResponse.cj b/src/openapi/models/open_api_response.cj similarity index 100% rename from src/openapi/models/OpenApiResponse.cj rename to src/openapi/models/open_api_response.cj diff --git a/src/openapi/models/OpenApiResponses.cj b/src/openapi/models/open_api_responses.cj similarity index 100% rename from src/openapi/models/OpenApiResponses.cj rename to src/openapi/models/open_api_responses.cj diff --git a/src/openapi/models/OpenApiSchema.cj b/src/openapi/models/open_api_schema.cj similarity index 100% rename from src/openapi/models/OpenApiSchema.cj rename to src/openapi/models/open_api_schema.cj diff --git a/src/openapi/models/OpenApiSchemaTypes.cj b/src/openapi/models/open_api_schema_types.cj similarity index 100% rename from src/openapi/models/OpenApiSchemaTypes.cj rename to src/openapi/models/open_api_schema_types.cj diff --git a/src/openapi/models/OpenApiSecurityRequirement.cj b/src/openapi/models/open_api_security_requirement.cj similarity index 100% rename from src/openapi/models/OpenApiSecurityRequirement.cj rename to src/openapi/models/open_api_security_requirement.cj diff --git a/src/openapi/models/OpenApiSecurityScheme.cj b/src/openapi/models/open_api_security_scheme.cj similarity index 100% rename from src/openapi/models/OpenApiSecurityScheme.cj rename to src/openapi/models/open_api_security_scheme.cj diff --git a/src/openapi/models/OpenApiServer.cj b/src/openapi/models/open_api_server.cj similarity index 100% rename from src/openapi/models/OpenApiServer.cj rename to src/openapi/models/open_api_server.cj diff --git a/src/openapi/models/OpenApiTag.cj b/src/openapi/models/open_api_tag.cj similarity index 100% rename from src/openapi/models/OpenApiTag.cj rename to src/openapi/models/open_api_tag.cj diff --git a/src/openapi/models/OperationType.cj b/src/openapi/models/operation_type.cj similarity index 100% rename from src/openapi/models/OperationType.cj rename to src/openapi/models/operation_type.cj diff --git a/src/openapi/models/ParameterLocation.cj b/src/openapi/models/parameter_location.cj similarity index 100% rename from src/openapi/models/ParameterLocation.cj rename to src/openapi/models/parameter_location.cj diff --git a/src/openapi/models/ReferenceType.cj b/src/openapi/models/reference_type.cj similarity index 100% rename from src/openapi/models/ReferenceType.cj rename to src/openapi/models/reference_type.cj diff --git a/src/openapi/interfaces/IOpenApiWriter.cj b/src/openapi/models/serialize.cj similarity index 80% rename from src/openapi/interfaces/IOpenApiWriter.cj rename to src/openapi/models/serialize.cj index 42590f0..5172661 100644 --- a/src/openapi/interfaces/IOpenApiWriter.cj +++ b/src/openapi/models/serialize.cj @@ -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 文档写入接口。 diff --git a/src/openapi/services/OpenApiDocumentProvider.cj b/src/openapi/open_api_document_provider.cj similarity index 75% rename from src/openapi/services/OpenApiDocumentProvider.cj rename to src/openapi/open_api_document_provider.cj index 855994f..265237a 100644 --- a/src/openapi/services/OpenApiDocumentProvider.cj +++ b/src/openapi/open_api_document_provider.cj @@ -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) } } diff --git a/src/openapi/services/OpenApiDocumentService.cj b/src/openapi/open_api_document_service.cj similarity index 89% rename from src/openapi/services/OpenApiDocumentService.cj rename to src/openapi/open_api_document_service.cj index 5243cee..df3f67e 100644 --- a/src/openapi/services/OpenApiDocumentService.cj +++ b/src/openapi/open_api_document_service.cj @@ -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>().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() 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() |> + let rawGroupNames = endpoint.metadata.getOrderedMetadata() |> 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 {f => f.summary.isSome()}) { + if (let Some(metadata) <- endpoint.metadata.getLastMetadata {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 {f => f.description.isSome()}) { + .getLastMetadata {f => f.description.isSome()}) { if (let Some(description) <- metadata.description) { operation.description = description } } // operationId - if (let Some(metadata) <- endpoint.metadata.getLastMetadata {f => f.name.isSome()}) { + if (let Some(metadata) <- endpoint.metadata.getLastMetadata {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() // tags - if (let Some(metadata) <- endpoint.metadata.getLastMetadata {f => f.tags.isSome()}) { + if (let Some(metadata) <- endpoint.metadata.getLastMetadata {f => f.tags.isSome()}) { if (let Some(tags) <- metadata.tags) { for (name in tags.split(',')) { result.add(OpenApiTag(name: name)) @@ -251,7 +243,7 @@ protected class OpenApiDocumentService { if (let Some(actionDescriptor) <- actionDescriptor) { result.add(OpenApiTag(name: actionDescriptor.controllerName)) } else { - result.add(OpenApiTag(name: OpenApiConstants.DefaultOpenApiName)) + result.add(OpenApiTag(name: "default")) } return result @@ -299,12 +291,12 @@ protected class OpenApiDocumentService { private func createOpenApiOperationResponses(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) { let responses = OpenApiResponses() - // 获取action的返回类型:优先从 actionDescriptor,其次从 IApiResponseTypeMetadata(动态路由) + // 获取action的返回类型:优先从 actionDescriptor,其次从 ApiResponseTypeMetadata(动态路由) let returnType: ?TypeInfo = if (let Some(actionDescriptor) <- actionDescriptor) { Nullable.getUnderlyingType(actionDescriptor.actionFunction.returnType) ?? actionDescriptor .actionFunction .returnType - } else if (let Some(meta) <- endpoint.metadata.getMetadata()) { + } else if (let Some(meta) <- endpoint.metadata.getMetadata()) { 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 {f => f.ignore}) { + if (let Some(metadata) <- endpoint.metadata.getLastMetadata {f => f.ignore}) { return true } return false } } - -extend EndpointMetadataCollection { - /** - * @brief 返回最后一个满足条件的元数据。 - * @param filter 用于筛选元数据的条件。 - * @return 最后一个满足条件的元数据;如果不存在则返回 `None`。 - */ - public func getLastMetadata(filter: (T) -> Bool): ?T { - let metadatas = this.getOrderedMetadata() - var index = metadatas.size - 1 - while (index >= 0) { - let metadata = metadatas[index] - if (filter(metadata)) { - return metadata - } - index-- - } - - return None - } -} diff --git a/src/openapi/open_api_extensions.cj b/src/openapi/open_api_extensions.cj new file mode 100644 index 0000000..a7d3f60 --- /dev/null +++ b/src/openapi/open_api_extensions.cj @@ -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() + 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() + if (!callSiteFactory.isService()) { + 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(documentName, configureOptions) + this.addSingleton(NamedService(documentName)) + this.addSingleton() + return this + } +} + +/** + * @brief 启用 OpenAPI UI 中间件。 + */ +extend ApplicationBuilder <: OpenApiUIMiddlewareExtensions{ + /** + * @brief 启用 OpenAPI UI 中间件。 + */ + public func useOpenApiUI(): Unit { + verifyOpenApiServicesAreRegistered() + use() + } + + private func verifyOpenApiServicesAreRegistered() { + let callSiteFactory = this.services.getOrThrow() + if (!callSiteFactory.isService()) { + 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(filter: (T) -> Bool): ?T { + let metadatas = this.getOrderedMetadata() + var index = metadatas.size - 1 + while (index >= 0) { + let metadata = metadatas[index] + if (filter(metadata)) { + return metadata + } + index-- + } + + return None + } +} diff --git a/src/openapi/open_api_options.cj b/src/openapi/open_api_options.cj new file mode 100644 index 0000000..c799923 --- /dev/null +++ b/src/openapi/open_api_options.cj @@ -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 + } + } +} diff --git a/src/openapi/services/OpenApiParameterService.cj b/src/openapi/open_api_parameter_service.cj similarity index 97% rename from src/openapi/services/OpenApiParameterService.cj rename to src/openapi/open_api_parameter_service.cj index 4081549..70c7e53 100644 --- a/src/openapi/services/OpenApiParameterService.cj +++ b/src/openapi/open_api_parameter_service.cj @@ -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 参数生成能力。 diff --git a/src/openapi/services/OpenApiSchemaService.cj b/src/openapi/open_api_schema_service.cj similarity index 88% rename from src/openapi/services/OpenApiSchemaService.cj rename to src/openapi/open_api_schema_service.cj index eb4769e..12b5c58 100644 --- a/src/openapi/services/OpenApiSchemaService.cj +++ b/src/openapi/open_api_schema_service.cj @@ -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 */ diff --git a/src/openapi/services/JsonOpenApiWriter.cj b/src/openapi/open_api_support.cj similarity index 56% rename from src/openapi/services/JsonOpenApiWriter.cj rename to src/openapi/open_api_support.cj index 47b0469..0f16e0a 100644 --- a/src/openapi/services/JsonOpenApiWriter.cj +++ b/src/openapi/open_api_support.cj @@ -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) + } +} diff --git a/src/openapi/OpenApiUIMiddleware.cj b/src/openapi/open_api_ui_middleware.cj similarity index 100% rename from src/openapi/OpenApiUIMiddleware.cj rename to src/openapi/open_api_ui_middleware.cj diff --git a/src/openapi/infrastructure/ReflectUtilities.cj b/src/openapi/reflect_utilities.cj similarity index 98% rename from src/openapi/infrastructure/ReflectUtilities.cj rename to src/openapi/reflect_utilities.cj index 297b8c9..0ef831d 100644 --- a/src/openapi/infrastructure/ReflectUtilities.cj +++ b/src/openapi/reflect_utilities.cj @@ -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.* diff --git a/src/openapi/services/IDocumentProvider.cj b/src/openapi/services/IDocumentProvider.cj deleted file mode 100644 index a303469..0000000 --- a/src/openapi/services/IDocumentProvider.cj +++ /dev/null @@ -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 - /** - * @brief 按名称创建 OpenAPI 文档。 - * @param documentName 要创建的文档名称。 - * @return 生成后的 OpenAPI 文档。 - */ - func create(documentName: String, services: IServiceProvider): OpenApiDocument -} diff --git a/src/openapi/services/NamedService.cj b/src/openapi/services/NamedService.cj deleted file mode 100644 index 6ef0f93..0000000 --- a/src/openapi/services/NamedService.cj +++ /dev/null @@ -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 - } -} diff --git a/src/openapi/services/OpenApiOptions.cj b/src/openapi/services/OpenApiOptions.cj deleted file mode 100644 index 83c17c0..0000000 --- a/src/openapi/services/OpenApiOptions.cj +++ /dev/null @@ -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() - private let _operationTransformer = ArrayList() - private let _schemaTransformers = ArrayList() - - /** - * 未标注 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 { - get() { - _documentTransformers - } - } - - /** - * @brief 返回当前操作转换器集合。 - * @return 已注册的操作转换器集合。 - */ - protected prop operationTransformers: List { - get() { - _operationTransformer - } - } - - /** - * @brief 返回当前架构转换器集合。 - * @return 已注册的架构转换器集合。 - */ - protected prop schemaTransformers: List { - 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)) - } -} diff --git a/src/openapi/SwaggerUIResources.cj b/src/openapi/swagger_ui_resources.cj similarity index 100% rename from src/openapi/SwaggerUIResources.cj rename to src/openapi/swagger_ui_resources.cj diff --git a/src/openapi/transformers/DelegateOpenApiDocumentTransformer.cj b/src/openapi/transformers/DelegateOpenApiDocumentTransformer.cj deleted file mode 100644 index e39a363..0000000 --- a/src/openapi/transformers/DelegateOpenApiDocumentTransformer.cj +++ /dev/null @@ -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) - } -} diff --git a/src/openapi/transformers/DelegateOpenApiOperationTransformer.cj b/src/openapi/transformers/DelegateOpenApiOperationTransformer.cj deleted file mode 100644 index ad0d83d..0000000 --- a/src/openapi/transformers/DelegateOpenApiOperationTransformer.cj +++ /dev/null @@ -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) - } -} diff --git a/src/openapi/transformers/DelegateOpenApiSchemaTransformer.cj b/src/openapi/transformers/DelegateOpenApiSchemaTransformer.cj deleted file mode 100644 index 223ec29..0000000 --- a/src/openapi/transformers/DelegateOpenApiSchemaTransformer.cj +++ /dev/null @@ -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) - } -} diff --git a/src/openapi/transformers/IOpenApiDocumentTransformer.cj b/src/openapi/transformers/IOpenApiDocumentTransformer.cj deleted file mode 100644 index c10a101..0000000 --- a/src/openapi/transformers/IOpenApiDocumentTransformer.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/transformers/IOpenApiOperationTransformer.cj b/src/openapi/transformers/IOpenApiOperationTransformer.cj deleted file mode 100644 index eeaadf8..0000000 --- a/src/openapi/transformers/IOpenApiOperationTransformer.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/transformers/IOpenApiSchemaTransformer.cj b/src/openapi/transformers/IOpenApiSchemaTransformer.cj deleted file mode 100644 index 9975230..0000000 --- a/src/openapi/transformers/IOpenApiSchemaTransformer.cj +++ /dev/null @@ -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 -} diff --git a/src/openapi/transformers/OpenApiDocumentTransformerContext.cj b/src/openapi/transformers/OpenApiDocumentTransformerContext.cj deleted file mode 100644 index 22bb297..0000000 --- a/src/openapi/transformers/OpenApiDocumentTransformerContext.cj +++ /dev/null @@ -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 - } -} diff --git a/src/openapi/transformers/OpenApiOperationTransformerContext.cj b/src/openapi/transformers/OpenApiOperationTransformerContext.cj deleted file mode 100644 index 5bdb2bc..0000000 --- a/src/openapi/transformers/OpenApiOperationTransformerContext.cj +++ /dev/null @@ -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 - } -} diff --git a/src/openapi/transformers/OpenApiSchemaTransformerContext.cj b/src/openapi/transformers/OpenApiSchemaTransformerContext.cj deleted file mode 100644 index ce04007..0000000 --- a/src/openapi/transformers/OpenApiSchemaTransformerContext.cj +++ /dev/null @@ -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 - } -} diff --git a/src/openapi/infrastructure/TypeNameParser.cj b/src/openapi/type_name_parser.cj similarity index 98% rename from src/openapi/infrastructure/TypeNameParser.cj rename to src/openapi/type_name_parser.cj index ffa4c18..a4a6e97 100644 --- a/src/openapi/infrastructure/TypeNameParser.cj +++ b/src/openapi/type_name_parser.cj @@ -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.* diff --git a/src/simapi_extensions.cj b/src/simapi_extensions.cj index 0605403..fb226ed 100644 --- a/src/simapi_extensions.cj +++ b/src/simapi_extensions.cj @@ -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 扩展入口。 diff --git a/tools/gen-swagger-ui-resources.ps1 b/tools/gen-swagger-ui-resources.ps1 index 806b662..9002146 100644 --- a/tools/gen-swagger-ui-resources.ps1 +++ b/tools/gen-swagger-ui-resources.ps1 @@ -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" },