107 lines
5.7 KiB
Go
107 lines
5.7 KiB
Go
// 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 <hex HMAC-SHA256 of DEVREQ1 canonical bytes>"}, "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)
|
||
|
|
}
|
||
|
|
}
|