Skip to content

fix(parser): write numeric/bool values unquoted for newly created config files#194

Open
thenull99 wants to merge 3 commits into
pelican-dev:mainfrom
HyperGaming99:fix/yaml-numeric-quoting-on-create
Open

fix(parser): write numeric/bool values unquoted for newly created config files#194
thenull99 wants to merge 3 commits into
pelican-dev:mainfrom
HyperGaming99:fix/yaml-numeric-quoting-on-create

Conversation

@thenull99

@thenull99 thenull99 commented Jun 28, 2026

Copy link
Copy Markdown

Problem

When a server is created for the first time, its configuration file is often generated from scratch, so the target key does not exist yet. setValueWithSjson (parser/helpers.go) previously inferred a value's type solely by mirroring the type already present in the document. A freshly created key therefore has no existing type to mirror, and every value falls back to a quoted string.

As a result numeric values such as ports are written quoted:

server-port: "25565"   # before
server-port: 25565     # expected

The same applies to booleans and to JSON/TOML output. This breaks games/servers that require a real number (or boolean) for those keys.

Fix

In setValueWithSjson:

  • New key (!existing.Exists()) — infer the natural type from the value itself, writing clean JSON numbers and booleans unquoted (SetRaw for numbers to preserve large-integer precision).
  • Explicit numeric type from the egg is now honoured, mirroring the existing handling of an explicit boolean type (previously only booleans were honoured).
  • Strict canonical JSON-number check (isJSONNumber) replaces the lax gjson.Parse check. gjson.Parse treats values like 007, +5, 1.20.1 and 25565x as numbers, which would coerce them incorrectly and could even produce invalid JSON via SetRaw. The new regex matches the JSON number grammar exactly, so those stay strings.

Existing string-typed keys are intentionally left untouched, so values like a numeric password in an existing template are not coerced to numbers — this keeps the change low-risk and avoids regressions.

Verification

go build and go vet pass. Verified behaviour (new-key and existing-key paths):

Input ({} unless noted) Result
new server-port = "25565" 25565 ✅ (the reported bug)
new online-mode = "true" true
new motd = "Welcome" "Welcome"
new id = "007" "007" ✅ (leading zero preserved)
new key = "25565x" "25565x"
new owner = "1234567890123456789" 1234567890123456789 ✅ (precise)
existing {"pass":"old"}"12345" "12345" ✅ (no regression)
existing {"port":1}"25565" 25565
existing {"flag":false}"true" true
explicit egg Number 100 100

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of numeric configuration replacements with strict validation of canonical JSON number formats, including exponent notation.
    • Boolean replacements now parse more reliably; when values aren’t valid booleans, they fall back safely to preserve the original string.
    • Numeric detection is tightened for both mirrored existing numeric fields and newly created keys, reducing cases where numbers were written with incorrect quoting.

…fig files

When a server is first created its configuration file is often generated
from scratch, so the target key does not exist yet. setValueWithSjson
previously inferred a value's type solely by mirroring the type already
present in the document, which meant a freshly created key had no type to
mirror and every value fell back to a quoted string. Numeric values such
as ports were therefore written as `server-port: "25565"` instead of
`server-port: 25565` in YAML (and quoted in JSON/TOML).

Changes:
- Infer the natural type from the value itself when the key does not exist
  yet, writing clean JSON numbers and booleans unquoted.
- Honour an explicitly declared numeric type from the egg definition
  (previously only an explicit boolean type was honoured).
- Replace the lax gjson.Parse number check with a strict canonical
  JSON-number match so values like "007", "+5", "1.20.1" or "25565x" stay
  strings instead of being coerced (which could also have produced invalid
  JSON via SetRaw).

Existing string-typed keys are intentionally left untouched, so values
like numeric passwords in an existing template are not coerced to numbers.
@thenull99
thenull99 requested a review from a team as a code owner June 28, 2026 08:29
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rferfgvrtghrtfg, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 4 minutes and 18 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 79f4282c-f6b2-4f18-9633-1698334eb360

📥 Commits

Reviewing files that changed from the base of the PR and between 8438c6d and 4921827.

📒 Files selected for processing (1)
  • parser/helpers.go
📝 Walkthrough

Walkthrough

parser/helpers.go adds canonical JSON number validation and refactors setValueWithSjson to use switch-based handling for explicit numeric and boolean replacement types, with boolean parsing centralized in a new helper.

Changes

JSON Literal Handling

Layer / File(s) Summary
Number and boolean literal handling
parser/helpers.go
Adds jsonNumberRegex and isJSONNumber, refactors setValueWithSjson to use isJSONNumber for numeric raw writes, and adds setBool for parsed boolean writes with raw-string fallback.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hopped through numbers so neat,
And booleans parsed with a tidy beat.
No stray zeros, no fuzzy disguise,
Just JSON that sparkles before bunny eyes.
🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: writing numeric and boolean config values unquoted for newly created files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
parser/helpers.go (2)

173-216: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated boolean parse/fallback logic.

The explicit-boolean case (Lines 176-181) and the mirror-existing-boolean case (Lines 193-198) are identical. Consider extracting a small helper to keep them in sync.

♻️ Sketch
func (cfr *ConfigurationFileReplacement) boolOrStringFallback(jsonStr, path, value string) (any, *string, error) {
	v, err := strconv.ParseBool(value)
	if err != nil {
		log.WithFields(log.Fields{"value": value, "path": path, "match": cfr.Match}).
			Warn("cannot parse replacement as boolean, falling back to string value")
		s, e := sjson.Set(jsonStr, path, value)
		return nil, &s, e
	}
	return v, nil, nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@parser/helpers.go` around lines 173 - 216, The explicit boolean branch and
the existing-boolean mirror branch in the replacement logic duplicate the same
parse-and-fallback behavior. Extract that shared
`strconv.ParseBool`/warning/`sjson.Set` flow into a small helper on
`ConfigurationFileReplacement` (or a local helper in `parser/helpers.go`) and
use it from both the explicit `cfr.ReplaceWith.Type()` boolean case and the
`existing.Type == gjson.True || gjson.False` case so the behavior stays
identical in one place.

182-185: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Number path silently degrades when value isn't canonical.

When the egg declares Number but value fails isJSONNumber (e.g. 25565x, 007), this case is skipped and the value silently falls through to the mirror/default branch and is written as a string. The analogous explicit-Boolean path (Lines 177-179) logs a warning on bad input. Consider adding a matching warning here so misconfigured eggs are diagnosable.

♻️ Possible adjustment
-	case cfr.ReplaceWith.Type() == jsonparser.Number && isJSONNumber(value):
-		// Explicit numeric type declared in the egg definition. Write the literal
-		// as-is via SetRaw to avoid float64 precision loss for large integers (> 2^53).
-		return sjson.SetRaw(jsonStr, path, value)
+	case cfr.ReplaceWith.Type() == jsonparser.Number:
+		// Explicit numeric type declared in the egg definition. Write the literal
+		// as-is via SetRaw to avoid float64 precision loss for large integers (> 2^53).
+		if isJSONNumber(value) {
+			return sjson.SetRaw(jsonStr, path, value)
+		}
+		log.WithFields(log.Fields{"value": value, "path": path, "match": cfr.Match}).
+			Warn("cannot parse replacement as number, falling back to string value")
+		return sjson.Set(jsonStr, path, value)

Note: this also changes the non-numeric fallback from "mirror existing type" to "string"; confirm that matches intended behavior before adopting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@parser/helpers.go` around lines 182 - 185, The explicit Number branch in
parser/helpers.go silently falls through to the default string/mirror handling
when isJSONNumber(value) fails, which hides misconfigured egg values. Update the
case around cfr.ReplaceWith.Type() and isJSONNumber to emit a warning similar to
the Boolean path before falling back, and review the fallback behavior in the
default branch to ensure Number-typed values are handled intentionally rather
than being silently written as strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@parser/helpers.go`:
- Around line 173-216: The explicit boolean branch and the existing-boolean
mirror branch in the replacement logic duplicate the same parse-and-fallback
behavior. Extract that shared `strconv.ParseBool`/warning/`sjson.Set` flow into
a small helper on `ConfigurationFileReplacement` (or a local helper in
`parser/helpers.go`) and use it from both the explicit `cfr.ReplaceWith.Type()`
boolean case and the `existing.Type == gjson.True || gjson.False` case so the
behavior stays identical in one place.
- Around line 182-185: The explicit Number branch in parser/helpers.go silently
falls through to the default string/mirror handling when isJSONNumber(value)
fails, which hides misconfigured egg values. Update the case around
cfr.ReplaceWith.Type() and isJSONNumber to emit a warning similar to the Boolean
path before falling back, and review the fallback behavior in the default branch
to ensure Number-typed values are handled intentionally rather than being
silently written as strings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b6e355c5-d720-4362-9c3e-fb2d0b35dae0

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2070e and 20b4c99.

📒 Files selected for processing (1)
  • parser/helpers.go
📜 Review details
🔇 Additional comments (1)
parser/helpers.go (1)

38-51: LGTM!

The explicit-boolean and mirror-existing-boolean cases in setValueWithSjson
contained identical parse/log/fallback logic. Extract a setBool helper so the
two paths stay in sync.
@thenull99
thenull99 force-pushed the fix/yaml-numeric-quoting-on-create branch from 8438c6d to 081e75e Compare June 28, 2026 08:45
The explicit-Number path previously matched on `type == Number && isJSONNumber`,
so when an egg declared a numeric type but the expanded value was not a canonical
JSON number (e.g. "25565x" or "007"), the case was skipped and the value silently
fell through to the mirror/default branch and was written as a string.

Extract a setNumber helper that, like setBool, logs a warning and falls back to
writing the raw string when the value is not a canonical JSON number, so a
misconfigured egg is diagnosable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants