package upstreamimpl import ( "context" "encoding/json" "fmt" "strings" "time" "github.com/gogf/gf/v2/frame/g" "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model" ) const ( defaultUpstreamHTTPTimeout = 10 * time.Second upstreamHTTPTimeoutKey = "dependencies.upstream_http.timeout" ) func templateConfigJSON(channelConfig *model.SimChannelConfigItemCache) string { if channelConfig == nil || channelConfig.Template == nil { return "" } return channelConfig.Template.TempConfig } // parseUpstreamDuration accepts Go duration strings and positive numeric // seconds so template and application configuration use the same contract. func parseUpstreamDuration(raw json.RawMessage, field string) (time.Duration, error) { if len(raw) == 0 || string(raw) == "null" { return 0, fmt.Errorf("%s is empty", field) } var value any if err := json.Unmarshal(raw, &value); err != nil { return 0, fmt.Errorf("invalid %s: %w", field, err) } switch v := value.(type) { case float64: if v <= 0 || v != float64(int64(v)) { return 0, fmt.Errorf("invalid %s: expected positive seconds", field) } return time.Duration(v) * time.Second, nil case string: value := strings.TrimSpace(v) if value == "" { return 0, fmt.Errorf("invalid %s: empty value", field) } duration, err := time.ParseDuration(value) if err != nil || duration <= 0 { return 0, fmt.Errorf("invalid %s: expected positive duration", field) } return duration, nil default: return 0, fmt.Errorf("invalid %s: expected duration or seconds", field) } } func parseUpstreamConfigDuration(value any, field string) (time.Duration, error) { raw, err := json.Marshal(value) if err != nil { return 0, fmt.Errorf("encode %s: %w", field, err) } return parseUpstreamDuration(raw, field) } func loadDefaultUpstreamHTTPTimeout(ctx context.Context) time.Duration { value := g.Cfg().MustGet(ctx, upstreamHTTPTimeoutKey, defaultUpstreamHTTPTimeout.String()).Val() timeout, err := parseUpstreamConfigDuration(value, upstreamHTTPTimeoutKey) if err != nil { g.Log().Warningf(ctx, "%v, fallback to %s", err, defaultUpstreamHTTPTimeout) return defaultUpstreamHTTPTimeout } return timeout } // resolveUpstreamHTTPTimeout applies the single timeout override for a // template. Missing or invalid template values fall back to application // configuration and finally the code default. func resolveUpstreamHTTPTimeout(ctx context.Context, tempConfig string) time.Duration { defaultTimeout := loadDefaultUpstreamHTTPTimeout(ctx) if strings.TrimSpace(tempConfig) == "" { return defaultTimeout } var raw struct { HTTP struct { Timeout json.RawMessage `json:"timeout"` } `json:"http"` } if err := json.Unmarshal([]byte(tempConfig), &raw); err != nil { g.Log().Warningf(ctx, "parse upstream template config failed: %v, fallback to %s", err, defaultTimeout) return defaultTimeout } if len(raw.HTTP.Timeout) == 0 || string(raw.HTTP.Timeout) == "null" { return defaultTimeout } timeout, err := parseUpstreamDuration(raw.HTTP.Timeout, "http.timeout") if err != nil { g.Log().Warningf(ctx, "%v, fallback to %s", err, defaultTimeout) return defaultTimeout } return timeout }