diff --git a/doc/STYLE.md b/doc/STYLE.md index 06c9ddd61..bab3a2b4d 100644 --- a/doc/STYLE.md +++ b/doc/STYLE.md @@ -194,6 +194,12 @@ All `warning` fields should have unique names which start with `warning_`, the value of which should be an explanation. This allows for programs to deal with them sanely, and also perform translations. +### Documenting JSON APIs + +We use JSON schemas to validate that JSON-RPC returns are in the +correct form, and also to generate documentation. See +[doc/schemas/WRITING_SCHEMAS.md](WRITING_SCHEMAS.md). + ## Changing JSON APIs All JSON API changes need a Changelog line (see below). diff --git a/doc/schemas/WRITING_SCHEMAS.md b/doc/schemas/WRITING_SCHEMAS.md new file mode 100644 index 000000000..eaf45fb55 --- /dev/null +++ b/doc/schemas/WRITING_SCHEMAS.md @@ -0,0 +1,77 @@ +# Writing JSON Schemas + +A JSON Schema is a JSON file which defines what a structure should +look like; in our case we use it in our testsuite to check that they +match command responses, and also use it to generate our +documentation. + +Yes, schemas are horrible to write, but they're damn useful. We can +only use a subset of the full [https://json-schema.org/](JSON Schema +Specification), but if you find that limiting it's probably a sign +that you should simplify your JSON output. + +## How to Write a Schema + +Name the schema doc/schemas/`command`.schema.json: the testsuite should +pick it up and check all invocations of that command against it. + +I recommend copying an existing one to start. + +You will need to put the magic lines in the manual page so `make doc-all` +will fill it in for you: + +``` +[comment]: # (GENERATE-FROM-SCHEMA-START) +[comment]: # (GENERATE-FROM-SCHEMA-END) +``` + +If something goes wrong, try tools/fromscheme.py +doc/schemas/`command`.schema.json to see how far it got before it died. + +You should always use `"additionalProperties": false`, otherwise +your schema might not be covering everything. Deprecated fields +simply have `"deprecated": true` in their properties, so they +are allowed by omitted from the documentation. + +You should always list all fields which are *always* present in +`"required"`. + +We extend the basic types; see +[contrib/pyln-testing/pyln/testing/fixtures.py](fixtures.py). + + +### Using Conditional Fields + +Sometimes one field is only sometimes present; if you can, you should make +the schema know when it should (and should not!) be there. + +There are two kinds of conditional fields expressable: fields which +are only present if another field is present, or fields only present +if another field has certain values. + +To add conditional fields: + +1. Do *not* mention them in the main "properties" section. +2. Set `"additionalProperties": true` for the main "properties" section. +3. Add an `"allOf": [` array at the same height as `"properties"'`. Inside + this place one `if`/`then` for each conditional field. +4. If a field simply requires another field to be present, use the pattern + `"required": [ "field" ]` inside the "if". +5. If a field requires another field value, use the pattern + `"properties": { "field": { "enum": [ "val1", "val2" ] } }` inside + the "if". +6. Inside the "then", use `"additionalProperties": false` and place + empty `{}` for all the other possible properties. +7. If you haven't covered all the possibilties with `if` statements, + add an `else` with `"additionalProperties": false` which simply + mentions every allowable property. This ensures that the fields + can *only* be present when conditions are met. + +### JSON Drinking Game! + +1. Sip whenever you have an additional comma at the end of a sequence. +2. Sip whenever you omit a comma in a sequence because you cut & paste. +3. Skull whenever you wish JSON had comments. + +Good luck! +Rusty.