// Regenerates the CALL-MEMORY extension from the public Go request models. package main import ( "data-terminal-cloud/internal/callmemory" "encoding/json" "os" "reflect" "strings" "time" ) func schema(t reflect.Type) map[string]any { if t.Kind() == reflect.Pointer { return map[string]any{"anyOf": []any{schema(t.Elem()), map[string]any{"type": "null"}}} } if t == reflect.TypeOf(time.Time{}) { return map[string]any{"type": "string", "format": "date-time"} } if t == reflect.TypeOf(json.RawMessage{}) { return map[string]any{} } switch t.Kind() { case reflect.String: return map[string]any{"type": "string"} case reflect.Bool: return map[string]any{"type": "boolean"} case reflect.Int, reflect.Int64: return map[string]any{"type": "integer", "format": "int64", "minimum": 0} case reflect.Slice: return map[string]any{"type": "array", "items": schema(t.Elem())} case reflect.Map: return map[string]any{"type": "object", "additionalProperties": schema(t.Elem())} case reflect.Struct: props := map[string]any{} required := []string{} for i := 0; i < t.NumField(); i++ { f := t.Field(i) tag := strings.Split(f.Tag.Get("json"), ",") if tag[0] == "-" { continue } if f.Anonymous { nested := schema(f.Type) for k, v := range nested["properties"].(map[string]any) { props[k] = v } required = append(required, nested["required"].([]string)...) continue } name := tag[0] if name == "" { name = f.Name } props[name] = schema(f.Type) if !strings.Contains(f.Tag.Get("json"), "omitempty") { required = append(required, name) } } return map[string]any{"type": "object", "additionalProperties": false, "properties": props, "required": required} default: return map[string]any{} } } func main() { device := map[string]any{"warmup": callmemory.WarmupInput{}, "prepare": callmemory.PrepareInput{}, "events": callmemory.EventsInput{}, "heartbeat": callmemory.HeartbeatInput{}, "end": callmemory.EndInput{}, "status": callmemory.StatusInput{}, "command-acks": callmemory.AckInput{}} memory := map[string]any{"query": callmemory.QueryInput{}, "mutations": callmemory.MutationInput{}, "mutations/resolve": callmemory.ResolveInput{}, "mutations/confirm": callmemory.ConfirmInput{}, "mutations/status": struct { ID string `json:"mutation_id"` }{}, "sync/status": struct { ID string `json:"elder_id"` }{}, "access/query": struct { Elder string `json:"elder_id"` }{}, "access/grant": callmemory.AccessInput{}} paths := map[string]any{} schemas := map[string]any{"Manifest": schema(reflect.TypeOf(callmemory.Manifest{}))} for family, ops := range map[string]map[string]any{"device/calls": device, "memories": memory} { for name, model := range ops { key := strings.ReplaceAll(family+"/"+name, "/", "_") security := "CustomerBearer" description := "登录用户,独立记忆权限校验。正文上限 64 KiB,拒绝重复 JSON 键和未知字段。变更需提交稳定 mutation_id 与目标 record_version。" parameters := []any{map[string]any{"in": "header", "name": "X-Request-ID", "required": family == "device/calls", "schema": map[string]any{"type": "string", "format": "uuid"}}} if family == "device/calls" { security = "DeviceHMAC" description = "ONBOARDING-1.1 DEVREQ1 签名原始正文。nonce 5 分钟去重,时钟偏差不超过 120 秒。事件和 status 不续租;heartbeat 必须在启动 SDK 前成功,后续每 5 秒续租,绝对期限最长 45 秒。" for _, header := range []string{"X-Device-ID", "X-Key-Version", "X-Timestamp-Ms", "X-Nonce"} { parameters = append(parameters, map[string]any{"in": "header", "name": header, "required": true, "schema": map[string]any{"type": "string"}}) } } schemas[key] = schema(reflect.TypeOf(model)) responses := map[string]any{} for _, status := range []string{"200", "400", "401", "403", "404", "409", "410", "413", "422", "429", "503"} { responses[status] = map[string]any{"description": "CALL-MEMORY envelope; code/recovery_action are stable, data is present on success", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"$ref": "#/components/schemas/Envelope"}}}} } paths["/api/v1/"+family+"/"+name] = map[string]any{"post": map[string]any{"operationId": key, "description": description, "security": []any{map[string]any{security: []string{}}}, "parameters": parameters, "requestBody": map[string]any{"required": true, "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"$ref": "#/components/schemas/" + key}}}}, "responses": responses}} } } schemas["Envelope"] = map[string]any{"type": "object", "required": []string{"ok", "code", "request_id", "server_time_ms"}, "properties": map[string]any{"ok": map[string]any{"type": "boolean"}, "code": map[string]any{"type": "string"}, "request_id": map[string]any{"type": "string"}, "server_time_ms": map[string]any{"type": "integer"}, "message": map[string]any{"type": "string"}, "recovery_action": map[string]any{"type": "string"}, "retry_after_ms": map[string]any{"type": "integer"}, "data": map[string]any{"type": "object", "additionalProperties": true}}} doc := map[string]any{"openapi": "3.1.0", "info": map[string]any{"title": "CALL-MEMORY-1.1", "version": "1.1.0"}, "paths": paths, "components": map[string]any{"schemas": schemas, "securitySchemes": map[string]any{"DeviceHMAC": map[string]any{"type": "apiKey", "in": "header", "name": "Authorization", "description": "Device-HMAC-SHA256 "}, "CustomerBearer": map[string]any{"type": "http", "scheme": "bearer"}}}} b, e := json.MarshalIndent(doc, "", " ") if e != nil { panic(e) } if e = os.WriteFile("openapi/call-memory.yaml", append(b, '\n'), 0644); e != nil { panic(e) } }