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 | 28 | return |
| 29 | 29 | } |
| 30 | 30 | var node yaml.Node |
| 31 | − if err := yaml.Unmarshal(data, &node); err != nil { | |
| 31 | + if err = yaml.Unmarshal(data, &node); err != nil { | |
| 32 | 32 | c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()}) |
| 33 | 33 | return |
| 34 | 34 | } |
| @@ -41,17 +41,18 @@ func (h *Handler) GetConfigYAML(c *gin.Context) { · 6 unmodified lines | ||
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | func WriteConfig(path string, data []byte) error { |
| 44 | + data = config.NormalizeCommentIndentation(data) | |
| 44 | 45 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) |
| 45 | 46 | if err != nil { |
| 46 | 47 | return err |
| 47 | 48 | } |
| 48 | − if _, err := f.Write(data); err != nil { | |
| 49 | − f.Close() | |
| 50 | − return err | |
| 49 | + if _, errWrite := f.Write(data); errWrite != nil { | |
| 50 | + _ = f.Close() | |
| 51 | + return errWrite | |
| 51 | 52 | } |
| 52 | − if err := f.Sync(); err != nil { | |
| 53 | − f.Close() | |
| 54 | − return err | |
| 53 | + if errSync := f.Sync(); errSync != nil { | |
| 54 | + _ = f.Close() | |
| 55 | + return errSync | |
| 55 | 56 | } |
| 56 | 57 | return f.Close() |
| 57 | 58 | } |
| @@ -63,7 +64,7 @@ func (h *Handler) PutConfigYAML(c *gin.Context) { · 5 unmodified lines | ||
| 63 | 64 | return |
| 64 | 65 | } |
| 65 | 66 | var cfg config.Config |
| 66 | − if err := yaml.Unmarshal(body, &cfg); err != nil { | |
| 67 | + if err = yaml.Unmarshal(body, &cfg); err != nil { | |
| 67 | 68 | c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()}) |
| 68 | 69 | return |
| 69 | 70 | } |
| @@ -75,18 +76,20 @@ func (h *Handler) PutConfigYAML(c *gin.Context) { · 5 unmodified lines | ||
| 75 | 76 | return |
| 76 | 77 | } |
| 77 | 78 | tempFile := tmpFile.Name() |
| 78 | − if _, err := tmpFile.Write(body); err != nil { | |
| 79 | − tmpFile.Close() | |
| 80 | − os.Remove(tempFile) | |
| 81 | − c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()}) | |
| 79 | + if _, errWrite := tmpFile.Write(body); errWrite != nil { | |
| 80 | + _ = tmpFile.Close() | |
| 81 | + _ = os.Remove(tempFile) | |
| 82 | + c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errWrite.Error()}) | |
| 82 | 83 | return |
| 83 | 84 | } |
| 84 | − if err := tmpFile.Close(); err != nil { | |
| 85 | − os.Remove(tempFile) | |
| 86 | − c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()}) | |
| 85 | + if errClose := tmpFile.Close(); errClose != nil { | |
| 86 | + _ = os.Remove(tempFile) | |
| 87 | + c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errClose.Error()}) | |
| 87 | 88 | return |
| 88 | 89 | } |
| 89 | − defer os.Remove(tempFile) | |
| 90 | + defer func() { | |
| 91 | + _ = os.Remove(tempFile) | |
| 92 | + }() | |
| 90 | 93 | _, err = config.LoadConfigOptional(tempFile, false) |
| 91 | 94 | if err != nil { |
| 92 | 95 | c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()}) |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | ||
| 5 | 5 | package config |
| 6 | 6 | |
| 7 | 7 | import ( |
| 8 | + "bytes" | |
| 8 | 9 | "errors" |
| 9 | 10 | "fmt" |
| 10 | 11 | "os" |
| @@ -462,13 +463,19 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error { · 451 unmodified lines | ||
| 462 | 463 | return err |
| 463 | 464 | } |
| 464 | 465 | defer func() { _ = f.Close() }() |
| 465 | − enc := yaml.NewEncoder(f) | |
| 466 | + var buf bytes.Buffer | |
| 467 | + enc := yaml.NewEncoder(&buf) | |
| 466 | 468 | enc.SetIndent(2) |
| 467 | 469 | if err = enc.Encode(&original); err != nil { |
| 468 | 470 | _ = enc.Close() |
| 469 | 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 | 481 | func sanitizeConfigForPersist(cfg *Config) *Config { |
| @@ -518,13 +525,40 @@ func SaveConfigPreserveCommentsUpdateNestedScalar(configFile string, path []stri · 43 unmodified lines | ||
| 518 | 525 | return err |
| 519 | 526 | } |
| 520 | 527 | defer func() { _ = f.Close() }() |
| 521 | − enc := yaml.NewEncoder(f) | |
| 528 | + var buf bytes.Buffer | |
| 529 | + enc := yaml.NewEncoder(&buf) | |
| 522 | 530 | enc.SetIndent(2) |
| 523 | 531 | if err = enc.Encode(&root); err != nil { |
| 524 | 532 | _ = enc.Close() |
| 525 | 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 | 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 | 805 | // Fallback to structural equality to preserve nodes lacking explicit identifiers. |
| 771 | 806 | for i := range original { |