80 lines
2.6 KiB
Go
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
|
|
}
|