Files
tf_provider/TOOLS/resource-generator/internal/helpers/helpers.go
T
“Naeel” 5f81e4a664 fix: ToSnake acronym bug, migrate compare script to Gateway, YAML regen all stands
- ToSnake: fix acronym splitting (CPU, TTL, CA, API, VIP, AVI)
- compare_yaml_vs_api.py: deck-api → lk-api-gateway
- YAML regenerated from API for all 3 stands
- Go resources regenerated
- Providers rebuilt 3.0.6/5.0.5/2.0.6
2026-08-10 09:25:53 +04:00

336 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package helpers — конвертация имён, форматирование, plan-модификаторы.
package helpers
import (
"fmt"
"go/format"
"os"
"strings"
"unicode"
"resource-generator/internal/types"
)
// ToCamel конвертирует snake_case → CamelCase.
func ToCamel(s string) string {
parts := strings.FieldsFunc(s, func(r rune) bool { return r == '_' || r == '-' })
for i, p := range parts {
if len(p) == 0 {
continue
}
parts[i] = strings.ToUpper(p[:1]) + p[1:]
}
out := strings.Join(parts, "")
return EnsureGoIdent(out)
}
// ToSnake конвертирует CamelCase → snake_case.
// Аббревиатуры (CPU, TTL, CA, API, VIP) сохраняются слитно: resourceCPU → resource_cpu.
func ToSnake(s string) string {
var out []rune
runes := []rune(s)
for i, r := range runes {
if i > 0 && r >= 'A' && r <= 'Z' {
// Вставляем _ только если предыдущий символ — строчная буква
// (переход из слова в аббревиатуру: resourceCPU → resource_cpu)
// ИЛИ если следующий символ — строчная буква
// (переход из аббревиатуры в слово: HTTPSProxy → https_proxy)
prev := runes[i-1]
if prev >= 'a' && prev <= 'z' {
out = append(out, '_')
} else if i+1 < len(runes) && runes[i+1] >= 'a' && runes[i+1] <= 'z' {
out = append(out, '_')
}
}
out = append(out, rune(strings.ToLower(string(r))[0]))
}
return string(out)
}
// ParamType возвращает Go-тип для параметра (types.Bool/Int64/String).
func ParamType(p types.Param) string {
switch strings.ToLower(p.Type) {
case "bool":
return "types.Bool"
case "int", "int64", "number":
return "types.Int64"
default:
return "types.String"
}
}
// ParamDefaultExpr возвращает выражение значения по умолчанию.
func ParamDefaultExpr(p types.Param) string {
if p.Default == "" {
return ""
}
switch strings.ToLower(p.Type) {
case "bool":
val := strings.ToLower(p.Default)
return fmt.Sprintf("booldefault.StaticBool(%s)", val)
case "int", "int64", "number":
return fmt.Sprintf("int64default.StaticInt64(%s)", p.Default)
default:
return fmt.Sprintf("stringdefault.StaticString(%q)", p.Default)
}
}
// ParamFormat возвращает вызов Format* для форматирования значения параметра.
func ParamFormat(p types.Param, varName string) string {
switch strings.ToLower(p.Type) {
case "bool":
return fmt.Sprintf("resources_core.FormatBool(%s)", varName)
case "int", "int64", "number":
return fmt.Sprintf("resources_core.FormatInt64(%s)", varName)
default:
return fmt.Sprintf("resources_core.FormatString(%s)", varName)
}
}
// ParamParse возвращает имя Parse*-функции для параметра.
func ParamParse(p types.Param) string {
switch strings.ToLower(p.Type) {
case "bool":
return "resources_core.ParseBool"
case "int", "int64", "number":
return "resources_core.ParseInt64"
default:
return "resources_core.ParseString"
}
}
// ParamDescription формирует MarkdownDescription для параметра.
func ParamDescription(p types.Param) string {
var desc string
if strings.TrimSpace(p.Descr) != "" {
desc = strings.TrimSpace(p.Descr)
} else if strings.TrimSpace(p.Man) != "" {
desc = strings.TrimSpace(p.Man)
} else if p.RefSvcId > 0 {
desc = fmt.Sprintf("UI display_name for service_id=%d (mapped to UUID in core)", p.RefSvcId)
}
if p.CreateOnly {
if desc == "" {
desc = "Не изменяется после создания."
} else {
desc = desc + " Не изменяется после создания."
}
}
if p.ForceNew {
if desc == "" {
desc = "Изменение требует пересоздания."
} else {
desc = desc + " Изменение требует пересоздания."
}
}
if desc == "" {
return ""
}
return fmt.Sprintf("%q", desc)
}
// OutputParamType возвращает Go-тип для выходного параметра (types.Map/List/String).
func OutputParamType(p types.OutputParam) string {
switch strings.ToLower(p.Type) {
case "map":
return "types.Map"
case "list":
return "types.List"
default:
return "types.String"
}
}
// OutputParamIsMap возвращает true, если выходной параметр — map.
func OutputParamIsMap(p types.OutputParam) bool {
return strings.EqualFold(strings.TrimSpace(p.Type), "map")
}
// OutputParamIsList возвращает true, если выходной параметр — list.
func OutputParamIsList(p types.OutputParam) bool {
return strings.EqualFold(strings.TrimSpace(p.Type), "list")
}
// OutputParamSensitive возвращает true, если параметр секретный.
func OutputParamSensitive(p types.OutputParam) bool {
return p.Sensitive
}
// EnsureGoIdent проверяет, что строка начинается с буквы или _.
func EnsureGoIdent(s string) string {
if s == "" {
return "R"
}
first := rune(s[0])
if unicode.IsLetter(first) || first == '_' {
return s
}
return "R" + s
}
// FormatSourceOrWarn форматирует Go-код. При ошибке пишет warning в stderr.
func FormatSourceOrWarn(filePath string, src []byte) []byte {
formatted, err := format.Source(src)
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: gofmt failed for %s: %v — writing unformatted code\n", filePath, err)
return src
}
return formatted
}
// PlanModifierType возвращает суффикс типа план-модификатора (Bool/Int64/String).
func PlanModifierType(p types.Param) string {
switch strings.ToLower(strings.TrimSpace(p.Type)) {
case "bool":
return "Bool"
case "int", "int64", "number":
return "Int64"
default:
return "String"
}
}
// PlanModifierExpr возвращает выражение план-модификатора RequiresReplace.
func PlanModifierExpr(p types.Param) string {
switch strings.ToLower(strings.TrimSpace(p.Type)) {
case "bool":
return "boolplanmodifier.RequiresReplace()"
case "int", "int64", "number":
return "int64planmodifier.RequiresReplace()"
default:
return "stringplanmodifier.RequiresReplace()"
}
}
// ─── Nested (map-fixed) helpers ──────────────────────────────────────────────
// IsNested возвращает true для параметров с SubParams (map-fixed).
// array-map-fixed исключается — требует отдельной обработки.
func IsNested(p types.Param) bool {
return p.HasSubParams && !IsNestedList(p)
}
// IsNestedList возвращает true для array-map-fixed (список объектов).
func IsNestedList(p types.Param) bool {
return p.HasSubParams && strings.Contains(strings.ToLower(p.Type), "array")
}
// NestedModelName возвращает имя вложенного Go-struct для ресурса и параметра.
// Например: NestedModelName("postgres", "cluster_configuration") → "PostgresClusterConfigurationModel"
func NestedModelName(resourceName, paramCode string) string {
return ToCamel(resourceName) + ToCamel(paramCode) + "Model"
}
// NestedTfType возвращает Go-тип для вложенного поля в Model:
// *PostgresClusterConfigurationModel (SingleNestedAttribute) или
// []PostgresClusterConfigurationModel (ListNestedAttribute — array-map-fixed).
func NestedTfType(p types.Param, resourceName string) string {
name := NestedModelName(resourceName, p.Code)
if IsNestedList(p) {
return "[]" + name
}
return "*" + name
}
// NestedSchemaType возвращает тип схемы для nested параметра:
// "SingleNested" (map-fixed) или "ListNested" (array-map-fixed).
func NestedSchemaType(p types.Param) string {
if IsNestedList(p) {
return "ListNested"
}
return "SingleNested"
}
// NestedSchemaBlock генерирует открывающую строку для nested-атрибута в Schema.
func NestedSchemaBlock(p types.Param) string {
t := NestedSchemaType(p)
switch t {
case "ListNested":
return fmt.Sprintf("schema.%sAttribute{Optional: true,\n\t\t\tNestedObject: schema.NestedAttributeObject{Attributes: map[string]schema.Attribute{", t)
default:
req := "Required: true"
if p.Default != "" {
req = "Optional: true"
}
return fmt.Sprintf("schema.%sAttribute{%s,\n\t\t\tAttributes: map[string]schema.Attribute{", t, req)
}
}
// NestedSchemaEnd возвращает закрывающую строку для nested-атрибута.
// Закрывает Attributes map и сам атрибут: `}},` для SingleNested, `}}},` для ListNested.
func NestedSchemaEnd(p types.Param) string {
t := NestedSchemaType(p)
if t == "ListNested" {
return "}}},"
}
return "}},"
}
// SubSchemaType возвращает тип схемы для подпараметра.
func SubSchemaType(p types.Param) string {
switch strings.ToLower(p.Type) {
case "bool":
return "Bool"
case "int64", "int", "number":
return "Int64"
default:
return "String"
}
}
// SubDefaultExpr возвращает выражение default для подпараметра.
// ⚠️ Только Optional+Computed (Required несовместим с Default во фреймворке).
func SubDefaultExpr(p types.Param) string {
if p.Default == "" {
return ""
}
switch strings.ToLower(p.Type) {
case "bool":
return fmt.Sprintf("booldefault.StaticBool(%s)", strings.ToLower(p.Default))
case "int64", "int", "number":
return fmt.Sprintf("int64default.StaticInt64(%s)", p.Default)
default:
return fmt.Sprintf("stringdefault.StaticString(%q)", p.Default)
}
}
// SubParamFormat возвращает JSON-ключ:значение для форматирования подпараметра.
func SubParamFormat(p types.Param, varPrefix string) string {
varName := varPrefix + "." + ToCamel(p.Code)
switch strings.ToLower(p.Type) {
case "bool":
return fmt.Sprintf(`"%s": `+varName+`.ValueBool()`, p.Code)
case "int64", "int", "number":
return fmt.Sprintf(`"%s": %s `+varName+`.ValueInt64()`, p.Code, "%s")
default:
return fmt.Sprintf(`"%s": `+varName+`.ValueString()`, p.Code)
}
}
// NestedJSONExpr возвращает выражение для сборки JSON из nested struct через resources_core.BuildJSON.
// varPrefix — префикс переменной (например "data" или "plan").
// Генерирует доступ: data.ClusterConfiguration.Cpu, data.ClusterConfiguration.Memory, ...
func NestedJSONExpr(p types.Param, varPrefix string) string {
varName := varPrefix + "." + ToCamel(p.Code)
pairs := make([]string, 0, len(p.SubParams))
for _, sp := range p.SubParams {
field := varName + "." + ToCamel(sp.Code)
var valExpr string
// IsJson проверяем до Type — для json полей Type="string", но сериализация другая.
if sp.IsJson {
valExpr = fmt.Sprintf(`%s.ValueString()`, field)
} else {
switch strings.ToLower(sp.Type) {
case "bool":
valExpr = fmt.Sprintf(`fmt.Sprintf("%%v", %s.ValueBool())`, field)
case "int64", "int", "number":
valExpr = fmt.Sprintf(`fmt.Sprintf("%%d", %s.ValueInt64())`, field)
default:
valExpr = fmt.Sprintf(`fmt.Sprintf("\"%%s\"", %s.ValueString())`, field)
}
}
pairs = append(pairs, fmt.Sprintf(`"%s": %s`, sp.Code, valExpr))
}
return fmt.Sprintf("resources_core.BuildJSON(map[string]string{%s})", strings.Join(pairs, ", "))
}