Add version/correlation headers to Nexus and Praxis ecosystem clients
mavend's hand-rolled Nexus and Praxis HTTP clients sent bare requests with no version or correlation headers, unlike the Hexis client. Added a shared context-based correlation ID mechanism and version headers (X-Nexus-Version, X-Praxis-Version) across all Nexus/Praxis call sites, and threaded the correlation ID already generated in executeCapability through to the Nexus/Praxis calls in the same request chain. Synced vendor/ copy of hexis/pkg/client after its WithCorrelationID addition. Part of Vikunja #273. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ghELqYhZNLub2TXGMazqA
This commit is contained in:
+34
-3
@@ -17,6 +17,30 @@ import (
|
|||||||
"github.com/kami/maven/internal/config"
|
"github.com/kami/maven/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ecosystemCorrelationKey carries a per-call correlation ID through context
|
||||||
|
// so every ecosystem client (Nexus, Praxis, Hexis) tags its request with the
|
||||||
|
// same ID, letting a single Maven-initiated action be traced end to end.
|
||||||
|
type ecosystemCorrelationKey struct{}
|
||||||
|
|
||||||
|
func withCorrelationID(ctx context.Context, id string) context.Context {
|
||||||
|
return context.WithValue(ctx, ecosystemCorrelationKey{}, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func correlationIDFromCtx(ctx context.Context) string {
|
||||||
|
id, _ := ctx.Value(ecosystemCorrelationKey{}).(string)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// setEcosystemHeaders stamps the version and correlation headers common to
|
||||||
|
// every outgoing ecosystem request.
|
||||||
|
func setEcosystemHeaders(req *http.Request, ctx context.Context, versionHeader string) {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set(versionHeader, "v1")
|
||||||
|
if id := correlationIDFromCtx(ctx); id != "" {
|
||||||
|
req.Header.Set("X-Correlation-ID", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type nexusClient struct {
|
type nexusClient struct {
|
||||||
baseURL string
|
baseURL string
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
@@ -83,7 +107,7 @@ func (c *nexusClient) Resolve(ctx context.Context, query string, types []string)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create request: %w", err)
|
return nil, fmt.Errorf("create request: %w", err)
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
setEcosystemHeaders(req, ctx, "X-Nexus-Version")
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,6 +132,7 @@ func (c *nexusClient) Health(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setEcosystemHeaders(req, ctx, "X-Nexus-Version")
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -140,6 +165,7 @@ func (c *praxisClient) getJSON(ctx context.Context, path string, out any) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setEcosystemHeaders(req, ctx, "X-Praxis-Version")
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -191,7 +217,7 @@ func (c *praxisClient) postItemAction(ctx context.Context, path, itemID string)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
setEcosystemHeaders(req, ctx, "X-Praxis-Version")
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -232,7 +258,7 @@ func (c *praxisClient) Pin(ctx context.Context, itemID string, pinned bool) (*pr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
setEcosystemHeaders(req, ctx, "X-Praxis-Version")
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -308,6 +334,9 @@ func (w *ecosystemWiring) resolveEntityReference(ctx context.Context, text strin
|
|||||||
if w == nil || w.nexus == nil {
|
if w == nil || w.nexus == nil {
|
||||||
return "", "", nil, nil
|
return "", "", nil, nil
|
||||||
}
|
}
|
||||||
|
if correlationIDFromCtx(ctx) == "" {
|
||||||
|
ctx = withCorrelationID(ctx, newCorrelationID())
|
||||||
|
}
|
||||||
result, err := w.nexus.Resolve(ctx, text, entityTypes)
|
result, err := w.nexus.Resolve(ctx, text, entityTypes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ecosystem: nexus resolve error: %v", err)
|
log.Printf("ecosystem: nexus resolve error: %v", err)
|
||||||
@@ -353,6 +382,8 @@ func (w *ecosystemWiring) executeCapability(ctx context.Context, capabilityID, t
|
|||||||
return "", fmt.Errorf("hexis not configured")
|
return "", fmt.Errorf("hexis not configured")
|
||||||
}
|
}
|
||||||
correlationID = newCorrelationID()
|
correlationID = newCorrelationID()
|
||||||
|
ctx = withCorrelationID(ctx, correlationID)
|
||||||
|
ctx = hexisclient.WithCorrelationID(ctx, correlationID)
|
||||||
req := hexisclient.ExecuteRequest{
|
req := hexisclient.ExecuteRequest{
|
||||||
CapabilityID: capabilityID,
|
CapabilityID: capabilityID,
|
||||||
TargetEntityID: targetEntityID,
|
TargetEntityID: targetEntityID,
|
||||||
|
|||||||
+38
@@ -10,6 +10,35 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// APIVersion is sent as X-Hexis-Version on every request so the server can
|
||||||
|
// negotiate/reject unsupported client versions.
|
||||||
|
const APIVersion = "v1"
|
||||||
|
|
||||||
|
type correlationKey struct{}
|
||||||
|
type causationKey struct{}
|
||||||
|
|
||||||
|
// WithCorrelationID returns a context that carries a correlation ID to be
|
||||||
|
// sent as X-Correlation-ID on every Hexis request made with it.
|
||||||
|
func WithCorrelationID(ctx context.Context, id string) context.Context {
|
||||||
|
return context.WithValue(ctx, correlationKey{}, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCausationID returns a context that carries a causation ID to be sent
|
||||||
|
// as X-Causation-ID on every Hexis request made with it.
|
||||||
|
func WithCausationID(ctx context.Context, id string) context.Context {
|
||||||
|
return context.WithValue(ctx, causationKey{}, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func correlationIDFrom(ctx context.Context) string {
|
||||||
|
id, _ := ctx.Value(correlationKey{}).(string)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func causationIDFrom(ctx context.Context) string {
|
||||||
|
id, _ := ctx.Value(causationKey{}).(string)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
baseURL string
|
baseURL string
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
@@ -37,6 +66,13 @@ func (c *Client) do(ctx context.Context, method, path string, body, result any)
|
|||||||
return fmt.Errorf("create request: %w", err)
|
return fmt.Errorf("create request: %w", err)
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("X-Hexis-Version", APIVersion)
|
||||||
|
if id := correlationIDFrom(ctx); id != "" {
|
||||||
|
req.Header.Set("X-Correlation-ID", id)
|
||||||
|
}
|
||||||
|
if id := causationIDFrom(ctx); id != "" {
|
||||||
|
req.Header.Set("X-Causation-ID", id)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -83,6 +119,7 @@ type Execution struct {
|
|||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
RequestedBy map[string]string `json:"requested_by,omitempty"`
|
RequestedBy map[string]string `json:"requested_by,omitempty"`
|
||||||
CorrelationID string `json:"correlation_id,omitempty"`
|
CorrelationID string `json:"correlation_id,omitempty"`
|
||||||
|
CausationID string `json:"causation_id,omitempty"`
|
||||||
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +131,7 @@ type ExecuteRequest struct {
|
|||||||
Origin map[string]string `json:"origin,omitempty"`
|
Origin map[string]string `json:"origin,omitempty"`
|
||||||
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
||||||
CorrelationID string `json:"correlation_id,omitempty"`
|
CorrelationID string `json:"correlation_id,omitempty"`
|
||||||
|
CausationID string `json:"causation_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateCapabilityRequest struct {
|
type CreateCapabilityRequest struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user