Enhance error management for file operations and clean up temporary files. Add `NormalizeCommentIndentation` function to ensure YAML comments maintain consistent formatting.
Unified · Split
| @@ -28,7 +28,7 @@ func (h *Handler) GetConfigYAML(c *gin.Context) { · 27 unmodified lines | |||
| 28 | return | 28 | return |
| 29 | } | 29 | } |
| 30 | var node yaml.Node | 30 | var node yaml.Node |
| 31 | if err := yaml.Unmarshal(data, &node); err != nil { | 31 | if err = yaml.Unmarshal(data, &node); err != nil { |
| 32 | c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()}) | 32 | c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()}) |
| 33 | return | 33 | return |
| 34 | } | 34 | } |
| @@ -41,17 +41,18 @@ func (h *Handler) GetConfigYAML(c *gin.Context) { · 6 unmodified lines | |||
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | func WriteConfig(path string, data []byte) error { | 43 | func WriteConfig(path string, data []byte) error { |
| 44 | data = config.NormalizeCommentIndentation(data) | ||
| 44 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) | 45 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) |
| 45 | if err != nil { | 46 | if err != nil { |
| 46 | return err | 47 | return err |
| 47 | } | 48 | } |
| 48 | if _, err := f.Write(data); err != nil { | 49 | if _, errWrite := f.Write(data); errWrite != nil { |
| 49 | f.Close() | 50 | _ = f.Close() |
| 50 | return err | 51 | return errWrite |
| 51 | } | 52 | } |
| 52 | if err := f.Sync(); err != nil { | 53 | if errSync := f.Sync(); errSync != nil { |
| 53 | f.Close() | 54 | _ = f.Close() |
| 54 | return err | 55 | return errSync |
| 55 | } | 56 | } |
| 56 | return f.Close() | 57 | return f.Close() |
| 57 | } | 58 | } |
| @@ -63,7 +64,7 @@ func (h *Handler) PutConfigYAML(c *gin.Context) { · 5 unmodified lines | |||
| 63 | return | 64 | return |
| 64 | } | 65 | } |
| 65 | var cfg config.Config | 66 | var cfg config.Config |
| 66 | if err := yaml.Unmarshal(body, &cfg); err != nil { | 67 | if err = yaml.Unmarshal(body, &cfg); err != nil { |
| 67 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()}) | 68 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()}) |
| 68 | return | 69 | return |
| 69 | } | 70 | } |
| @@ -75,18 +76,20 @@ func (h *Handler) PutConfigYAML(c *gin.Context) { · 5 unmodified lines | |||
| 75 | return | 76 | return |
| 76 | } | 77 | } |
| 77 | tempFile := tmpFile.Name() | 78 | tempFile := tmpFile.Name() |
| 78 | if _, err := tmpFile.Write(body); err != nil { | 79 | if _, errWrite := tmpFile.Write(body); errWrite != nil { |
| 79 | tmpFile.Close() | 80 | _ = tmpFile.Close() |
| 80 | os.Remove(tempFile) | 81 | _ = os.Remove(tempFile) |
| 81 | c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()}) | 82 | c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errWrite.Error()}) |
| 82 | return | 83 | return |
| 83 | } | 84 | } |
| 84 | if err := tmpFile.Close(); err != nil { | 85 | if errClose := tmpFile.Close(); errClose != nil { |
| 85 | os.Remove(tempFile) | 86 | _ = os.Remove(tempFile) |
| 86 | c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()}) | 87 | c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errClose.Error()}) |
| 87 | return | 88 | return |
| 88 | } | 89 | } |
| 89 | defer os.Remove(tempFile) | 90 | defer func() { |
| 91 | _ = os.Remove(tempFile) | ||
| 92 | }() | ||
| 90 | _, err = config.LoadConfigOptional(tempFile, false) | 93 | _, err = config.LoadConfigOptional(tempFile, false) |
| 91 | if err != nil { | 94 | if err != nil { |
| 92 | c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()}) | 95 | c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()}) |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | package config | 5 | package config |
| 6 | 6 | ||
| 7 | import ( | 7 | import ( |
| 8 | "bytes" | ||
| 8 | "errors" | 9 | "errors" |
| 9 | "fmt" | 10 | "fmt" |
| 10 | "os" | 11 | "os" |
| @@ -462,13 +463,19 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error { · 451 unmodified lines | |||
| 462 | return err | 463 | return err |
| 463 | } | 464 | } |
| 464 | defer func() { _ = f.Close() }() | 465 | defer func() { _ = f.Close() }() |
| 465 | enc := yaml.NewEncoder(f) | 466 | var buf bytes.Buffer |
| 467 | enc := yaml.NewEncoder(&buf) | ||
| 466 | enc.SetIndent(2) | 468 | enc.SetIndent(2) |
| 467 | if err = enc.Encode(&original); err != nil { | 469 | if err = enc.Encode(&original); err != nil { |
| 468 | _ = enc.Close() | 470 | _ = enc.Close() |
| 469 | return err | 471 | return err |
| 470 | } | 472 | } |
| 471 | return enc.Close() | 473 | if err = enc.Close(); err != nil { |
| 474 | return err | ||
| 475 | } | ||
| 476 | data = NormalizeCommentIndentation(buf.Bytes()) | ||
| 477 | _, err = f.Write(data) | ||
| 478 | return err | ||
| 472 | } | 479 | } |
| 473 | 480 | ||
| 474 | func sanitizeConfigForPersist(cfg *Config) *Config { | 481 | func sanitizeConfigForPersist(cfg *Config) *Config { |
| @@ -518,13 +525,40 @@ func SaveConfigPreserveCommentsUpdateNestedScalar(configFile string, path []stri · 43 unmodified lines | |||
| 518 | return err | 525 | return err |
| 519 | } | 526 | } |
| 520 | defer func() { _ = f.Close() }() | 527 | defer func() { _ = f.Close() }() |
| 521 | enc := yaml.NewEncoder(f) | 528 | var buf bytes.Buffer |
| 529 | enc := yaml.NewEncoder(&buf) | ||
| 522 | enc.SetIndent(2) | 530 | enc.SetIndent(2) |
| 523 | if err = enc.Encode(&root); err != nil { | 531 | if err = enc.Encode(&root); err != nil { |
| 524 | _ = enc.Close() | 532 | _ = enc.Close() |
| 525 | return err | 533 | return err |
| 526 | } | 534 | } |
| 527 | return enc.Close() | 535 | if err = enc.Close(); err != nil { |
| 536 | return err | ||
| 537 | } | ||
| 538 | data = NormalizeCommentIndentation(buf.Bytes()) | ||
| 539 | _, err = f.Write(data) | ||
| 540 | return err | ||
| 541 | } | ||
| 542 | |||
| 543 | // NormalizeCommentIndentation removes indentation from standalone YAML comment lines to keep them left aligned. | ||
| 544 | func NormalizeCommentIndentation(data []byte) []byte { | ||
| 545 | lines := bytes.Split(data, []byte("\n")) | ||
| 546 | changed := false | ||
| 547 | for i, line := range lines { | ||
| 548 | trimmed := bytes.TrimLeft(line, " \t") | ||
| 549 | if len(trimmed) == 0 || trimmed[0] != '#' { | ||
| 550 | continue | ||
| 551 | } | ||
| 552 | if len(trimmed) == len(line) { | ||
| 553 | continue | ||
| 554 | } | ||
| 555 | lines[i] = append([]byte(nil), trimmed...) | ||
| 556 | changed = true | ||
| 557 | } | ||
| 558 | if !changed { | ||
| 559 | return data | ||
| 560 | } | ||
| 561 | return bytes.Join(lines, []byte("\n")) | ||
| 528 | } | 562 | } |
| 529 | 563 | ||
| 530 | // getOrCreateMapValue finds the value node for a given key in a mapping node. | 564 | // getOrCreateMapValue finds the value node for a given key in a mapping node. |
| @@ -766,6 +800,7 @@ func matchSequenceElement(original []*yaml.Node, used []bool, target *yaml.Node) · 235 unmodified lines | |||
| 766 | } | 800 | } |
| 767 | } | 801 | } |
| 768 | } | 802 | } |
| 803 | default: | ||
| 769 | } | 804 | } |
| 770 | // Fallback to structural equality to preserve nodes lacking explicit identifiers. | 805 | // Fallback to structural equality to preserve nodes lacking explicit identifiers. |
| 771 | for i := range original { | 806 | for i := range original { |