regal icon indicating copy to clipboard operation
regal copied to clipboard

Create and publish `.regal/config.yaml` schema to JSON schema store

Open anderseknert opened this issue 1 year ago • 3 comments

We should create a JSON schema for the Regal configuration file format and have it published to the JSON schema store. This allows tools like the YAML language server (which is used by editors like VS Code and Zed) to automatically provide auto-completions and corrections based on the schema when the filename matches .regal/config.yaml.

Note that we should avoid having the schema auto-generated from Go structs, as both categories and rule names are dynamic. I'd suggest we use properties for all known categories and rule names, likely generated from the default config. We could provide one strict version, where only these names are allowed (to help identify typos), and one version where additional properties are allowed, for those who might have custom rules.

anderseknert avatar Aug 26 '24 07:08 anderseknert

Note that we should avoid having the schema auto-generated from Go structs, as both categories and rule names are dynamic. I'd suggest we use properties for all known categories and rule names, likely generated from the default config. We could provide one strict version, where only these names are allowed (to help identify typos), and one version where additional properties are allowed, for those who might have custom rules.

The generators aren't that bad these days. We could start with one and see what needs to be adjusted or added.

srenatus avatar Nov 12 '25 12:11 srenatus

This is what a reflection-based JSON Schema generator (https://github.com/swaggest/jsonschema-go) gives us:

{
  "definitions": {
    "ConfigBuiltin": {
      "properties": {
        "decl": {
          "$ref": "#/definitions/ConfigDecl"
        }
      },
      "type": "object"
    },
    "ConfigCapabilities": {
      "properties": {
        "builtins": {
          "additionalProperties": {
            "$ref": "#/definitions/ConfigBuiltin"
          },
          "type": [
            "object",
            "null"
          ]
        },
        "features": {
          "items": {
            "type": "string"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "future_keywords": {
          "items": {
            "type": "string"
          },
          "type": [
            "array",
            "null"
          ]
        }
      },
      "type": "object"
    },
    "ConfigCategory": {
      "additionalProperties": {
        "$ref": "#/definitions/ConfigRule"
      },
      "type": "object"
    },
    "ConfigDecl": {
      "properties": {
        "args": {
          "items": {
            "type": "string"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "result": {
          "type": "string"
        }
      },
      "type": "object"
    },
    "ConfigFeatures": {
      "properties": {
        "remote": {
          "$ref": "#/definitions/ConfigRemoteFeatures"
        }
      },
      "type": "object"
    },
    "ConfigIgnore": {
      "properties": {
        "files": {
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "ConfigProject": {
      "properties": {
        "rego-version": {
          "type": [
            "null",
            "integer"
          ]
        },
        "roots": {
          "items": {
            "$ref": "#/definitions/ConfigRoot"
          },
          "type": [
            "null",
            "array"
          ]
        }
      },
      "type": "object"
    },
    "ConfigRemoteFeatures": {
      "properties": {
        "check-version": {
          "type": "boolean"
        }
      },
      "type": "object"
    },
    "ConfigRoot": {
      "properties": {
        "rego-version": {
          "type": [
            "null",
            "integer"
          ]
        }
      },
      "type": "object"
    },
    "ConfigRule": {
      "properties": {
        "ignore": {
          "$ref": "#/definitions/ConfigIgnore"
        },
      },
      "type": "object"
    }
  },
  "properties": {
    "capabilities": {
      "$ref": "#/definitions/ConfigCapabilities"
    },
    "capabilities_url": {
      "type": "string"
    },
    "features": {
      "$ref": "#/definitions/ConfigFeatures"
    },
    "ignore": {
      "$ref": "#/definitions/ConfigIgnore"
    },
    "project": {
      "$ref": "#/definitions/ConfigProject"
    },
    "rules": {
      "additionalProperties": {
        "$ref": "#/definitions/ConfigCategory"
      },
      "type": [
        "object",
        "null"
      ]
    }
  },
  "type": "object"
}

The pain points there:

  1. (as you've mentioned) custom rule settings are not captured, so we cannot add additionalProperties: false to ConfigRule.
  2. project/roots doesn't work -- the generated schema puts it down as object, but we also allow strings

srenatus avatar Nov 12 '25 12:11 srenatus

Regarding issue number 2, it should be possible to anyOf (or whatever is equivalent) to allow either strings or objects, right? I'm not sure how advanced/modern constructs the YAML LSP supports though. Up until very recently I have seen warnings in my JSON schemas about some features not being supported in the schemas I have written (for other purposes). Only one way to know, I suppose..

anderseknert avatar Nov 12 '25 12:11 anderseknert