// Package params — слияние, фильтрация, анализ параметров и ForceNew. // // Основные операции: // - Merge — объединяет параметры из нескольких операций по Code // - ComputeCreateOnly — поля, которые есть в create, но отсутствуют в modify // - BuildSubresourceForceNewCodes — поля, требующие пересоздания subresource // - Analyze — определяет используемые типы (bool/int64/string) для импортов // - AnalyzePlanModifiers — ForceNew-поля, требующие planmodifier.*.RequiresReplace() package params import ( "sort" "strings" "resource-generator/internal/types" ) // Merge объединяет несколько наборов параметров, убирая дубликаты по Code. // При совпадении Code SubParams объединяются union'ом (новые поля из modify добавляются к create). func Merge(sets ...[]types.Param) []types.Param { seen := map[string]types.Param{} for _, s := range sets { for _, p := range s { key := strings.ToLower(strings.TrimSpace(p.Code)) if key == "" { continue } if existing, ok := seen[key]; !ok { seen[key] = p } else if p.HasSubParams && len(p.SubParams) > 0 { merged := unionSubParams(existing.SubParams, p.SubParams) existing.SubParams = merged existing.HasSubParams = len(merged) > 0 seen[key] = existing } } } out := make([]types.Param, 0, len(seen)) for _, p := range seen { out = append(out, p) } sort.Slice(out, func(i, j int) bool { return out[i].Code < out[j].Code }) return out } // unionSubParams добавляет sub-params из extra, которых ещё нет в base (сравнение по Code). func unionSubParams(base, extra []types.Param) []types.Param { inBase := make(map[string]struct{}, len(base)) for _, p := range base { inBase[strings.ToLower(strings.TrimSpace(p.Code))] = struct{}{} } out := append([]types.Param(nil), base...) for _, p := range extra { if _, seen := inBase[strings.ToLower(strings.TrimSpace(p.Code))]; !seen { out = append(out, p) } } sort.Slice(out, func(i, j int) bool { return out[i].Code < out[j].Code }) return out } // AlignParamTypes выравнивает типы параметров по схеме. func AlignParamTypes(params []types.Param, schema []types.Param) []types.Param { if len(params) == 0 || len(schema) == 0 { return params } codeType := make(map[string]string, len(schema)) for _, p := range schema { key := strings.ToLower(strings.TrimSpace(p.Code)) if key == "" { continue } if strings.TrimSpace(p.Type) != "" { codeType[key] = p.Type } } if len(codeType) == 0 { return params } for i, p := range params { key := strings.ToLower(strings.TrimSpace(p.Code)) if key == "" { continue } if t, ok := codeType[key]; ok && strings.TrimSpace(t) != "" { params[i].Type = t } } // Рекурсивно выравниваем SubParams (например cpu может быть string в modify, но int64 в create). schemaByCode := make(map[string]types.Param, len(schema)) for _, p := range schema { schemaByCode[strings.ToLower(strings.TrimSpace(p.Code))] = p } for i, p := range params { if !p.HasSubParams { continue } key := strings.ToLower(strings.TrimSpace(p.Code)) if sp, ok := schemaByCode[key]; ok && len(sp.SubParams) > 0 { params[i].SubParams = AlignParamTypes(p.SubParams, sp.SubParams) } } return params } // ComputeCreateOnly вычисляет набор полей, которые есть только в create (не в modify). func ComputeCreateOnly(createParams []types.Param, modifyParams []types.Param) map[string]struct{} { modifyCodes := make(map[string]struct{}, len(modifyParams)) for _, p := range modifyParams { key := strings.ToLower(strings.TrimSpace(p.Code)) if key == "" { continue } modifyCodes[key] = struct{}{} } createOnly := make(map[string]struct{}) for _, p := range createParams { key := strings.ToLower(strings.TrimSpace(p.Code)) if key == "" { continue } if _, ok := modifyCodes[key]; !ok { createOnly[key] = struct{}{} } } return createOnly } // MarkCreateOnly проставляет CreateOnly=true для полей, которых нет в modify. func MarkCreateOnly(schema []types.Param, createOnly map[string]struct{}) []types.Param { if len(schema) == 0 || len(createOnly) == 0 { return schema } for i, p := range schema { key := strings.ToLower(strings.TrimSpace(p.Code)) if key == "" { continue } if _, ok := createOnly[key]; ok { schema[i].CreateOnly = true } } return schema } // FilterCreateOnly возвращает только CreateOnly-параметры. func FilterCreateOnly(schema []types.Param) []types.Param { if len(schema) == 0 { return []types.Param{} } result := make([]types.Param, 0, len(schema)) for _, p := range schema { if p.CreateOnly { result = append(result, p) } } return result } // FilterCreateOnlyRequired возвращает обязательные CreateOnly-параметры. func FilterCreateOnlyRequired(schema []types.Param) []types.Param { if len(schema) == 0 { return []types.Param{} } result := make([]types.Param, 0, len(schema)) for _, p := range schema { if p.CreateOnly && p.Required { result = append(result, p) } } return result } // Analyze анализирует типы параметров и проставляет флаги uses/needs. func Analyze(usesBool *bool, usesInt64 *bool, usesString *bool, hasDefaults *bool, needsBoolDefault *bool, needsInt64Default *bool, needsStringDefault *bool, params []types.Param) { for _, p := range params { switch strings.ToLower(p.Type) { case "bool": *usesBool = true if p.Default != "" { *needsBoolDefault = true *hasDefaults = true } case "int", "int64", "number": *usesInt64 = true if p.Default != "" { *needsInt64Default = true *hasDefaults = true } default: *usesString = true if p.Default != "" { *needsStringDefault = true *hasDefaults = true } } } } // BuildSubresourceForceNewCodes строит карту параметров, требующих ForceNew. func BuildSubresourceForceNewCodes(sr *types.GenSubresource, createOnly map[string]struct{}) map[string]struct{} { forceNew := map[string]struct{}{} if sr.ModifyOpName == "" { for _, p := range sr.SchemaParams { key := strings.ToLower(strings.TrimSpace(p.Code)) if key != "" { forceNew[key] = struct{}{} } } return forceNew } for key := range createOnly { forceNew[key] = struct{}{} } for _, p := range sr.DeleteParams { key := strings.ToLower(strings.TrimSpace(p.Code)) if key != "" { forceNew[key] = struct{}{} } } return forceNew } // MarkForceNew проставляет ForceNew=true для указанных параметров. func MarkForceNew(params []types.Param, forceNew map[string]struct{}) []types.Param { if len(params) == 0 || len(forceNew) == 0 { return params } for i, p := range params { key := strings.ToLower(strings.TrimSpace(p.Code)) if key == "" { continue } if _, ok := forceNew[key]; ok { params[i].ForceNew = true } } return params } // FilterForceNew возвращает только ForceNew-параметры. func FilterForceNew(params []types.Param) []types.Param { result := []types.Param{} for _, p := range params { if p.ForceNew { result = append(result, p) } } return result } // AnalyzeJsonPlanMod возвращает true, если хотя бы один параметр — JSON. func AnalyzeJsonPlanMod(params []types.Param) bool { for _, p := range params { if p.IsJson { return true } } return false } // HasNestedParams возвращает true, если есть nested (только map-fixed, не array-map-fixed) параметры. func HasNestedParams(params []types.Param) bool { for _, p := range params { if p.HasSubParams && !strings.Contains(strings.ToLower(p.Type), "array") { return true } } return false } // AnalyzeNestedDefaults проверяет sub-params nested-параметров на наличие default'ов. // Возвращает true если найдены nested параметры. func AnalyzeNestedDefaults(needsBoolDefault *bool, needsInt64Default *bool, needsStringDefault *bool, params []types.Param) bool { found := false for _, p := range params { if len(p.SubParams) == 0 { continue } found = true for _, sp := range p.SubParams { if sp.Default == "" { continue } switch strings.ToLower(sp.Type) { case "bool": *needsBoolDefault = true case "int64", "int", "number": *needsInt64Default = true default: *needsStringDefault = true } } } return found } // HasRestoreCasingParams возвращает true, если есть ref_svc-параметры (кроме S3). func HasRestoreCasingParams(params []types.Param) bool { for _, p := range params { if p.RefSvcId > 0 && p.RefSvcId != 12 && strings.EqualFold(strings.TrimSpace(p.Type), "string") { return true } } return false } // AnalyzeNeedsStrings возвращает true, если есть строковые параметры. func AnalyzeNeedsStrings(params []types.Param) bool { for _, p := range params { // Пропускаем IsNested (map-fixed без array): они не участвуют в strings.EqualFold if p.HasSubParams && !strings.Contains(strings.ToLower(p.Type), "array") { continue } t := strings.ToLower(strings.TrimSpace(p.Type)) if t != "bool" && t != "int" && t != "int64" && t != "number" { return true } } return false } // AnalyzePlanModifiers анализирует ForceNew-параметры для добавления planmodifier-импортов. func AnalyzePlanModifiers(needsBool *bool, needsInt64 *bool, needsString *bool, params []types.Param) { for _, p := range params { if !p.ForceNew { continue } switch strings.ToLower(strings.TrimSpace(p.Type)) { case "bool": *needsBool = true case "int", "int64", "number": *needsInt64 = true default: *needsString = true } } }