{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://chriopter.github.io/pspdx/schema/pspdx-v1.json",
  "title": ".pspdx",
  "description": "What a PSP homebrew project says about itself to PSPDX. The id comes from the source: io.github.<owner>.<repository> on GitHub, the host of the source and the name elsewhere. Version, date, package and size come from the project's releases, the icon, picture, film and sound out of the EBOOT. Without release the app follows its newest release; with it, exactly that release and never a newer one until the file changes. A catalog may list a project that has no .pspdx from a file of its own; the project's own file wins once it has one. No string holds a control character, except a newline in description. All text is UTF-8. A console shows the characters its font has and skips the rest. Readers ignore fields they don't know, so a newer optional field never breaks an older reader; this schema lists only the known ones, so a typo shows up when a file is checked against it.",
  "type": "object",
  "required": [
    "schema",
    "source",
    "name"
  ],
  "additionalProperties": false,
  "allOf": [
    {
      "if": {
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "enum": [
              "plugin",
              "iso"
            ]
          }
        }
      },
      "then": {
        "not": {
          "required": [
            "installdir"
          ]
        }
      }
    },
    {
      "if": {
        "required": [
          "source"
        ],
        "properties": {
          "source": {
            "type": "string",
            "not": {
              "pattern": "^https://github\\.com/"
            }
          }
        }
      },
      "then": {
        "properties": {
          "name": {
            "pattern": "[A-Za-z0-9]"
          },
          "source": {
            "pattern": "^https://(?:[^/?#\\\\\\u0000-\\u001f]*@)?(?![Ww][Ww][Ww]\\.(?:[:/?#]|(?![\\s\\S])))(?:-*[A-Za-z0-9][A-Za-z0-9-]*\\.)*-*[A-Za-z0-9][A-Za-z0-9-]*\\.?(?::[0-9]*)?(?:[/?#][^\\\\\\u0000-\\u001f]*)?(?![\\s\\S])",
            "not": {
              "pattern": "^https://(?:[^/?#\\\\]*@)?(?![^/?#\\\\]*@)(?:[A-Za-z0-9-]*\\.)*-*[Gg]-*[Ii]-*[Tt]-*[Hh]-*[Uu]-*[Bb]-*\\.-*[Ii]-*[Oo]-*\\.?(?:[:/?#]|(?![\\s\\S]))"
            }
          }
        }
      }
    },
    {
      "if": {
        "required": [
          "source",
          "release"
        ],
        "properties": {
          "source": {
            "type": "string",
            "not": {
              "pattern": "^https://github\\.com/"
            }
          }
        }
      },
      "then": {
        "properties": {
          "release": {
            "required": [
              "url",
              "published_at"
            ]
          }
        }
      }
    }
  ],
  "properties": {
    "schema": {
      "const": "https://chriopter.github.io/pspdx/schema/pspdx-v1.json",
      "description": "required; names this format and its version"
    },
    "source": {
      "type": "string",
      "maxLength": 255,
      "pattern": "^https://[^\\u0000-\\u001f]+(?![\\s\\S])",
      "if": {
        "pattern": "^https://github\\.com/"
      },
      "then": {
        "pattern": "^https://github\\.com/(?=[._-]*[A-Za-z0-9])[A-Za-z0-9_.-]{1,39}/(?=[._-]*[A-Za-z0-9])(?![._-]+\\.git/?(?![\\s\\S]))(?![A-Za-z0-9_.-]+\\.git\\.git/?(?![\\s\\S]))[A-Za-z0-9_.-]{1,100}/?(?![\\s\\S])"
      },
      "description": "required; where the project lives: an https:// URL, and on github.com a repository URL, https://github.com/<owner>/<repository>, a .git or a slash after it allowed. The owner is 1 to 39 and the repository 1 to 100 of [A-Za-z0-9_.-], each with a letter or digit besides a .git it ends in, so neither is . or ..; and a repository does not end in .git.git. Only a GitHub repository has releases the console can ask for in version 1. Any other source's host and the name make its id: the host without www., backwards, and the name, each part reduced to its ASCII letters and digits in lower case. So there the host is ASCII letters, digits and hyphens, with a letter or digit in every label, any other name written in punycode (xn--), the URL holds no backslash, and a host under github.io, whose ids would start io.github., is refused."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40,
      "pattern": "^[^\\u0000-\\u001f]*(?![\\s\\S])",
      "description": "required; what the list shows"
    },
    "type": {
      "enum": [
        "homebrew",
        "plugin",
        "iso"
      ],
      "default": "homebrew",
      "description": "optional; homebrew when missing. homebrew is an EBOOT installed under PSP/GAME, plugin a .prx for custom firmware, iso a disc image."
    },
    "category": {
      "type": "string",
      "minLength": 1,
      "maxLength": 24,
      "pattern": "^[^\\u0000-\\u001f]*(?![\\s\\S])",
      "description": "optional; the one group the app belongs in. game, emulator, app and demo are the ones the console has a tab for; without a category, a tab word in tags decides."
    },
    "tags": {
      "type": "array",
      "maxItems": 8,
      "uniqueItems": true,
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 24,
        "pattern": "^[^\\u0000-\\u001f]*(?![\\s\\S])"
      },
      "description": "optional; up to 8 different words for what the app is, each 1 to 24 characters. game, emulator, app and demo are the ones the console has a tab for."
    },
    "languages": {
      "type": "array",
      "maxItems": 16,
      "uniqueItems": true,
      "items": {
        "type": "string",
        "pattern": "^[a-z]{2,3}(?:-[a-z0-9]{2,8})?(?![\\s\\S])"
      },
      "description": "optional; the languages the app speaks, as lowercase ISO 639-1 codes, or 639-2 where a language has no two-letter code, a region after a hyphen where it matters: en, de, ja, pt-br. Up to 16, none twice. A console may show its user's language first."
    },
    "installdir": {
      "type": "string",
      "pattern": "^PSP/GAME/(?![.][Pp][Ss][Pp][Dd][Xx]-[Ss][Tt][Aa][Gg][Ee](?![\\s\\S]))[A-Za-z0-9_.-]{0,31}[A-Za-z0-9_-](?![\\s\\S])",
      "description": "optional; only for type homebrew. When missing: PSP/GAME/<repository name> for a GitHub source, PSP/GAME/<name> otherwise, the name reduced to [A-Za-z0-9_.-], cut at 32 and without the dots it then ends in. Where the package goes on the Memory Stick: the directory in the zip that holds the EBOOT.PBP, with everything beside and below it, is copied here. The folder is 1 to 32 of [A-Za-z0-9_.-], the name the XMB shows; it does not end in a dot, which FAT drops, so it is never . or .., and it is not .pspdx-stage in any case, where the client stages an install."
    },
    "summary": {
      "type": "string",
      "maxLength": 60,
      "pattern": "^[^\\u0000-\\u001f]*(?![\\s\\S])",
      "description": "optional; one line; the repository's description otherwise"
    },
    "author": {
      "type": "string",
      "maxLength": 60,
      "pattern": "^[^\\u0000-\\u001f]*(?![\\s\\S])",
      "description": "optional; a name; the repository's owner otherwise"
    },
    "license": {
      "type": "string",
      "maxLength": 60,
      "pattern": "^[^\\u0000-\\u001f]*(?![\\s\\S])",
      "description": "optional; free text, an SPDX identifier where there is one; what GitHub reports otherwise"
    },
    "description": {
      "type": "string",
      "maxLength": 2500,
      "pattern": "^[^\\u0000-\\u0009\\u000b-\\u001f]*(?![\\s\\S])",
      "description": "optional; plain text, newlines allowed"
    },
    "release": {
      "type": "object",
      "required": [
        "tag"
      ],
      "additionalProperties": false,
      "properties": {
        "tag": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "pattern": "^[^\\u0000-\\u001f]*(?![\\s\\S])",
          "description": "required; the release's tag as the host names it, a prerelease too"
        },
        "url": {
          "type": "string",
          "maxLength": 512,
          "format": "uri",
          "pattern": "^https://[\\u0021-\\u007e]+(?![\\s\\S])",
          "description": "optional; the release ZIP. On GitHub one of that release's assets, chosen by the ZIP rule when missing. Required outside GitHub."
        },
        "published_at": {
          "type": "string",
          "anyOf": [
            {
              "format": "date-time",
              "pattern": "^(?!1970-01-01T00:00:00Z)(?:19[7-9][0-9]|[2-9][0-9]{3})-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z(?![\\s\\S])"
            },
            {
              "format": "date",
              "pattern": "^(?!1970-01-01(?![\\s\\S]))(?:19[7-9][0-9]|[2-9][0-9]{3})-[0-9]{2}-[0-9]{2}(?![\\s\\S])"
            }
          ],
          "description": "optional; when the release was published, UTC, as a catalog writes it, after the start of 1970: 1970-01-01T00:00:01Z at the earliest, 1970-01-02 as a date. On GitHub the date comes from GitHub and this value is ignored. Required outside GitHub."
        }
      },
      "description": "optional; pins one release: PSPDX installs exactly this one and never looks for a newer one until the file changes. Without it, the newest release, with updates. A release is usable with one zip, or of several exactly one with psp in its name, in any case. Size and hashes are never written here; a catalog computes them."
    }
  },
  "$defs": {}
}
