diff --git a/TOOLS/resource-generator/internal/loader/loader.go b/TOOLS/resource-generator/internal/loader/loader.go index 7cb9b17..33f66c9 100644 --- a/TOOLS/resource-generator/internal/loader/loader.go +++ b/TOOLS/resource-generator/internal/loader/loader.go @@ -22,6 +22,7 @@ import ( "resource-generator/internal/params" "resource-generator/internal/types" + "tf-tools/lib" ) // LoadSpecs загружает все YAML-спеки из директории и строит модели всех ресурсов. @@ -63,12 +64,15 @@ func LoadSpecs(dir string) ([]types.GenResource, []types.GenSubresource, []types name = strings.TrimSpace(op.Action) } modifier := types.GenModifier{ - ServiceName: spec.Name, - ServiceID: spec.ServiceID, - ModifierName: name, - OperationName: op.Action, - Params: ConvertParams(op.Params), + ServiceName: spec.Name, + ServiceID: spec.ServiceID, + ModifierName: name, + OperationName: op.Action, + Params: ConvertParams(op.Params), + DeleteStrategy: normalizeDeleteStrategy(op.DeleteStrategy), + Idempotency: normalizeIdempotency(op.Idempotency), } + modifier.DeleteParams = convertDeleteParams(op.DeleteParams) modifier.SchemaParams = modifier.Params for idx := range modifier.SchemaParams { if modifier.SchemaParams[idx].HasSubParams { @@ -375,6 +379,76 @@ func ValidateSpec(path string, spec *types.ServiceSpec) error { if op.Kind == "modifier" && strings.TrimSpace(op.Action) == "" { return fmt.Errorf("operation[%d] %q (kind=modifier): missing required field: action", i, op.Name) } + if op.Kind == "modifier" { + if err := validateModifierOperation(i, &op); err != nil { + return err + } + } + } + return nil +} + +// normalizeDeleteStrategy возвращает каноническое значение delete_strategy. +// Пусто → noop_warn; неизвестное — как есть (валидация в ValidateSpec отклонит раньше). +func normalizeDeleteStrategy(raw string) string { + v := strings.ToLower(strings.TrimSpace(raw)) + if v == "" { + return "noop_warn" + } + return v +} + +// normalizeIdempotency возвращает каноническое значение idempotency. +// Пусто → none. +func normalizeIdempotency(raw string) string { + v := strings.ToLower(strings.TrimSpace(raw)) + if v == "" { + return "none" + } + return v +} + +// convertDeleteParams переносит lib.DeleteParam → types.DeleteParam (wire-строки). +func convertDeleteParams(raw []lib.DeleteParam) []types.DeleteParam { + if len(raw) == 0 { + return nil + } + out := make([]types.DeleteParam, 0, len(raw)) + for _, p := range raw { + out = append(out, types.DeleteParam{Code: strings.TrimSpace(p.Code), Value: p.Value}) + } + return out +} + +// validateModifierOperation — fail-fast для полей modifier-операции. +func validateModifierOperation(i int, op *lib.OperationSpec) error { + ds := normalizeDeleteStrategy(op.DeleteStrategy) + switch ds { + case "noop_warn", "inverse", "error": + default: + return fmt.Errorf("operation[%d] %q (kind=modifier): unknown delete_strategy %q (valid: noop_warn, inverse, error)", i, op.Name, op.DeleteStrategy) + } + + idem := normalizeIdempotency(op.Idempotency) + switch idem { + case "none", "check_before_run": + default: + return fmt.Errorf("operation[%d] %q (kind=modifier): unknown idempotency %q (valid: none, check_before_run)", i, op.Name, op.Idempotency) + } + + if ds == "inverse" && len(op.DeleteParams) == 0 { + return fmt.Errorf("operation[%d] %q (kind=modifier): delete_strategy=inverse требует delete_params", i, op.Name) + } + + // каждый delete_params.code обязан существовать среди params (по lower-code) + paramCodes := make(map[string]struct{}, len(op.Params)) + for _, p := range op.Params { + paramCodes[strings.ToLower(strings.TrimSpace(p.Code))] = struct{}{} + } + for _, dp := range op.DeleteParams { + if _, ok := paramCodes[strings.ToLower(strings.TrimSpace(dp.Code))]; !ok { + return fmt.Errorf("operation[%d] %q (kind=modifier): delete_params.code %q отсутствует в params", i, op.Name, dp.Code) + } } return nil } diff --git a/TOOLS/resource-generator/internal/types/types.go b/TOOLS/resource-generator/internal/types/types.go index b422abc..dd6d8d0 100644 --- a/TOOLS/resource-generator/internal/types/types.go +++ b/TOOLS/resource-generator/internal/types/types.go @@ -158,6 +158,12 @@ type GenModifier struct { OperationName string Params []Param SchemaParams []Param + // DeleteStrategy — noop_warn | inverse | error (нормализовано из YAML, пусто → noop_warn). + DeleteStrategy string + // Idempotency — none | check_before_run (нормализовано из YAML, пусто → none). + Idempotency string + // DeleteParams — обратные значения (wire-строки), только при DeleteStrategy==inverse. + DeleteParams []DeleteParam UsesBool bool UsesInt64 bool UsesString bool @@ -167,3 +173,9 @@ type GenModifier struct { NeedsStringDefault bool NeedsJsonPlanMod bool } + +// DeleteParam — обратное значение параметра inverse-Delete (code → wire-строка). +type DeleteParam struct { + Code string + Value string +}