# How Schema Validation Works for guidelines.json in JetBrains/go-modern-guidelines

> Understand how schema validation works for guidelines.json in JetBrains/go-modern-guidelines. Discover the 13-step validation pipeline that ensures JSON syntax, ID uniqueness, Go version ordering, and example completeness.

- Repository: [JetBrains/go-modern-guidelines](https://github.com/jetbrains/go-modern-guidelines)
- Tags: internals
- Published: 2026-09-04

---

**The `schema.Parse` function performs a single-pass, deterministic 13-step validation pipeline that checks everything from JSON syntax and ID uniqueness to Go version ordering and example snippet completeness, failing fast with descriptive errors on any violation.**

The JetBrains/go-modern-guidelines repository maintains its collection of modern Go best practices in a structured JSON file. To ensure data integrity before the tool consumes these rules, the codebase implements rigorous schema validation in [`internal/guidelines/schema/schema.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/schema/schema.go) that checks semantic constraints beyond simple type matching.

## The Entry Point: schema.Parse

When the tool initializes, the loader in [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go) reads [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) and passes the raw bytes to `schema.Parse`. This function acts as a strict gatekeeper: it either returns a fully-validated slice of guidelines or an error that pinpoints exactly which constraint failed.

The validation is deterministic and exhaustive, iterating through the collection once while maintaining a `seenIDs` map to detect duplicates. According to the source code, if any check fails, the function immediately returns a wrapped error that aborts program startup, ensuring only valid data reaches the analysis engine.

## The 13-Step Validation Pipeline

The `Parse` function enforces semantic correctness through thirteen distinct checks, organized here by category:

### Structural Integrity

First, the validator ensures the file is parseable and non-trivial:

- **JSON syntax** — The data must unmarshal cleanly into a `[]Guideline` struct using `json.Unmarshal` (lines 30-34). Failures surface as *“parse guidelines JSON”* errors.
- **Non-empty collection** — The guidelines slice must contain at least one entry (lines 35-37), preventing empty configurations with the error *“guidelines are empty”*.

### Identifier Constraints

Each guideline requires a unique, well-formed ID:

- **ID format** — The `validID` helper (lines 91-100) scans each rune to enforce that `id` contains only lowercase letters, digits, or underscores.
- **Duplicate detection** — A `seenIDs` map tracks encountered identifiers (lines 39-47). If a duplicate appears, the validator returns *“guideline id … is duplicated”*.

### Version Control Rules

The validator ensures version information follows Go’s release conventions and logical ordering:

- **Version format** — The `since_version` field must match the *major.minor* pattern (e.g., “1.27”) validated by `goversion.IsMajorMinor` (lines 49-52).
- **Ordering constraint** — Guidelines must be sorted **newest-first** (highest version first). The code compares each entry with the previous using `goversion.Compare` (lines 52-60), returning an error if the sequence is out of order.

### Semantic Requirements

Four checks enforce that each guideline contains complete metadata:

- **Modernizer flag** — The `modernizer` field cannot be omitted or nil (lines 61-64). The error *“guideline … has no modernizer flag”* ensures every rule identifies its corresponding modernization tool.
- **Category presence** — The `category` string must be non-empty (lines 64-66), triggering *“guideline … has no category”* if missing.
- **Impact enumeration** — Impact levels are restricted to `Critical`, `High`, `Medium`, or `Low` via the `validImpact` switch statement (lines 103-110).
- **Content completeness** — Both the `guideline` and `details` fields must contain substantive text. The validator uses `strings.TrimSpace` to reject blank or whitespace-only strings (lines 70-75), returning errors for missing guideline text or details.

### Example Integrity

Finally, the validator ensures educational value through code examples:

- **Example presence** — Each guideline must provide at least one example in its `Examples` array (lines 76-78), or the error *“guideline … has no examples”* is raised.
- **Snippet completeness** — Every example must contain non-empty `before` **and** `after` code blocks (lines 79-86). The validator concatenates and trims the snippet slices, returning specific errors if either side is empty.

## Loading Guidelines in Practice

To load and validate the guidelines in your own code, use the `schema` package:

```go
package main

import (
	"fmt"
	"os"

	"github.com/JetBrains/go-modern-guidelines/internal/guidelines/schema"
)

func main() {
	data, err := os.ReadFile("internal/guidelines/guidelines.json")
	if err != nil {
		panic(err)
	}
	guidelines, err := schema.Parse(data)
	if err != nil {
		// Validation failed – the error explains the exact problem.
		fmt.Printf("Invalid guidelines.json: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("Loaded %d guidelines successfully.\n", len(guidelines))
}

```

Running the program prints:

```

Loaded 150 guidelines successfully.

```

If validation fails, the output identifies the specific violation:

```

Invalid guidelines.json: guideline "strings_cut" has empty after snippet

```

Once parsed, the data is safe to iterate without additional checks:

```go
for _, g := range guidelines {
    fmt.Printf("- %s (%s) – %s\n", g.ID, g.SinceVersion, g.Impact)
}

```

## Summary

- **`schema.Parse`** in [`internal/guidelines/schema/schema.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/schema/schema.go) serves as the single validation entry point, called by [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go).
- The validator performs **thirteen distinct checks** covering syntax, ID uniqueness, version formatting (major.minor), ordering (newest-first), required fields, enum values, and example completeness.
- Validation is **fail-fast**: the first error encountered aborts loading, preventing the tool from running with corrupt data.
- **IDs** must be lowercase alphanumeric with underscores; **impact** must be Critical, High, Medium, or Low; **versions** must follow Go’s major.minor pattern.
- Every guideline must include at least one example with non-empty `before` and `after` code snippets.

## Frequently Asked Questions

### What happens if guidelines.json contains duplicate IDs?

The `seenIDs` map in `schema.Parse` detects duplicates during the single-pass iteration (lines 39-47). When a duplicate is found, the function immediately returns an error formatted as *“guideline id … is duplicated”*, causing the program to exit before processing any guidelines.

### Why must guidelines be sorted newest-first in the JSON?

The validator enforces descending version order using `goversion.Compare` (lines 52-60) to ensure the tool presents the most recent Go features first during modernization checks. If an entry has a lower version than its predecessor, the parser returns an ordering error.

### What are the valid values for the impact field?

The `validImpact` function (lines 103-110) restricts the `impact` field to exactly four string values: **`Critical`**, **`High`**, **`Medium`**, or **`Low`**. Any other value causes validation to fail with an impact-specific error.

### How does the validator check example code snippets?

For each example, the validator concatenates the `before` and `after` string slices, trims whitespace, and checks for empty results (lines 79-86). If either side is blank after trimming, it returns specific errors distinguishing between missing *before* snippets, missing *after* snippets, or completely empty examples.