From 2d1ed133f24e05e206d989edd43afb1096291f05 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 21:32:21 +0000 Subject: [PATCH] refactor(api): align OpenAPI client with AX principles Use core-style error wrapping in the OpenAPI client, replace direct spec reads with streamed file I/O, and add compile-time interface assertions for ToolBridge. Co-Authored-By: Virgil --- bridge.go | 3 +++ client.go | 44 ++++++++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/bridge.go b/bridge.go index 099a167..11fba20 100644 --- a/bridge.go +++ b/bridge.go @@ -42,6 +42,9 @@ type boundTool struct { handler gin.HandlerFunc } +var _ RouteGroup = (*ToolBridge)(nil) +var _ DescribableGroup = (*ToolBridge)(nil) + // NewToolBridge creates a bridge that mounts tool endpoints at basePath. // // Example: diff --git a/client.go b/client.go index d8d9a00..7ff11ed 100644 --- a/client.go +++ b/client.go @@ -15,6 +15,8 @@ import ( "sync" "gopkg.in/yaml.v3" + + coreerr "dappco.re/go/core/log" ) // OpenAPIClient is a small runtime client that can call operations by their @@ -139,7 +141,7 @@ func (c *OpenAPIClient) Call(operationID string, params any) (any, error) { op, ok := c.operations[operationID] if !ok { - return nil, fmt.Errorf("operation %q not found in OpenAPI spec", operationID) + return nil, coreerr.E("OpenAPIClient.Call", fmt.Sprintf("operation %q not found in OpenAPI spec", operationID), nil) } merged, err := normaliseParams(params) @@ -192,7 +194,7 @@ func (c *OpenAPIClient) Call(operationID string, params any) (any, error) { } if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return nil, fmt.Errorf("openapi call %s returned %s: %s", operationID, resp.Status, strings.TrimSpace(string(payload))) + return nil, coreerr.E("OpenAPIClient.Call", fmt.Sprintf("openapi call %s returned %s: %s", operationID, resp.Status, strings.TrimSpace(string(payload))), nil) } if op.responseSchema != nil && len(bytes.TrimSpace(payload)) > 0 { @@ -216,9 +218,9 @@ func (c *OpenAPIClient) Call(operationID string, params any) (any, error) { if success, ok := envelope["success"].(bool); ok { if !success { if errObj, ok := envelope["error"].(map[string]any); ok { - return nil, fmt.Errorf("openapi call %s failed: %v", operationID, errObj) + return nil, coreerr.E("OpenAPIClient.Call", fmt.Sprintf("openapi call %s failed: %v", operationID, errObj), nil) } - return nil, fmt.Errorf("openapi call %s failed", operationID) + return nil, coreerr.E("OpenAPIClient.Call", fmt.Sprintf("openapi call %s failed", operationID), nil) } if data, ok := envelope["data"]; ok { return data, nil @@ -238,17 +240,23 @@ func (c *OpenAPIClient) load() error { func (c *OpenAPIClient) loadSpec() error { if c.specPath == "" { - return fmt.Errorf("spec path is required") + return coreerr.E("OpenAPIClient.loadSpec", "spec path is required", nil) } - data, err := os.ReadFile(c.specPath) + f, err := os.Open(c.specPath) if err != nil { - return fmt.Errorf("read spec: %w", err) + return coreerr.E("OpenAPIClient.loadSpec", "read spec", err) + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + return coreerr.E("OpenAPIClient.loadSpec", "read spec", err) } var spec map[string]any if err := yaml.Unmarshal(data, &spec); err != nil { - return fmt.Errorf("parse spec: %w", err) + return coreerr.E("OpenAPIClient.loadSpec", "parse spec", err) } operations := make(map[string]openAPIOperation) @@ -309,7 +317,7 @@ func (c *OpenAPIClient) loadSpec() error { func (c *OpenAPIClient) buildURL(op openAPIOperation, params map[string]any) (string, error) { base := strings.TrimRight(c.baseURL, "/") if base == "" { - return "", fmt.Errorf("base URL is required") + return "", coreerr.E("OpenAPIClient.buildURL", "base URL is required", nil) } path := op.pathTemplate @@ -328,7 +336,7 @@ func (c *OpenAPIClient) buildURL(op openAPIOperation, params map[string]any) (st } if strings.Contains(path, "{") { - return "", fmt.Errorf("missing path parameters for %q", op.pathTemplate) + return "", coreerr.E("OpenAPIClient.buildURL", fmt.Sprintf("missing path parameters for %q", op.pathTemplate), nil) } fullURL, err := url.JoinPath(base, path) @@ -577,12 +585,12 @@ func normaliseParams(params any) (map[string]any, error) { data, err := json.Marshal(params) if err != nil { - return nil, fmt.Errorf("marshal params: %w", err) + return nil, coreerr.E("OpenAPIClient.normaliseParams", "marshal params", err) } var out map[string]any if err := json.Unmarshal(data, &out); err != nil { - return nil, fmt.Errorf("decode params: %w", err) + return nil, coreerr.E("OpenAPIClient.normaliseParams", "decode params", err) } return out, nil @@ -741,15 +749,15 @@ func validateOpenAPISchema(body []byte, schema map[string]any, label string) err dec := json.NewDecoder(bytes.NewReader(body)) dec.UseNumber() if err := dec.Decode(&payload); err != nil { - return fmt.Errorf("validate %s: invalid JSON: %w", label, err) + return coreerr.E("OpenAPIClient.validateOpenAPISchema", fmt.Sprintf("validate %s: invalid JSON", label), err) } var extra any if err := dec.Decode(&extra); err != io.EOF { - return fmt.Errorf("validate %s: expected a single JSON value", label) + return coreerr.E("OpenAPIClient.validateOpenAPISchema", fmt.Sprintf("validate %s: expected a single JSON value", label), nil) } if err := validateSchemaNode(payload, schema, ""); err != nil { - return fmt.Errorf("validate %s: %w", label, err) + return coreerr.E("OpenAPIClient.validateOpenAPISchema", fmt.Sprintf("validate %s", label), err) } return nil @@ -760,15 +768,15 @@ func validateOpenAPIResponse(payload []byte, schema map[string]any, operationID dec := json.NewDecoder(bytes.NewReader(payload)) dec.UseNumber() if err := dec.Decode(&decoded); err != nil { - return fmt.Errorf("openapi call %s returned invalid JSON: %w", operationID, err) + return coreerr.E("OpenAPIClient.validateOpenAPIResponse", fmt.Sprintf("openapi call %s returned invalid JSON", operationID), err) } var extra any if err := dec.Decode(&extra); err != io.EOF { - return fmt.Errorf("openapi call %s returned multiple JSON values", operationID) + return coreerr.E("OpenAPIClient.validateOpenAPIResponse", fmt.Sprintf("openapi call %s returned multiple JSON values", operationID), nil) } if err := validateSchemaNode(decoded, schema, ""); err != nil { - return fmt.Errorf("openapi call %s response does not match spec: %w", operationID, err) + return coreerr.E("OpenAPIClient.validateOpenAPIResponse", fmt.Sprintf("openapi call %s response does not match spec", operationID), err) } return nil