- websocket.v: fix signed type shift warning by casting to u32
- openapi.v: add or {} block for map indexing with sum type values
1823 lines
52 KiB
V
1823 lines
52 KiB
V
// openapi.v - OpenAPI 3.0/3.1 规范数据结构
|
|
// 本模块提供 OpenAPI 文档的数据结构定义,支持 OpenAPI 3.0.x 和 3.1.x 规范
|
|
module hono
|
|
|
|
import x.json2
|
|
|
|
// ============================================================================
|
|
// OpenAPI 基础结构体 (Task 1.1)
|
|
// ============================================================================
|
|
|
|
// OpenAPIContact - 联系信息
|
|
pub struct OpenAPIContact {
|
|
pub mut:
|
|
name string
|
|
url string
|
|
email string
|
|
}
|
|
|
|
// OpenAPILicense - 许可证信息
|
|
pub struct OpenAPILicense {
|
|
pub mut:
|
|
name string // 必需
|
|
url string
|
|
}
|
|
|
|
// OpenAPIInfo - API 基本信息
|
|
pub struct OpenAPIInfo {
|
|
pub mut:
|
|
title string // 必需
|
|
version string // 必需
|
|
description string
|
|
terms_of_service string
|
|
contact OpenAPIContact
|
|
license OpenAPILicense
|
|
}
|
|
|
|
// OpenAPIServer - 服务器信息
|
|
pub struct OpenAPIServer {
|
|
pub mut:
|
|
url string // 必需
|
|
description string
|
|
}
|
|
|
|
// OpenAPIExternalDocs - 外部文档
|
|
pub struct OpenAPIExternalDocs {
|
|
pub mut:
|
|
url string // 必需
|
|
description string
|
|
}
|
|
|
|
// OpenAPITag - 标签定义
|
|
pub struct OpenAPITag {
|
|
pub mut:
|
|
name string // 必需
|
|
description string
|
|
external_docs OpenAPIExternalDocs
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// OpenAPI 路径和操作结构体 (Task 1.2)
|
|
// ============================================================================
|
|
|
|
// OpenAPIMediaType - 媒体类型
|
|
pub struct OpenAPIMediaType {
|
|
pub mut:
|
|
schema OpenAPISchema
|
|
example string
|
|
}
|
|
|
|
// OpenAPIHeader - 响应头
|
|
pub struct OpenAPIHeader {
|
|
pub mut:
|
|
description string
|
|
required bool
|
|
schema OpenAPISchema
|
|
}
|
|
|
|
// OpenAPIResponse - 响应定义
|
|
pub struct OpenAPIResponse {
|
|
pub mut:
|
|
description string // 必需
|
|
headers map[string]OpenAPIHeader
|
|
content map[string]OpenAPIMediaType
|
|
}
|
|
|
|
// OpenAPIRequestBody - 请求体定义
|
|
pub struct OpenAPIRequestBody {
|
|
pub mut:
|
|
description string
|
|
content map[string]OpenAPIMediaType // 必需
|
|
required bool
|
|
}
|
|
|
|
// OpenAPIParameter - 参数定义
|
|
pub struct OpenAPIParameter {
|
|
pub mut:
|
|
name string // 必需
|
|
in_location string // 必需: "path", "query", "header", "cookie"
|
|
description string
|
|
required bool
|
|
deprecated bool
|
|
schema OpenAPISchema
|
|
example string
|
|
}
|
|
|
|
// OpenAPIOperation - 操作定义
|
|
pub struct OpenAPIOperation {
|
|
pub mut:
|
|
summary string
|
|
description string
|
|
operation_id string
|
|
tags []string
|
|
parameters []OpenAPIParameter
|
|
request_body OpenAPIRequestBody
|
|
responses map[string]OpenAPIResponse // 必需
|
|
deprecated bool
|
|
security []map[string][]string
|
|
}
|
|
|
|
// OpenAPIPathItem - 路径项
|
|
pub struct OpenAPIPathItem {
|
|
pub mut:
|
|
get OpenAPIOperation
|
|
post OpenAPIOperation
|
|
put OpenAPIOperation
|
|
delete OpenAPIOperation
|
|
patch OpenAPIOperation
|
|
head OpenAPIOperation
|
|
options OpenAPIOperation
|
|
summary string
|
|
description string
|
|
parameters []OpenAPIParameter
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// OpenAPI Schema 和 Components 结构体 (Task 1.3)
|
|
// ============================================================================
|
|
|
|
// OpenAPISchema - JSON Schema 定义
|
|
pub struct OpenAPISchema {
|
|
pub mut:
|
|
schema_type string // "string", "integer", "number", "boolean", "array", "object"
|
|
format string // "int32", "int64", "float", "double", "date", "date-time", etc.
|
|
title string
|
|
description string
|
|
default_val string
|
|
example string
|
|
enum_values []string
|
|
required []string
|
|
properties map[string]OpenAPISchema
|
|
items &OpenAPISchema = unsafe { nil } // for array type
|
|
minimum f64
|
|
maximum f64
|
|
min_length int
|
|
max_length int
|
|
pattern string
|
|
nullable bool
|
|
read_only bool
|
|
write_only bool
|
|
ref string // $ref 引用
|
|
}
|
|
|
|
// OpenAPISecurityScheme - 安全方案
|
|
pub struct OpenAPISecurityScheme {
|
|
pub mut:
|
|
scheme_type string // "apiKey", "http", "oauth2", "openIdConnect"
|
|
description string
|
|
name string // for apiKey
|
|
in_location string // for apiKey: "query", "header", "cookie"
|
|
scheme string // for http: "bearer", "basic"
|
|
bearer_format string // for http bearer
|
|
}
|
|
|
|
// OpenAPIComponents - 可重用组件
|
|
pub struct OpenAPIComponents {
|
|
pub mut:
|
|
schemas map[string]OpenAPISchema
|
|
responses map[string]OpenAPIResponse
|
|
parameters map[string]OpenAPIParameter
|
|
request_bodies map[string]OpenAPIRequestBody
|
|
headers map[string]OpenAPIHeader
|
|
security_schemes map[string]OpenAPISecurityScheme
|
|
}
|
|
|
|
// OpenAPIDocument - OpenAPI 文档主结构
|
|
pub struct OpenAPIDocument {
|
|
pub mut:
|
|
openapi string // 必需: "3.0.0" 或 "3.1.0"
|
|
info OpenAPIInfo // 必需
|
|
servers []OpenAPIServer
|
|
paths map[string]OpenAPIPathItem // 必需
|
|
components OpenAPIComponents
|
|
security []map[string][]string
|
|
tags []OpenAPITag
|
|
external_docs OpenAPIExternalDocs
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// OpenAPI 文档序列化 (Task 2.1)
|
|
// ============================================================================
|
|
|
|
// Helper function to check if a string is empty
|
|
fn is_empty(s string) bool {
|
|
return s.len == 0
|
|
}
|
|
|
|
// Helper function to check if a map is empty
|
|
fn is_map_empty[K, V](m map[K]V) bool {
|
|
return m.len == 0
|
|
}
|
|
|
|
// Helper function to check if an array is empty
|
|
fn is_array_empty[T](arr []T) bool {
|
|
return arr.len == 0
|
|
}
|
|
|
|
// to_json - 将 OpenAPIContact 序列化为 JSON
|
|
pub fn (c OpenAPIContact) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
if !is_empty(c.name) {
|
|
obj['name'] = json2.Any(c.name)
|
|
}
|
|
if !is_empty(c.url) {
|
|
obj['url'] = json2.Any(c.url)
|
|
}
|
|
if !is_empty(c.email) {
|
|
obj['email'] = json2.Any(c.email)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPILicense 序列化为 JSON
|
|
pub fn (l OpenAPILicense) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// name is required
|
|
obj['name'] = json2.Any(l.name)
|
|
if !is_empty(l.url) {
|
|
obj['url'] = json2.Any(l.url)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIInfo 序列化为 JSON
|
|
pub fn (i OpenAPIInfo) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// title and version are required
|
|
obj['title'] = json2.Any(i.title)
|
|
obj['version'] = json2.Any(i.version)
|
|
if !is_empty(i.description) {
|
|
obj['description'] = json2.Any(i.description)
|
|
}
|
|
if !is_empty(i.terms_of_service) {
|
|
obj['termsOfService'] = json2.Any(i.terms_of_service)
|
|
}
|
|
// Only include contact if it has any non-empty fields
|
|
if !is_empty(i.contact.name) || !is_empty(i.contact.url) || !is_empty(i.contact.email) {
|
|
obj['contact'] = i.contact.to_json()
|
|
}
|
|
// Only include license if name is set (required field)
|
|
if !is_empty(i.license.name) {
|
|
obj['license'] = i.license.to_json()
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIServer 序列化为 JSON
|
|
pub fn (s OpenAPIServer) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// url is required
|
|
obj['url'] = json2.Any(s.url)
|
|
if !is_empty(s.description) {
|
|
obj['description'] = json2.Any(s.description)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIExternalDocs 序列化为 JSON
|
|
pub fn (e OpenAPIExternalDocs) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// url is required
|
|
obj['url'] = json2.Any(e.url)
|
|
if !is_empty(e.description) {
|
|
obj['description'] = json2.Any(e.description)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPITag 序列化为 JSON
|
|
pub fn (t OpenAPITag) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// name is required
|
|
obj['name'] = json2.Any(t.name)
|
|
if !is_empty(t.description) {
|
|
obj['description'] = json2.Any(t.description)
|
|
}
|
|
if !is_empty(t.external_docs.url) {
|
|
obj['externalDocs'] = t.external_docs.to_json()
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPISchema 序列化为 JSON
|
|
pub fn (s OpenAPISchema) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
|
|
// Handle $ref - if ref is set, only output $ref
|
|
if !is_empty(s.ref) {
|
|
obj[r'$ref'] = json2.Any(s.ref)
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// schema_type -> type
|
|
if !is_empty(s.schema_type) {
|
|
obj['type'] = json2.Any(s.schema_type)
|
|
}
|
|
if !is_empty(s.format) {
|
|
obj['format'] = json2.Any(s.format)
|
|
}
|
|
if !is_empty(s.title) {
|
|
obj['title'] = json2.Any(s.title)
|
|
}
|
|
if !is_empty(s.description) {
|
|
obj['description'] = json2.Any(s.description)
|
|
}
|
|
if !is_empty(s.default_val) {
|
|
obj['default'] = json2.Any(s.default_val)
|
|
}
|
|
if !is_empty(s.example) {
|
|
obj['example'] = json2.Any(s.example)
|
|
}
|
|
// enum_values -> enum
|
|
if !is_array_empty(s.enum_values) {
|
|
mut enum_arr := []json2.Any{}
|
|
for v in s.enum_values {
|
|
enum_arr << json2.Any(v)
|
|
}
|
|
obj['enum'] = json2.Any(enum_arr)
|
|
}
|
|
if !is_array_empty(s.required) {
|
|
mut req_arr := []json2.Any{}
|
|
for r in s.required {
|
|
req_arr << json2.Any(r)
|
|
}
|
|
obj['required'] = json2.Any(req_arr)
|
|
}
|
|
if !is_map_empty(s.properties) {
|
|
mut props := map[string]json2.Any{}
|
|
for key, val in s.properties {
|
|
props[key] = val.to_json()
|
|
}
|
|
obj['properties'] = json2.Any(props)
|
|
}
|
|
// items for array type
|
|
if s.items != unsafe { nil } {
|
|
obj['items'] = s.items.to_json()
|
|
}
|
|
if s.minimum != 0 {
|
|
obj['minimum'] = json2.Any(s.minimum)
|
|
}
|
|
if s.maximum != 0 {
|
|
obj['maximum'] = json2.Any(s.maximum)
|
|
}
|
|
if s.min_length != 0 {
|
|
obj['minLength'] = json2.Any(s.min_length)
|
|
}
|
|
if s.max_length != 0 {
|
|
obj['maxLength'] = json2.Any(s.max_length)
|
|
}
|
|
if !is_empty(s.pattern) {
|
|
obj['pattern'] = json2.Any(s.pattern)
|
|
}
|
|
if s.nullable {
|
|
obj['nullable'] = json2.Any(s.nullable)
|
|
}
|
|
if s.read_only {
|
|
obj['readOnly'] = json2.Any(s.read_only)
|
|
}
|
|
if s.write_only {
|
|
obj['writeOnly'] = json2.Any(s.write_only)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIHeader 序列化为 JSON
|
|
pub fn (h OpenAPIHeader) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
if !is_empty(h.description) {
|
|
obj['description'] = json2.Any(h.description)
|
|
}
|
|
if h.required {
|
|
obj['required'] = json2.Any(h.required)
|
|
}
|
|
// Only include schema if it has content
|
|
if !is_empty(h.schema.schema_type) || !is_empty(h.schema.ref) {
|
|
obj['schema'] = h.schema.to_json()
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIMediaType 序列化为 JSON
|
|
pub fn (m OpenAPIMediaType) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// Only include schema if it has content
|
|
if !is_empty(m.schema.schema_type) || !is_empty(m.schema.ref) || !is_map_empty(m.schema.properties) {
|
|
obj['schema'] = m.schema.to_json()
|
|
}
|
|
if !is_empty(m.example) {
|
|
obj['example'] = json2.Any(m.example)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIResponse 序列化为 JSON
|
|
pub fn (r OpenAPIResponse) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// description is required
|
|
obj['description'] = json2.Any(r.description)
|
|
if !is_map_empty(r.headers) {
|
|
mut headers := map[string]json2.Any{}
|
|
for key, val in r.headers {
|
|
headers[key] = val.to_json()
|
|
}
|
|
obj['headers'] = json2.Any(headers)
|
|
}
|
|
if !is_map_empty(r.content) {
|
|
mut content := map[string]json2.Any{}
|
|
for key, val in r.content {
|
|
content[key] = val.to_json()
|
|
}
|
|
obj['content'] = json2.Any(content)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIRequestBody 序列化为 JSON
|
|
pub fn (rb OpenAPIRequestBody) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
if !is_empty(rb.description) {
|
|
obj['description'] = json2.Any(rb.description)
|
|
}
|
|
// content is required
|
|
if !is_map_empty(rb.content) {
|
|
mut content := map[string]json2.Any{}
|
|
for key, val in rb.content {
|
|
content[key] = val.to_json()
|
|
}
|
|
obj['content'] = json2.Any(content)
|
|
}
|
|
if rb.required {
|
|
obj['required'] = json2.Any(rb.required)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIParameter 序列化为 JSON
|
|
pub fn (p OpenAPIParameter) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// name is required
|
|
obj['name'] = json2.Any(p.name)
|
|
// in_location -> in (required)
|
|
obj['in'] = json2.Any(p.in_location)
|
|
if !is_empty(p.description) {
|
|
obj['description'] = json2.Any(p.description)
|
|
}
|
|
if p.required {
|
|
obj['required'] = json2.Any(p.required)
|
|
}
|
|
if p.deprecated {
|
|
obj['deprecated'] = json2.Any(p.deprecated)
|
|
}
|
|
// Only include schema if it has content
|
|
if !is_empty(p.schema.schema_type) || !is_empty(p.schema.ref) {
|
|
obj['schema'] = p.schema.to_json()
|
|
}
|
|
if !is_empty(p.example) {
|
|
obj['example'] = json2.Any(p.example)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIOperation 序列化为 JSON
|
|
pub fn (op OpenAPIOperation) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
if !is_empty(op.summary) {
|
|
obj['summary'] = json2.Any(op.summary)
|
|
}
|
|
if !is_empty(op.description) {
|
|
obj['description'] = json2.Any(op.description)
|
|
}
|
|
if !is_empty(op.operation_id) {
|
|
obj['operationId'] = json2.Any(op.operation_id)
|
|
}
|
|
if !is_array_empty(op.tags) {
|
|
mut tags_arr := []json2.Any{}
|
|
for t in op.tags {
|
|
tags_arr << json2.Any(t)
|
|
}
|
|
obj['tags'] = json2.Any(tags_arr)
|
|
}
|
|
if !is_array_empty(op.parameters) {
|
|
mut params_arr := []json2.Any{}
|
|
for p in op.parameters {
|
|
params_arr << p.to_json()
|
|
}
|
|
obj['parameters'] = json2.Any(params_arr)
|
|
}
|
|
// Only include request_body if it has content
|
|
if !is_map_empty(op.request_body.content) {
|
|
obj['requestBody'] = op.request_body.to_json()
|
|
}
|
|
// responses is required
|
|
if !is_map_empty(op.responses) {
|
|
mut responses := map[string]json2.Any{}
|
|
for key, val in op.responses {
|
|
responses[key] = val.to_json()
|
|
}
|
|
obj['responses'] = json2.Any(responses)
|
|
}
|
|
if op.deprecated {
|
|
obj['deprecated'] = json2.Any(op.deprecated)
|
|
}
|
|
if !is_array_empty(op.security) {
|
|
mut sec_arr := []json2.Any{}
|
|
for sec in op.security {
|
|
mut sec_obj := map[string]json2.Any{}
|
|
for key, val in sec {
|
|
mut scopes := []json2.Any{}
|
|
for scope in val {
|
|
scopes << json2.Any(scope)
|
|
}
|
|
sec_obj[key] = json2.Any(scopes)
|
|
}
|
|
sec_arr << json2.Any(sec_obj)
|
|
}
|
|
obj['security'] = json2.Any(sec_arr)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// Helper to check if an operation is empty (has no responses)
|
|
fn is_operation_empty(op OpenAPIOperation) bool {
|
|
return is_map_empty(op.responses)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIPathItem 序列化为 JSON
|
|
pub fn (pi OpenAPIPathItem) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
if !is_empty(pi.summary) {
|
|
obj['summary'] = json2.Any(pi.summary)
|
|
}
|
|
if !is_empty(pi.description) {
|
|
obj['description'] = json2.Any(pi.description)
|
|
}
|
|
if !is_array_empty(pi.parameters) {
|
|
mut params_arr := []json2.Any{}
|
|
for p in pi.parameters {
|
|
params_arr << p.to_json()
|
|
}
|
|
obj['parameters'] = json2.Any(params_arr)
|
|
}
|
|
// HTTP methods - only include if they have responses
|
|
if !is_operation_empty(pi.get) {
|
|
obj['get'] = pi.get.to_json()
|
|
}
|
|
if !is_operation_empty(pi.post) {
|
|
obj['post'] = pi.post.to_json()
|
|
}
|
|
if !is_operation_empty(pi.put) {
|
|
obj['put'] = pi.put.to_json()
|
|
}
|
|
if !is_operation_empty(pi.delete) {
|
|
obj['delete'] = pi.delete.to_json()
|
|
}
|
|
if !is_operation_empty(pi.patch) {
|
|
obj['patch'] = pi.patch.to_json()
|
|
}
|
|
if !is_operation_empty(pi.head) {
|
|
obj['head'] = pi.head.to_json()
|
|
}
|
|
if !is_operation_empty(pi.options) {
|
|
obj['options'] = pi.options.to_json()
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPISecurityScheme 序列化为 JSON
|
|
pub fn (ss OpenAPISecurityScheme) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// scheme_type -> type
|
|
if !is_empty(ss.scheme_type) {
|
|
obj['type'] = json2.Any(ss.scheme_type)
|
|
}
|
|
if !is_empty(ss.description) {
|
|
obj['description'] = json2.Any(ss.description)
|
|
}
|
|
if !is_empty(ss.name) {
|
|
obj['name'] = json2.Any(ss.name)
|
|
}
|
|
// in_location -> in
|
|
if !is_empty(ss.in_location) {
|
|
obj['in'] = json2.Any(ss.in_location)
|
|
}
|
|
if !is_empty(ss.scheme) {
|
|
obj['scheme'] = json2.Any(ss.scheme)
|
|
}
|
|
if !is_empty(ss.bearer_format) {
|
|
obj['bearerFormat'] = json2.Any(ss.bearer_format)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// Helper to check if components is empty
|
|
fn is_components_empty(c OpenAPIComponents) bool {
|
|
return is_map_empty(c.schemas) && is_map_empty(c.responses) &&
|
|
is_map_empty(c.parameters) && is_map_empty(c.request_bodies) &&
|
|
is_map_empty(c.headers) && is_map_empty(c.security_schemes)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIComponents 序列化为 JSON
|
|
pub fn (c OpenAPIComponents) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
if !is_map_empty(c.schemas) {
|
|
mut schemas := map[string]json2.Any{}
|
|
for key, val in c.schemas {
|
|
schemas[key] = val.to_json()
|
|
}
|
|
obj['schemas'] = json2.Any(schemas)
|
|
}
|
|
if !is_map_empty(c.responses) {
|
|
mut responses := map[string]json2.Any{}
|
|
for key, val in c.responses {
|
|
responses[key] = val.to_json()
|
|
}
|
|
obj['responses'] = json2.Any(responses)
|
|
}
|
|
if !is_map_empty(c.parameters) {
|
|
mut parameters := map[string]json2.Any{}
|
|
for key, val in c.parameters {
|
|
parameters[key] = val.to_json()
|
|
}
|
|
obj['parameters'] = json2.Any(parameters)
|
|
}
|
|
if !is_map_empty(c.request_bodies) {
|
|
mut request_bodies := map[string]json2.Any{}
|
|
for key, val in c.request_bodies {
|
|
request_bodies[key] = val.to_json()
|
|
}
|
|
obj['requestBodies'] = json2.Any(request_bodies)
|
|
}
|
|
if !is_map_empty(c.headers) {
|
|
mut headers := map[string]json2.Any{}
|
|
for key, val in c.headers {
|
|
headers[key] = val.to_json()
|
|
}
|
|
obj['headers'] = json2.Any(headers)
|
|
}
|
|
if !is_map_empty(c.security_schemes) {
|
|
mut security_schemes := map[string]json2.Any{}
|
|
for key, val in c.security_schemes {
|
|
security_schemes[key] = val.to_json()
|
|
}
|
|
obj['securitySchemes'] = json2.Any(security_schemes)
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json - 将 OpenAPIDocument 序列化为 JSON Any 对象
|
|
pub fn (doc OpenAPIDocument) to_json() json2.Any {
|
|
mut obj := map[string]json2.Any{}
|
|
// openapi is required
|
|
obj['openapi'] = json2.Any(doc.openapi)
|
|
// info is required
|
|
obj['info'] = doc.info.to_json()
|
|
// servers (optional)
|
|
if !is_array_empty(doc.servers) {
|
|
mut servers_arr := []json2.Any{}
|
|
for s in doc.servers {
|
|
servers_arr << s.to_json()
|
|
}
|
|
obj['servers'] = json2.Any(servers_arr)
|
|
}
|
|
// paths is required
|
|
mut paths := map[string]json2.Any{}
|
|
for key, val in doc.paths {
|
|
paths[key] = val.to_json()
|
|
}
|
|
obj['paths'] = json2.Any(paths)
|
|
// components (optional)
|
|
if !is_components_empty(doc.components) {
|
|
obj['components'] = doc.components.to_json()
|
|
}
|
|
// security (optional)
|
|
if !is_array_empty(doc.security) {
|
|
mut sec_arr := []json2.Any{}
|
|
for sec in doc.security {
|
|
mut sec_obj := map[string]json2.Any{}
|
|
for key, val in sec {
|
|
mut scopes := []json2.Any{}
|
|
for scope in val {
|
|
scopes << json2.Any(scope)
|
|
}
|
|
sec_obj[key] = json2.Any(scopes)
|
|
}
|
|
sec_arr << json2.Any(sec_obj)
|
|
}
|
|
obj['security'] = json2.Any(sec_arr)
|
|
}
|
|
// tags (optional)
|
|
if !is_array_empty(doc.tags) {
|
|
mut tags_arr := []json2.Any{}
|
|
for t in doc.tags {
|
|
tags_arr << t.to_json()
|
|
}
|
|
obj['tags'] = json2.Any(tags_arr)
|
|
}
|
|
// external_docs (optional)
|
|
if !is_empty(doc.external_docs.url) {
|
|
obj['externalDocs'] = doc.external_docs.to_json()
|
|
}
|
|
return json2.Any(obj)
|
|
}
|
|
|
|
// to_json_str - 将 OpenAPIDocument 序列化为 JSON 字符串
|
|
pub fn (doc OpenAPIDocument) to_json_str() string {
|
|
return doc.to_json().str()
|
|
}
|
|
|
|
// to_json_pretty - 将 OpenAPIDocument 序列化为格式化的 JSON 字符串
|
|
pub fn (doc OpenAPIDocument) to_json_pretty() string {
|
|
return json2.encode(doc.to_json(), prettify: true)
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// OpenAPI 文档反序列化 (Task 2.3)
|
|
// ============================================================================
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIContact
|
|
pub fn OpenAPIContact.from_json(j json2.Any) OpenAPIContact {
|
|
obj := j.as_map()
|
|
return OpenAPIContact{
|
|
name: if 'name' in obj { obj['name'] or { json2.Any('') }.str() } else { '' }
|
|
url: if 'url' in obj { obj['url'] or { json2.Any('') }.str() } else { '' }
|
|
email: if 'email' in obj { obj['email'] or { json2.Any('') }.str() } else { '' }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPILicense
|
|
pub fn OpenAPILicense.from_json(j json2.Any) OpenAPILicense {
|
|
obj := j.as_map()
|
|
return OpenAPILicense{
|
|
name: if 'name' in obj { obj['name'] or { json2.Any('') }.str() } else { '' }
|
|
url: if 'url' in obj { obj['url'] or { json2.Any('') }.str() } else { '' }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIInfo
|
|
pub fn OpenAPIInfo.from_json(j json2.Any) OpenAPIInfo {
|
|
obj := j.as_map()
|
|
return OpenAPIInfo{
|
|
title: if 'title' in obj { obj['title'] or { json2.Any('') }.str() } else { '' }
|
|
version: if 'version' in obj { obj['version'] or { json2.Any('') }.str() } else { '' }
|
|
description: if 'description' in obj { obj['description'] or { json2.Any('') }.str() } else { '' }
|
|
terms_of_service: if 'termsOfService' in obj { obj['termsOfService'] or { json2.Any('') }.str() } else { '' }
|
|
contact: if 'contact' in obj { OpenAPIContact.from_json(obj['contact'] or { json2.Any('') }) } else { OpenAPIContact{} }
|
|
license: if 'license' in obj { OpenAPILicense.from_json(obj['license'] or { json2.Any('') }) } else { OpenAPILicense{} }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIServer
|
|
pub fn OpenAPIServer.from_json(j json2.Any) OpenAPIServer {
|
|
obj := j.as_map()
|
|
return OpenAPIServer{
|
|
url: if 'url' in obj { obj['url'] or { json2.Any('') }.str() } else { '' }
|
|
description: if 'description' in obj { obj['description'] or { json2.Any('') }.str() } else { '' }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIExternalDocs
|
|
pub fn OpenAPIExternalDocs.from_json(j json2.Any) OpenAPIExternalDocs {
|
|
obj := j.as_map()
|
|
return OpenAPIExternalDocs{
|
|
url: if 'url' in obj { obj['url'] or { json2.Any('') }.str() } else { '' }
|
|
description: if 'description' in obj { obj['description'] or { json2.Any('') }.str() } else { '' }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPITag
|
|
pub fn OpenAPITag.from_json(j json2.Any) OpenAPITag {
|
|
obj := j.as_map()
|
|
return OpenAPITag{
|
|
name: if 'name' in obj { obj['name'] or { json2.Any('') }.str() } else { '' }
|
|
description: if 'description' in obj { obj['description'] or { json2.Any('') }.str() } else { '' }
|
|
external_docs: if 'externalDocs' in obj { OpenAPIExternalDocs.from_json(obj['externalDocs'] or { json2.Any('') }) } else { OpenAPIExternalDocs{} }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPISchema
|
|
pub fn OpenAPISchema.from_json(j json2.Any) OpenAPISchema {
|
|
return openapi_schema_from_json_impl(j)
|
|
}
|
|
|
|
// Helper function to parse OpenAPISchema from JSON
|
|
fn openapi_schema_from_json_impl(j json2.Any) OpenAPISchema {
|
|
obj := j.as_map()
|
|
|
|
// Parse properties map
|
|
mut props := map[string]OpenAPISchema{}
|
|
if 'properties' in obj {
|
|
props_obj := (obj['properties'] or { json2.Any('') }).as_map()
|
|
for key, val in props_obj {
|
|
props[key] = openapi_schema_from_json_impl(val)
|
|
}
|
|
}
|
|
|
|
// Parse enum values
|
|
mut enum_vals := []string{}
|
|
if 'enum' in obj {
|
|
for e in (obj['enum'] or { json2.Any('') }).arr() {
|
|
enum_vals << e.str()
|
|
}
|
|
}
|
|
|
|
// Parse required array
|
|
mut required := []string{}
|
|
if 'required' in obj {
|
|
for r in (obj['required'] or { json2.Any('') }).arr() {
|
|
required << r.str()
|
|
}
|
|
}
|
|
|
|
mut schema := OpenAPISchema{
|
|
schema_type: if 'type' in obj { (obj['type'] or { json2.Any('') }).str() } else { '' }
|
|
format: if 'format' in obj { (obj['format'] or { json2.Any('') }).str() } else { '' }
|
|
title: if 'title' in obj { (obj['title'] or { json2.Any('') }).str() } else { '' }
|
|
description: if 'description' in obj { (obj['description'] or { json2.Any('') }).str() } else { '' }
|
|
default_val: if 'default' in obj { (obj['default'] or { json2.Any('') }).str() } else { '' }
|
|
example: if 'example' in obj { (obj['example'] or { json2.Any('') }).str() } else { '' }
|
|
enum_values: enum_vals
|
|
required: required
|
|
properties: props
|
|
minimum: if 'minimum' in obj { (obj['minimum'] or { json2.Any('') }).f64() } else { 0 }
|
|
maximum: if 'maximum' in obj { (obj['maximum'] or { json2.Any('') }).f64() } else { 0 }
|
|
min_length: if 'minLength' in obj { (obj['minLength'] or { json2.Any('') }).int() } else { 0 }
|
|
max_length: if 'maxLength' in obj { (obj['maxLength'] or { json2.Any('') }).int() } else { 0 }
|
|
pattern: if 'pattern' in obj { (obj['pattern'] or { json2.Any('') }).str() } else { '' }
|
|
nullable: if 'nullable' in obj { (obj['nullable'] or { json2.Any('') }).bool() } else { false }
|
|
read_only: if 'readOnly' in obj { (obj['readOnly'] or { json2.Any('') }).bool() } else { false }
|
|
write_only: if 'writeOnly' in obj { (obj['writeOnly'] or { json2.Any('') }).bool() } else { false }
|
|
ref: if r'$ref' in obj { (obj[r'$ref'] or { json2.Any('') }).str() } else { '' }
|
|
}
|
|
|
|
// Parse items (for array type) - handle pointer separately
|
|
if 'items' in obj {
|
|
items_schema := openapi_schema_from_json_impl(obj['items'] or { json2.Any('') })
|
|
schema.items = &items_schema
|
|
}
|
|
|
|
return schema
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIHeader
|
|
pub fn OpenAPIHeader.from_json(j json2.Any) OpenAPIHeader {
|
|
obj := j.as_map()
|
|
return OpenAPIHeader{
|
|
description: if 'description' in obj { (obj['description'] or { json2.Any('') }).str() } else { '' }
|
|
required: if 'required' in obj { (obj['required'] or { json2.Any('') }).bool() } else { false }
|
|
schema: if 'schema' in obj { OpenAPISchema.from_json(obj['schema'] or { json2.Any('') }) } else { OpenAPISchema{} }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIMediaType
|
|
pub fn OpenAPIMediaType.from_json(j json2.Any) OpenAPIMediaType {
|
|
obj := j.as_map()
|
|
return OpenAPIMediaType{
|
|
schema: if 'schema' in obj { OpenAPISchema.from_json(obj['schema'] or { json2.Any('') }) } else { OpenAPISchema{} }
|
|
example: if 'example' in obj { (obj['example'] or { json2.Any('') }).str() } else { '' }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIResponse
|
|
pub fn OpenAPIResponse.from_json(j json2.Any) OpenAPIResponse {
|
|
obj := j.as_map()
|
|
|
|
// Parse headers map
|
|
mut headers := map[string]OpenAPIHeader{}
|
|
if 'headers' in obj {
|
|
headers_obj := (obj['headers'] or { json2.Any('') }).as_map()
|
|
for key, val in headers_obj {
|
|
headers[key] = OpenAPIHeader.from_json(val)
|
|
}
|
|
}
|
|
|
|
// Parse content map
|
|
mut content := map[string]OpenAPIMediaType{}
|
|
if 'content' in obj {
|
|
content_obj := (obj['content'] or { json2.Any('') }).as_map()
|
|
for key, val in content_obj {
|
|
content[key] = OpenAPIMediaType.from_json(val)
|
|
}
|
|
}
|
|
|
|
return OpenAPIResponse{
|
|
description: if 'description' in obj { (obj['description'] or { json2.Any('') }).str() } else { '' }
|
|
headers: headers
|
|
content: content
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIRequestBody
|
|
pub fn OpenAPIRequestBody.from_json(j json2.Any) OpenAPIRequestBody {
|
|
obj := j.as_map()
|
|
|
|
// Parse content map
|
|
mut content := map[string]OpenAPIMediaType{}
|
|
if 'content' in obj {
|
|
content_obj := (obj['content'] or { json2.Any('') }).as_map()
|
|
for key, val in content_obj {
|
|
content[key] = OpenAPIMediaType.from_json(val)
|
|
}
|
|
}
|
|
|
|
return OpenAPIRequestBody{
|
|
description: if 'description' in obj { (obj['description'] or { json2.Any('') }).str() } else { '' }
|
|
content: content
|
|
required: if 'required' in obj { (obj['required'] or { json2.Any('') }).bool() } else { false }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIParameter
|
|
pub fn OpenAPIParameter.from_json(j json2.Any) OpenAPIParameter {
|
|
obj := j.as_map()
|
|
return OpenAPIParameter{
|
|
name: if 'name' in obj { (obj['name'] or { json2.Any('') }).str() } else { '' }
|
|
in_location: if 'in' in obj { (obj['in'] or { json2.Any('') }).str() } else { '' }
|
|
description: if 'description' in obj { (obj['description'] or { json2.Any('') }).str() } else { '' }
|
|
required: if 'required' in obj { (obj['required'] or { json2.Any('') }).bool() } else { false }
|
|
deprecated: if 'deprecated' in obj { (obj['deprecated'] or { json2.Any('') }).bool() } else { false }
|
|
schema: if 'schema' in obj { OpenAPISchema.from_json(obj['schema'] or { json2.Any('') }) } else { OpenAPISchema{} }
|
|
example: if 'example' in obj { (obj['example'] or { json2.Any('') }).str() } else { '' }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIOperation
|
|
pub fn OpenAPIOperation.from_json(j json2.Any) OpenAPIOperation {
|
|
obj := j.as_map()
|
|
|
|
// Parse tags array
|
|
mut tags := []string{}
|
|
if 'tags' in obj {
|
|
for t in (obj['tags'] or { json2.Any('') }).arr() {
|
|
tags << t.str()
|
|
}
|
|
}
|
|
|
|
// Parse parameters array
|
|
mut parameters := []OpenAPIParameter{}
|
|
if 'parameters' in obj {
|
|
for p in (obj['parameters'] or { json2.Any('') }).arr() {
|
|
parameters << OpenAPIParameter.from_json(p)
|
|
}
|
|
}
|
|
|
|
// Parse responses map
|
|
mut responses := map[string]OpenAPIResponse{}
|
|
if 'responses' in obj {
|
|
responses_obj := (obj['responses'] or { json2.Any('') }).as_map()
|
|
for key, val in responses_obj {
|
|
responses[key] = OpenAPIResponse.from_json(val)
|
|
}
|
|
}
|
|
|
|
// Parse security array
|
|
mut security := []map[string][]string{}
|
|
if 'security' in obj {
|
|
for sec in (obj['security'] or { json2.Any('') }).arr() {
|
|
mut sec_map := map[string][]string{}
|
|
sec_obj := sec.as_map()
|
|
for key, val in sec_obj {
|
|
mut scopes := []string{}
|
|
for scope in val.arr() {
|
|
scopes << scope.str()
|
|
}
|
|
sec_map[key] = scopes
|
|
}
|
|
security << sec_map
|
|
}
|
|
}
|
|
|
|
return OpenAPIOperation{
|
|
summary: if 'summary' in obj { (obj['summary'] or { json2.Any('') }).str() } else { '' }
|
|
description: if 'description' in obj { (obj['description'] or { json2.Any('') }).str() } else { '' }
|
|
operation_id: if 'operationId' in obj { (obj['operationId'] or { json2.Any('') }).str() } else { '' }
|
|
tags: tags
|
|
parameters: parameters
|
|
request_body: if 'requestBody' in obj { OpenAPIRequestBody.from_json(obj['requestBody'] or { json2.Any('') }) } else { OpenAPIRequestBody{} }
|
|
responses: responses
|
|
deprecated: if 'deprecated' in obj { (obj['deprecated'] or { json2.Any('') }).bool() } else { false }
|
|
security: security
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIPathItem
|
|
pub fn OpenAPIPathItem.from_json(j json2.Any) OpenAPIPathItem {
|
|
obj := j.as_map()
|
|
|
|
// Parse parameters array
|
|
mut parameters := []OpenAPIParameter{}
|
|
if 'parameters' in obj {
|
|
for p in (obj['parameters'] or { json2.Any('') }).arr() {
|
|
parameters << OpenAPIParameter.from_json(p)
|
|
}
|
|
}
|
|
|
|
return OpenAPIPathItem{
|
|
summary: if 'summary' in obj { (obj['summary'] or { json2.Any('') }).str() } else { '' }
|
|
description: if 'description' in obj { (obj['description'] or { json2.Any('') }).str() } else { '' }
|
|
parameters: parameters
|
|
get: if 'get' in obj { OpenAPIOperation.from_json(obj['get'] or { json2.Any('') }) } else { OpenAPIOperation{} }
|
|
post: if 'post' in obj { OpenAPIOperation.from_json(obj['post'] or { json2.Any('') }) } else { OpenAPIOperation{} }
|
|
put: if 'put' in obj { OpenAPIOperation.from_json(obj['put'] or { json2.Any('') }) } else { OpenAPIOperation{} }
|
|
delete: if 'delete' in obj { OpenAPIOperation.from_json(obj['delete'] or { json2.Any('') }) } else { OpenAPIOperation{} }
|
|
patch: if 'patch' in obj { OpenAPIOperation.from_json(obj['patch'] or { json2.Any('') }) } else { OpenAPIOperation{} }
|
|
head: if 'head' in obj { OpenAPIOperation.from_json(obj['head'] or { json2.Any('') }) } else { OpenAPIOperation{} }
|
|
options: if 'options' in obj { OpenAPIOperation.from_json(obj['options'] or { json2.Any('') }) } else { OpenAPIOperation{} }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPISecurityScheme
|
|
pub fn OpenAPISecurityScheme.from_json(j json2.Any) OpenAPISecurityScheme {
|
|
obj := j.as_map()
|
|
return OpenAPISecurityScheme{
|
|
scheme_type: if 'type' in obj { (obj['type'] or { json2.Any('') }).str() } else { '' }
|
|
description: if 'description' in obj { (obj['description'] or { json2.Any('') }).str() } else { '' }
|
|
name: if 'name' in obj { (obj['name'] or { json2.Any('') }).str() } else { '' }
|
|
in_location: if 'in' in obj { (obj['in'] or { json2.Any('') }).str() } else { '' }
|
|
scheme: if 'scheme' in obj { (obj['scheme'] or { json2.Any('') }).str() } else { '' }
|
|
bearer_format: if 'bearerFormat' in obj { (obj['bearerFormat'] or { json2.Any('') }).str() } else { '' }
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIComponents
|
|
pub fn OpenAPIComponents.from_json(j json2.Any) OpenAPIComponents {
|
|
obj := j.as_map()
|
|
|
|
// Parse schemas map
|
|
mut schemas := map[string]OpenAPISchema{}
|
|
if 'schemas' in obj {
|
|
schemas_obj := (obj['schemas'] or { json2.Any('') }).as_map()
|
|
for key, val in schemas_obj {
|
|
schemas[key] = OpenAPISchema.from_json(val)
|
|
}
|
|
}
|
|
|
|
// Parse responses map
|
|
mut responses := map[string]OpenAPIResponse{}
|
|
if 'responses' in obj {
|
|
responses_obj := (obj['responses'] or { json2.Any('') }).as_map()
|
|
for key, val in responses_obj {
|
|
responses[key] = OpenAPIResponse.from_json(val)
|
|
}
|
|
}
|
|
|
|
// Parse parameters map
|
|
mut parameters := map[string]OpenAPIParameter{}
|
|
if 'parameters' in obj {
|
|
parameters_obj := (obj['parameters'] or { json2.Any('') }).as_map()
|
|
for key, val in parameters_obj {
|
|
parameters[key] = OpenAPIParameter.from_json(val)
|
|
}
|
|
}
|
|
|
|
// Parse request_bodies map
|
|
mut request_bodies := map[string]OpenAPIRequestBody{}
|
|
if 'requestBodies' in obj {
|
|
request_bodies_obj := (obj['requestBodies'] or { json2.Any('') }).as_map()
|
|
for key, val in request_bodies_obj {
|
|
request_bodies[key] = OpenAPIRequestBody.from_json(val)
|
|
}
|
|
}
|
|
|
|
// Parse headers map
|
|
mut headers := map[string]OpenAPIHeader{}
|
|
if 'headers' in obj {
|
|
headers_obj := (obj['headers'] or { json2.Any('') }).as_map()
|
|
for key, val in headers_obj {
|
|
headers[key] = OpenAPIHeader.from_json(val)
|
|
}
|
|
}
|
|
|
|
// Parse security_schemes map
|
|
mut security_schemes := map[string]OpenAPISecurityScheme{}
|
|
if 'securitySchemes' in obj {
|
|
security_schemes_obj := (obj['securitySchemes'] or { json2.Any('') }).as_map()
|
|
for key, val in security_schemes_obj {
|
|
security_schemes[key] = OpenAPISecurityScheme.from_json(val)
|
|
}
|
|
}
|
|
|
|
return OpenAPIComponents{
|
|
schemas: schemas
|
|
responses: responses
|
|
parameters: parameters
|
|
request_bodies: request_bodies
|
|
headers: headers
|
|
security_schemes: security_schemes
|
|
}
|
|
}
|
|
|
|
// from_json - 从 JSON Any 对象反序列化 OpenAPIDocument
|
|
pub fn OpenAPIDocument.from_json(j json2.Any) OpenAPIDocument {
|
|
obj := j.as_map()
|
|
|
|
// Parse servers array
|
|
mut servers := []OpenAPIServer{}
|
|
if 'servers' in obj {
|
|
for s in (obj['servers'] or { json2.Any('') }).arr() {
|
|
servers << OpenAPIServer.from_json(s)
|
|
}
|
|
}
|
|
|
|
// Parse paths map
|
|
mut paths := map[string]OpenAPIPathItem{}
|
|
if 'paths' in obj {
|
|
paths_obj := (obj['paths'] or { json2.Any('') }).as_map()
|
|
for key, val in paths_obj {
|
|
paths[key] = OpenAPIPathItem.from_json(val)
|
|
}
|
|
}
|
|
|
|
// Parse security array
|
|
mut security := []map[string][]string{}
|
|
if 'security' in obj {
|
|
for sec in (obj['security'] or { json2.Any('') }).arr() {
|
|
mut sec_map := map[string][]string{}
|
|
sec_obj := sec.as_map()
|
|
for key, val in sec_obj {
|
|
mut scopes := []string{}
|
|
for scope in val.arr() {
|
|
scopes << scope.str()
|
|
}
|
|
sec_map[key] = scopes
|
|
}
|
|
security << sec_map
|
|
}
|
|
}
|
|
|
|
// Parse tags array
|
|
mut tags := []OpenAPITag{}
|
|
if 'tags' in obj {
|
|
for t in (obj['tags'] or { json2.Any('') }).arr() {
|
|
tags << OpenAPITag.from_json(t)
|
|
}
|
|
}
|
|
|
|
return OpenAPIDocument{
|
|
openapi: if 'openapi' in obj { (obj['openapi'] or { json2.Any('') }).str() } else { '' }
|
|
info: if 'info' in obj { OpenAPIInfo.from_json(obj['info'] or { json2.Any('') }) } else { OpenAPIInfo{} }
|
|
servers: servers
|
|
paths: paths
|
|
components: if 'components' in obj { OpenAPIComponents.from_json(obj['components'] or { json2.Any('') }) } else { OpenAPIComponents{} }
|
|
security: security
|
|
tags: tags
|
|
external_docs: if 'externalDocs' in obj { OpenAPIExternalDocs.from_json(obj['externalDocs'] or { json2.Any('') }) } else { OpenAPIExternalDocs{} }
|
|
}
|
|
}
|
|
|
|
// from_json_str - 从 JSON 字符串反序列化 OpenAPIDocument
|
|
pub fn OpenAPIDocument.from_json_str(json_str string) !OpenAPIDocument {
|
|
parsed := json2.decode[json2.Any](json_str)!
|
|
return OpenAPIDocument.from_json(parsed)
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// OpenAPI 文档验证 (Task 4.1)
|
|
// ============================================================================
|
|
|
|
// OpenAPIErrorKind - OpenAPI 错误类型
|
|
pub enum OpenAPIErrorKind {
|
|
missing_required_field
|
|
invalid_version
|
|
invalid_path
|
|
invalid_parameter
|
|
invalid_schema
|
|
serialization_error
|
|
deserialization_error
|
|
}
|
|
|
|
// OpenAPIError - OpenAPI 相关错误
|
|
pub struct OpenAPIError {
|
|
Error
|
|
pub:
|
|
kind OpenAPIErrorKind
|
|
error_msg string
|
|
path string // 可选:问题路径
|
|
field string // 可选:问题字段
|
|
}
|
|
|
|
// msg - 实现 IError 接口
|
|
pub fn (e OpenAPIError) msg() string {
|
|
return e.error_msg
|
|
}
|
|
|
|
// 支持的 OpenAPI 版本
|
|
const supported_openapi_versions = ['3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.1.0']
|
|
|
|
// validate - 验证 OpenAPIDocument 的有效性
|
|
// 验证必需字段、OpenAPI 版本和路径格式
|
|
// 返回描述性错误信息
|
|
pub fn (doc OpenAPIDocument) validate() ! {
|
|
// 1. 验证必需字段: openapi
|
|
if is_empty(doc.openapi) {
|
|
return error("Missing required field 'openapi' in OpenAPI document")
|
|
}
|
|
|
|
// 2. 验证 OpenAPI 版本 (3.0.x, 3.1.x)
|
|
if doc.openapi !in supported_openapi_versions {
|
|
return error("Unsupported OpenAPI version '${doc.openapi}'. Supported versions: ${supported_openapi_versions.join(', ')}")
|
|
}
|
|
|
|
// 3. 验证必需字段: info
|
|
if is_empty(doc.info.title) {
|
|
return error("Missing required field 'info.title' in OpenAPI document")
|
|
}
|
|
|
|
if is_empty(doc.info.version) {
|
|
return error("Missing required field 'info.version' in OpenAPI document")
|
|
}
|
|
|
|
// 4. 验证路径格式
|
|
for path, _ in doc.paths {
|
|
// 路径必须以 '/' 开头
|
|
if !path.starts_with('/') {
|
|
return error("Invalid path '${path}': paths must start with '/'")
|
|
}
|
|
|
|
// 检查路径参数格式 - 支持 {param} 和 :param 两种格式
|
|
// 验证 {param} 格式的括号匹配
|
|
mut open_braces := 0
|
|
mut last_open_idx := -1
|
|
for i, c in path {
|
|
if c == `{` {
|
|
open_braces++
|
|
last_open_idx = i
|
|
} else if c == `}` {
|
|
open_braces--
|
|
if open_braces < 0 {
|
|
return error("Invalid path '${path}': unmatched '}' at position ${i}")
|
|
}
|
|
}
|
|
}
|
|
if open_braces > 0 {
|
|
return error("Invalid path '${path}': unmatched '{' at position ${last_open_idx}")
|
|
}
|
|
|
|
// 检查空路径参数 {} 或 :
|
|
if path.contains('{}') {
|
|
return error("Invalid path '${path}': empty path parameter '{}'")
|
|
}
|
|
|
|
// 检查连续斜杠
|
|
if path.contains('//') {
|
|
return error("Invalid path '${path}': contains consecutive slashes '//'")
|
|
}
|
|
}
|
|
|
|
// 验证通过
|
|
return
|
|
}
|
|
|
|
// validate_with_details - 验证并返回所有错误(不仅仅是第一个)
|
|
pub fn (doc OpenAPIDocument) validate_with_details() []OpenAPIError {
|
|
mut errors := []OpenAPIError{}
|
|
|
|
// 1. 验证必需字段: openapi
|
|
if is_empty(doc.openapi) {
|
|
errors << OpenAPIError{
|
|
kind: .missing_required_field
|
|
error_msg: "Missing required field 'openapi' in OpenAPI document"
|
|
field: 'openapi'
|
|
}
|
|
} else if doc.openapi !in supported_openapi_versions {
|
|
// 2. 验证 OpenAPI 版本
|
|
errors << OpenAPIError{
|
|
kind: .invalid_version
|
|
error_msg: "Unsupported OpenAPI version '${doc.openapi}'. Supported versions: ${supported_openapi_versions.join(', ')}"
|
|
field: 'openapi'
|
|
}
|
|
}
|
|
|
|
// 3. 验证必需字段: info
|
|
if is_empty(doc.info.title) {
|
|
errors << OpenAPIError{
|
|
kind: .missing_required_field
|
|
error_msg: "Missing required field 'info.title' in OpenAPI document"
|
|
field: 'info.title'
|
|
}
|
|
}
|
|
|
|
if is_empty(doc.info.version) {
|
|
errors << OpenAPIError{
|
|
kind: .missing_required_field
|
|
error_msg: "Missing required field 'info.version' in OpenAPI document"
|
|
field: 'info.version'
|
|
}
|
|
}
|
|
|
|
// 4. 验证路径格式
|
|
for path, _ in doc.paths {
|
|
// 路径必须以 '/' 开头
|
|
if !path.starts_with('/') {
|
|
errors << OpenAPIError{
|
|
kind: .invalid_path
|
|
error_msg: "Invalid path '${path}': paths must start with '/'"
|
|
path: path
|
|
}
|
|
continue
|
|
}
|
|
|
|
// 检查路径参数格式
|
|
mut open_braces := 0
|
|
mut last_open_idx := -1
|
|
mut has_brace_error := false
|
|
for i, c in path {
|
|
if c == `{` {
|
|
open_braces++
|
|
last_open_idx = i
|
|
} else if c == `}` {
|
|
open_braces--
|
|
if open_braces < 0 {
|
|
errors << OpenAPIError{
|
|
kind: .invalid_path
|
|
error_msg: "Invalid path '${path}': unmatched '}' at position ${i}"
|
|
path: path
|
|
}
|
|
has_brace_error = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !has_brace_error && open_braces > 0 {
|
|
errors << OpenAPIError{
|
|
kind: .invalid_path
|
|
error_msg: "Invalid path '${path}': unmatched '{' at position ${last_open_idx}"
|
|
path: path
|
|
}
|
|
}
|
|
|
|
// 检查空路径参数
|
|
if path.contains('{}') {
|
|
errors << OpenAPIError{
|
|
kind: .invalid_path
|
|
error_msg: "Invalid path '${path}': empty path parameter '{}'"
|
|
path: path
|
|
}
|
|
}
|
|
|
|
// 检查连续斜杠
|
|
if path.contains('//') {
|
|
errors << OpenAPIError{
|
|
kind: .invalid_path
|
|
error_msg: "Invalid path '${path}': contains consecutive slashes '//'"
|
|
path: path
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
// is_valid - 检查文档是否有效
|
|
pub fn (doc OpenAPIDocument) is_valid() bool {
|
|
doc.validate() or { return false }
|
|
return true
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// OpenAPI 文档构建器 (Task 5)
|
|
// ============================================================================
|
|
|
|
// OpenAPIBuilder - 流式 API 构建器
|
|
// 使用示例:
|
|
// doc := OpenAPIBuilder.new()
|
|
// .openapi('3.0.0')
|
|
// .title('My API')
|
|
// .version('1.0.0')
|
|
// .description('API description')
|
|
// .server('https://api.example.com', 'Production server')
|
|
// .tag('users', 'User operations')
|
|
// .path('/users')
|
|
// .get(OpenAPIOperation{ ... })
|
|
// .post(OpenAPIOperation{ ... })
|
|
// .done()
|
|
// .build()!
|
|
pub struct OpenAPIBuilder {
|
|
mut:
|
|
doc OpenAPIDocument
|
|
}
|
|
|
|
// new - 创建新的 OpenAPIBuilder 实例
|
|
pub fn OpenAPIBuilder.new() OpenAPIBuilder {
|
|
return OpenAPIBuilder{
|
|
doc: OpenAPIDocument{
|
|
paths: map[string]OpenAPIPathItem{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// openapi - 设置 OpenAPI 版本
|
|
pub fn (mut b OpenAPIBuilder) openapi(version string) &OpenAPIBuilder {
|
|
b.doc.openapi = version
|
|
return unsafe { b }
|
|
}
|
|
|
|
// info - 设置完整的 API 信息
|
|
pub fn (mut b OpenAPIBuilder) info(info OpenAPIInfo) &OpenAPIBuilder {
|
|
b.doc.info = info
|
|
return unsafe { b }
|
|
}
|
|
|
|
// title - 设置 API 标题
|
|
pub fn (mut b OpenAPIBuilder) title(title string) &OpenAPIBuilder {
|
|
b.doc.info.title = title
|
|
return unsafe { b }
|
|
}
|
|
|
|
// version - 设置 API 版本
|
|
pub fn (mut b OpenAPIBuilder) version(version string) &OpenAPIBuilder {
|
|
b.doc.info.version = version
|
|
return unsafe { b }
|
|
}
|
|
|
|
// description - 设置 API 描述
|
|
pub fn (mut b OpenAPIBuilder) description(desc string) &OpenAPIBuilder {
|
|
b.doc.info.description = desc
|
|
return unsafe { b }
|
|
}
|
|
|
|
// terms_of_service - 设置服务条款 URL
|
|
pub fn (mut b OpenAPIBuilder) terms_of_service(url string) &OpenAPIBuilder {
|
|
b.doc.info.terms_of_service = url
|
|
return unsafe { b }
|
|
}
|
|
|
|
// contact - 设置联系信息
|
|
pub fn (mut b OpenAPIBuilder) contact(contact OpenAPIContact) &OpenAPIBuilder {
|
|
b.doc.info.contact = contact
|
|
return unsafe { b }
|
|
}
|
|
|
|
// license - 设置许可证信息
|
|
pub fn (mut b OpenAPIBuilder) license(license OpenAPILicense) &OpenAPIBuilder {
|
|
b.doc.info.license = license
|
|
return unsafe { b }
|
|
}
|
|
|
|
// server - 添加服务器信息
|
|
pub fn (mut b OpenAPIBuilder) server(url string, description string) &OpenAPIBuilder {
|
|
b.doc.servers << OpenAPIServer{
|
|
url: url
|
|
description: description
|
|
}
|
|
return unsafe { b }
|
|
}
|
|
|
|
// tag - 添加标签
|
|
pub fn (mut b OpenAPIBuilder) tag(name string, description string) &OpenAPIBuilder {
|
|
b.doc.tags << OpenAPITag{
|
|
name: name
|
|
description: description
|
|
}
|
|
return unsafe { b }
|
|
}
|
|
|
|
// external_docs - 设置外部文档
|
|
pub fn (mut b OpenAPIBuilder) external_docs(url string, description string) &OpenAPIBuilder {
|
|
b.doc.external_docs = OpenAPIExternalDocs{
|
|
url: url
|
|
description: description
|
|
}
|
|
return unsafe { b }
|
|
}
|
|
|
|
// add_schema - 添加可重用的 Schema 到 components
|
|
pub fn (mut b OpenAPIBuilder) add_schema(name string, schema OpenAPISchema) &OpenAPIBuilder {
|
|
b.doc.components.schemas[name] = schema
|
|
return unsafe { b }
|
|
}
|
|
|
|
// add_response - 添加可重用的 Response 到 components
|
|
pub fn (mut b OpenAPIBuilder) add_response(name string, response OpenAPIResponse) &OpenAPIBuilder {
|
|
b.doc.components.responses[name] = response
|
|
return unsafe { b }
|
|
}
|
|
|
|
// add_parameter - 添加可重用的 Parameter 到 components
|
|
pub fn (mut b OpenAPIBuilder) add_parameter(name string, param OpenAPIParameter) &OpenAPIBuilder {
|
|
b.doc.components.parameters[name] = param
|
|
return unsafe { b }
|
|
}
|
|
|
|
// add_security_scheme - 添加安全方案到 components
|
|
pub fn (mut b OpenAPIBuilder) add_security_scheme(name string, scheme OpenAPISecurityScheme) &OpenAPIBuilder {
|
|
b.doc.components.security_schemes[name] = scheme
|
|
return unsafe { b }
|
|
}
|
|
|
|
// security - 添加全局安全要求
|
|
pub fn (mut b OpenAPIBuilder) security(requirements map[string][]string) &OpenAPIBuilder {
|
|
b.doc.security << requirements
|
|
return unsafe { b }
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// OpenAPIPathBuilder - 路径构建器 (Task 5.2)
|
|
// ============================================================================
|
|
|
|
// OpenAPIPathBuilder - 路径构建器
|
|
// 用于为特定路径添加 HTTP 方法操作
|
|
pub struct OpenAPIPathBuilder {
|
|
mut:
|
|
parent &OpenAPIBuilder = unsafe { nil }
|
|
path string
|
|
path_item OpenAPIPathItem
|
|
}
|
|
|
|
// path - 开始构建路径,返回 OpenAPIPathBuilder
|
|
pub fn (mut b OpenAPIBuilder) path(path string) OpenAPIPathBuilder {
|
|
// 如果路径已存在,获取现有的 path_item
|
|
existing := b.doc.paths[path] or { OpenAPIPathItem{} }
|
|
return OpenAPIPathBuilder{
|
|
parent: unsafe { b }
|
|
path: path
|
|
path_item: existing
|
|
}
|
|
}
|
|
|
|
// summary - 设置路径摘要
|
|
pub fn (mut pb OpenAPIPathBuilder) summary(summary string) &OpenAPIPathBuilder {
|
|
pb.path_item.summary = summary
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// path_description - 设置路径描述
|
|
pub fn (mut pb OpenAPIPathBuilder) path_description(description string) &OpenAPIPathBuilder {
|
|
pb.path_item.description = description
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// parameters - 设置路径级别的参数
|
|
pub fn (mut pb OpenAPIPathBuilder) parameters(params []OpenAPIParameter) &OpenAPIPathBuilder {
|
|
pb.path_item.parameters = params
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// get - 添加 GET 操作
|
|
pub fn (mut pb OpenAPIPathBuilder) get(op OpenAPIOperation) &OpenAPIPathBuilder {
|
|
pb.path_item.get = op
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// post - 添加 POST 操作
|
|
pub fn (mut pb OpenAPIPathBuilder) post(op OpenAPIOperation) &OpenAPIPathBuilder {
|
|
pb.path_item.post = op
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// put - 添加 PUT 操作
|
|
pub fn (mut pb OpenAPIPathBuilder) put(op OpenAPIOperation) &OpenAPIPathBuilder {
|
|
pb.path_item.put = op
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// delete - 添加 DELETE 操作
|
|
pub fn (mut pb OpenAPIPathBuilder) delete(op OpenAPIOperation) &OpenAPIPathBuilder {
|
|
pb.path_item.delete = op
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// patch - 添加 PATCH 操作
|
|
pub fn (mut pb OpenAPIPathBuilder) patch(op OpenAPIOperation) &OpenAPIPathBuilder {
|
|
pb.path_item.patch = op
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// head - 添加 HEAD 操作
|
|
pub fn (mut pb OpenAPIPathBuilder) head(op OpenAPIOperation) &OpenAPIPathBuilder {
|
|
pb.path_item.head = op
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// options - 添加 OPTIONS 操作
|
|
pub fn (mut pb OpenAPIPathBuilder) options(op OpenAPIOperation) &OpenAPIPathBuilder {
|
|
pb.path_item.options = op
|
|
return unsafe { pb }
|
|
}
|
|
|
|
// done - 完成路径构建,返回父构建器
|
|
pub fn (mut pb OpenAPIPathBuilder) done() &OpenAPIBuilder {
|
|
if pb.parent != unsafe { nil } {
|
|
pb.parent.doc.paths[pb.path] = pb.path_item
|
|
}
|
|
return unsafe { pb.parent }
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// OpenAPIBuilder build 方法 (Task 5.3)
|
|
// ============================================================================
|
|
|
|
// build - 构建并验证 OpenAPI 文档
|
|
// 调用 validate() 验证文档,如果验证失败则返回错误
|
|
pub fn (b OpenAPIBuilder) build() !OpenAPIDocument {
|
|
// 验证文档
|
|
b.doc.validate()!
|
|
return b.doc
|
|
}
|
|
|
|
// build_unchecked - 构建 OpenAPI 文档(不验证)
|
|
// 用于需要跳过验证的场景
|
|
pub fn (b OpenAPIBuilder) build_unchecked() OpenAPIDocument {
|
|
return b.doc
|
|
}
|
|
|
|
// validate - 验证当前构建的文档
|
|
pub fn (b OpenAPIBuilder) validate() ! {
|
|
return b.doc.validate()
|
|
}
|
|
|
|
// get_document - 获取当前构建的文档(不验证)
|
|
pub fn (b OpenAPIBuilder) get_document() OpenAPIDocument {
|
|
return b.doc
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// 路由信息提取 (Task 8)
|
|
// ============================================================================
|
|
|
|
// RouteInfo - 路由信息结构体
|
|
// 包含路由的路径、HTTP 方法、路径参数和处理器信息
|
|
// 用于自动生成 OpenAPI 文档
|
|
pub struct RouteInfo {
|
|
pub:
|
|
path string // 路由路径,如 "/users/:id"
|
|
method string // HTTP 方法,如 "GET", "POST"
|
|
path_params []string // 路径参数列表,如 ["id"]
|
|
handler IHandler // 路由处理器
|
|
}
|
|
|
|
// extract_path_params - 从路径中提取路径参数
|
|
// 支持 :param 格式的路径参数
|
|
// 例如: "/users/:id/posts/:postId" -> ["id", "postId"]
|
|
fn extract_path_params(path string) []string {
|
|
mut params := []string{}
|
|
segments := path.split('/')
|
|
for segment in segments {
|
|
if segment.starts_with(':') && segment.len > 1 {
|
|
// 提取参数名(去掉冒号)
|
|
param_name := segment[1..]
|
|
params << param_name
|
|
}
|
|
}
|
|
return params
|
|
}
|
|
|
|
// get_routes - 获取应用的所有路由信息
|
|
// 遍历所有注册的路由,包含子应用的路由
|
|
// 提取路径参数
|
|
// 返回 RouteInfo 数组
|
|
pub fn (app Hono) get_routes() []RouteInfo {
|
|
mut routes := []RouteInfo{}
|
|
|
|
// 从 context_router 获取所有路由
|
|
// GET 路由
|
|
for handler in app.context_router.handlers.get {
|
|
routes << RouteInfo{
|
|
path: handler.path
|
|
method: 'GET'
|
|
path_params: extract_path_params(handler.path)
|
|
handler: handler
|
|
}
|
|
}
|
|
|
|
// POST 路由
|
|
for handler in app.context_router.handlers.post {
|
|
routes << RouteInfo{
|
|
path: handler.path
|
|
method: 'POST'
|
|
path_params: extract_path_params(handler.path)
|
|
handler: handler
|
|
}
|
|
}
|
|
|
|
// PUT 路由
|
|
for handler in app.context_router.handlers.put {
|
|
routes << RouteInfo{
|
|
path: handler.path
|
|
method: 'PUT'
|
|
path_params: extract_path_params(handler.path)
|
|
handler: handler
|
|
}
|
|
}
|
|
|
|
// DELETE 路由
|
|
for handler in app.context_router.handlers.delete {
|
|
routes << RouteInfo{
|
|
path: handler.path
|
|
method: 'DELETE'
|
|
path_params: extract_path_params(handler.path)
|
|
handler: handler
|
|
}
|
|
}
|
|
|
|
// PATCH 路由
|
|
for handler in app.context_router.handlers.patch {
|
|
routes << RouteInfo{
|
|
path: handler.path
|
|
method: 'PATCH'
|
|
path_params: extract_path_params(handler.path)
|
|
handler: handler
|
|
}
|
|
}
|
|
|
|
// HEAD 路由
|
|
for handler in app.context_router.handlers.head {
|
|
routes << RouteInfo{
|
|
path: handler.path
|
|
method: 'HEAD'
|
|
path_params: extract_path_params(handler.path)
|
|
handler: handler
|
|
}
|
|
}
|
|
|
|
// OPTIONS 路由
|
|
for handler in app.context_router.handlers.options {
|
|
routes << RouteInfo{
|
|
path: handler.path
|
|
method: 'OPTIONS'
|
|
path_params: extract_path_params(handler.path)
|
|
handler: handler
|
|
}
|
|
}
|
|
|
|
// 从子应用获取路由(已经在 route() 方法中合并到主应用)
|
|
// 子应用的路由已经通过 merge_routes_for_method 添加到 context_router 中
|
|
// 所以上面的遍历已经包含了子应用的路由
|
|
|
|
return routes
|
|
}
|
|
|
|
// generate_openapi_paths - 从路由信息生成 OpenAPI paths
|
|
// 将 RouteInfo 数组转换为 OpenAPI PathItem 映射
|
|
pub fn generate_openapi_paths(routes []RouteInfo) map[string]OpenAPIPathItem {
|
|
mut paths := map[string]OpenAPIPathItem{}
|
|
|
|
for route in routes {
|
|
// 将 :param 格式转换为 {param} 格式(OpenAPI 标准)
|
|
openapi_path := convert_path_to_openapi_format(route.path)
|
|
|
|
// 获取或创建 PathItem
|
|
mut path_item := paths[openapi_path] or { OpenAPIPathItem{} }
|
|
|
|
// 创建基本的 Operation
|
|
mut operation := OpenAPIOperation{
|
|
responses: {
|
|
'200': OpenAPIResponse{
|
|
description: 'Successful response'
|
|
}
|
|
}
|
|
}
|
|
|
|
// 添加路径参数
|
|
for param_name in route.path_params {
|
|
operation.parameters << OpenAPIParameter{
|
|
name: param_name
|
|
in_location: 'path'
|
|
required: true
|
|
schema: OpenAPISchema{
|
|
schema_type: 'string'
|
|
}
|
|
}
|
|
}
|
|
|
|
// 根据 HTTP 方法设置操作
|
|
match route.method {
|
|
'GET' { path_item.get = operation }
|
|
'POST' { path_item.post = operation }
|
|
'PUT' { path_item.put = operation }
|
|
'DELETE' { path_item.delete = operation }
|
|
'PATCH' { path_item.patch = operation }
|
|
'HEAD' { path_item.head = operation }
|
|
'OPTIONS' { path_item.options = operation }
|
|
else {}
|
|
}
|
|
|
|
paths[openapi_path] = path_item
|
|
}
|
|
|
|
return paths
|
|
}
|
|
|
|
// convert_path_to_openapi_format - 将 v-hono 路径格式转换为 OpenAPI 格式
|
|
// 将 :param 转换为 {param}
|
|
// 例如: "/users/:id" -> "/users/{id}"
|
|
fn convert_path_to_openapi_format(path string) string {
|
|
mut result := path
|
|
segments := path.split('/')
|
|
for segment in segments {
|
|
if segment.starts_with(':') && segment.len > 1 {
|
|
param_name := segment[1..]
|
|
result = result.replace(':${param_name}', '{${param_name}}')
|
|
}
|
|
}
|
|
return result
|
|
}
|