Files
go-sip/internal/store/control_receipt.go
T

80 lines
2.6 KiB
Go

package store
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"git.ipao.vip/rogee/go-sip/contracts"
)
type controlReceiptPayload struct {
TaskID string `json:"task_id,omitempty"`
Action string `json:"action,omitempty"`
Status string `json:"status"`
ReasonCode string `json:"reason_code"`
TaskState string `json:"task_state,omitempty"`
ReasonMessage string `json:"reason_message,omitempty"`
}
// State is read in the same transaction as the receipt. A missing task is
// distinguishable from a database failure; neither request data nor status
// from another tenant/Dispatcher can supply this fact.
func controlReceiptState(tx *sql.Tx, dispatcherID string, tenantID int64, taskID string) (string, error) {
var state string
err := tx.QueryRow(`SELECT CASE WHEN control_state<>'' THEN control_state ELSE status END FROM dispatcher_tasks WHERE dispatcher_id=? AND tenant_id=? AND task_id=?`, dispatcherID, tenantID, taskID).Scan(&state)
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
if err != nil {
return "", fmt.Errorf("read task control receipt state: %w", err)
}
return state, nil
}
// Validate each recoverable field against its own shared-schema definition,
// not against the whole invalid request. An invalid sibling cannot erase a
// valid task ID/action, and supplied task_state is never trusted.
func rejectedControlPayload(tx *sql.Tx, dispatcherID string, tenantID int64, raw json.RawMessage, reasonCode, reason string) (controlReceiptPayload, error) {
result := controlReceiptPayload{Status: "rejected", ReasonCode: reasonCode, ReasonMessage: reason}
schema, err := contracts.CompileCurrent("mq-task-control-receipt.schema.json")
if err != nil {
return result, err
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(raw, &fields); err != nil {
// A non-object payload has no independently parseable fields.
return result, nil
}
for _, key := range []string{"task_id", "action"} {
rawValue, present := fields[key]
if !present {
continue
}
var value any
if err := json.Unmarshal(rawValue, &value); err != nil {
continue
}
if schema.Properties["payload"].Properties[key].Validate(value) != nil {
continue
}
text, ok := value.(string)
if !ok {
return result, fmt.Errorf("shared control receipt %s schema accepted a non-string", key)
}
if key == "task_id" {
result.TaskID = text
} else {
result.Action = text
}
}
if result.TaskID != "" {
result.TaskState, err = controlReceiptState(tx, dispatcherID, tenantID, result.TaskID)
if err != nil {
return result, err
}
}
return result, nil
}