package plugin import ( "encoding/json" "fmt" ) // MergeJSONConfig merges override on top of base at the top-level // (binding fields win, base fields fill the rest). Both inputs are // expected to be JSON objects ("{}" when empty); arrays or scalars are // rejected because trigger and binding configs are always objects. // // Always returns a freshly allocated slice so callers may freely mutate // the result without affecting base. The fast-path (override empty) // returns a defensive copy of base for the same reason. func MergeJSONConfig(base, override json.RawMessage) (json.RawMessage, error) { if len(override) == 0 || string(override) == "{}" { if len(base) == 0 { return json.RawMessage("{}"), nil } return append(json.RawMessage(nil), base...), nil } if len(base) == 0 || string(base) == "{}" { return append(json.RawMessage(nil), override...), nil } baseMap := map[string]json.RawMessage{} if err := json.Unmarshal(base, &baseMap); err != nil { return nil, fmt.Errorf("merge config: base is not a JSON object: %w", err) } overMap := map[string]json.RawMessage{} if err := json.Unmarshal(override, &overMap); err != nil { return nil, fmt.Errorf("merge config: override is not a JSON object: %w", err) } for k, v := range overMap { baseMap[k] = v } out, err := json.Marshal(baseMap) if err != nil { return nil, fmt.Errorf("merge config: re-marshal: %w", err) } return out, nil } // WithEffectiveTrigger returns a copy of w with TriggerKind and // TriggerConfig set from a resolved (trigger, binding) pair. The // existing Trigger.Match contract reads w.TriggerConfig via // TriggerConfigOf[T], so this is the seam that lets the trigger plugins // stay unchanged after the trigger-split refactor. func WithEffectiveTrigger(w Workload, kind string, triggerConfig, bindingConfig json.RawMessage) (Workload, error) { merged, err := MergeJSONConfig(triggerConfig, bindingConfig) if err != nil { return Workload{}, err } w.TriggerKind = kind w.TriggerConfig = merged return w, nil }