Skip to content

Commit e4d0819

Browse files
authored
feat(docs): Add examples for predefined document schemas (#342)
1 parent 2971d72 commit e4d0819

File tree

11 files changed

+539
-99
lines changed

11 files changed

+539
-99
lines changed

docs/src/architecture/08_concepts/signed_doc/docs/proposal_submission_action.md

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -199,49 +199,106 @@ States:
199199
`hide` is only actioned if sent by the author,
200200
for a collaborator it identified that they do not wish to be listed as a `collaborator`.
201201

202-
Schema :
203-
<!-- markdownlint-disable MD013 -->
204-
```json
205-
{
206-
"$id": "https://raw.githubusercontent.com/input-output-hk/catalyst-libs/refs/heads/main/specs/signed_docs/docs/payload_schemas/proposal_submission_action.schema.json",
207-
"$schema": "http://json-schema.org/draft-07/schema#",
208-
"additionalProperties": false,
209-
"definitions": {
210-
"action": {
211-
"description": "The action being performed on the Proposal.",
212-
"enum": [
213-
"final",
214-
"draft",
215-
"hide"
202+
### Schema
203+
204+
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
205+
??? abstract
206+
207+
The kind of action is controlled by this payload.
208+
The Payload is a [JSON][RFC8259] Document, and must conform to this schema.
209+
210+
States:
211+
212+
* `final` : All collaborators must publish a `final` status for the proposal to be `final`.
213+
* `draft` : Reverses the previous `final` state for a signer and accepts collaborator status to a document.
214+
* `hide` : Requests the proposal be hidden (not final, but a hidden draft).
215+
`hide` is only actioned if sent by the author,
216+
for a collaborator it identified that they do not wish to be listed as a `collaborator`.
217+
218+
```json
219+
{
220+
"$id": "https://raw.githubusercontent.com/input-output-hk/catalyst-libs/refs/heads/main/specs/signed_docs/docs/payload_schemas/proposal_submission_action.schema.json",
221+
"$schema": "http://json-schema.org/draft-07/schema#",
222+
"additionalProperties": false,
223+
"definitions": {
224+
"action": {
225+
"description": "The action being performed on the Proposal.",
226+
"enum": [
227+
"final",
228+
"draft",
229+
"hide"
230+
],
231+
"type": "string"
232+
}
233+
},
234+
"description": "Structure of the payload of a Proposal Submission Action.",
235+
"maintainers": [
236+
{
237+
"name": "Catalyst Team",
238+
"url": "https://projectcatalyst.io/"
239+
}
216240
],
217-
"type": "string"
241+
"properties": {
242+
"action": {
243+
"$ref": "#/definitions/action"
244+
}
245+
},
246+
"required": [
247+
"action"
248+
],
249+
"title": "Proposal Submission Action Payload Schema",
250+
"type": "object",
251+
"x-changelog": {
252+
"2025-03-01": [
253+
"First Version Created."
254+
]
255+
}
256+
}
257+
```
258+
259+
<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
260+
261+
### Examples
262+
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
263+
??? example "Example: Final Proposal Submission"
264+
265+
This document indicates the linked proposal is final and requested to proceed for further consideration.
266+
267+
```json
268+
{
269+
"action": "final"
218270
}
219-
},
220-
"description": "Structure of the payload of a Proposal Submission Action.",
221-
"maintainers": [
271+
```
272+
273+
<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
274+
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
275+
??? example "Example: Draft Proposal Submission"
276+
277+
This document indicates the linked proposal is no longer final and should not proceed for further consideration.
278+
It is also used by collaborators to accept that they are a collaborator on a document.
279+
280+
```json
222281
{
223-
"name": "Catalyst Team",
224-
"url": "https://projectcatalyst.io/"
282+
"action": "draft"
225283
}
226-
],
227-
"properties": {
228-
"action": {
229-
"$ref": "#/definitions/action"
284+
```
285+
286+
<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
287+
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
288+
??? example "Example: Hidden Proposal Submission"
289+
290+
If submitted by the proposal author the document is hidden, it is still public but not shown as
291+
a proposal being drafted.
292+
If submitted by a collaborator, that collaborator is declaring they do not wish to be listed as
293+
a collaborator on the proposal.
294+
295+
```json
296+
{
297+
"action": "hide"
230298
}
231-
},
232-
"required": [
233-
"action"
234-
],
235-
"title": "Proposal Submission Action Payload Schema",
236-
"type": "object",
237-
"x-changelog": {
238-
"2025-03-01": [
239-
"First Version Created."
240-
]
241-
}
242-
}
243-
```
244-
<!-- markdownlint-enable MD013 -->
299+
```
300+
301+
<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
245302

246303
## Signers
247304

specs/gen_docs/gen/docs_page_md.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
"""Generate the individual pages docs/<doc_name>.md file."""
22

33
import argparse
4-
import json
54
import typing
65

7-
from pydantic import HttpUrl
8-
96
from gen.doc_generator import DocGenerator
107
from gen.doc_relationship_diagrams import DocRelationshipFile
118
from spec.signed_doc import SignedDoc
@@ -62,24 +59,7 @@ def document_payload(self) -> str:
6259
if self._doc.payload is None:
6360
return self.TODO_MSG
6461

65-
payload_docs = self._doc.payload.description + "\n"
66-
67-
schema = self._doc.payload.doc_schema
68-
if schema is not None:
69-
if isinstance(schema, HttpUrl):
70-
if schema == "https://json-schema.org/draft-07/schema":
71-
payload_docs += "\n**Must be a valid JSON Schema Draft 7 document.**"
72-
else:
73-
payload_docs += f"\nMust be a valid according to <{schema}>."
74-
else:
75-
payload_docs += f"""\nSchema :
76-
<!-- markdownlint-disable MD013 -->
77-
```json
78-
{json.dumps(schema, indent=2, sort_keys=True)}
79-
```
80-
<!-- markdownlint-enable MD013 -->
81-
"""
82-
return payload_docs.strip()
62+
return f"{self._doc.payload}"
8363

8464
def document_signers(self) -> str:
8565
"""Generate documentation about who may sign this documents."""

specs/gen_docs/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ description = "Generate Signed Document documentation files."
55
readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
8+
"jsonschema[format]>=4.23.0",
89
"pydantic>=2.11.4",
910
"pydot>=3.0.4",
1011
"rich>=14.0.0",

specs/gen_docs/spec/payload.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,122 @@
11
"""Payload Specification."""
22

3+
import json
4+
import textwrap
5+
import urllib
6+
import urllib.request
37
from typing import Any
48

9+
import jsonschema
10+
import rich
511
from pydantic import BaseModel, ConfigDict, Field, HttpUrl
612

713

14+
class PayloadExample(BaseModel):
15+
"""An Example of the payload."""
16+
17+
title: str
18+
description: str
19+
example: dict[str, Any]
20+
21+
model_config = ConfigDict(extra="forbid")
22+
23+
@classmethod
24+
def default(cls) -> list["PayloadExample"]:
25+
"""Return Default list."""
26+
return []
27+
28+
def __str__(self) -> str:
29+
"""Get the example properly formatted as markdown."""
30+
example = json.dumps(self.example, indent=2, sort_keys=True)
31+
textwrap.indent(example, " ")
32+
33+
return f"""
34+
35+
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
36+
??? example "Example: {self.title}"
37+
38+
{textwrap.indent(self.description, " ")}
39+
40+
```json
41+
{textwrap.indent(example, " ")}
42+
```
43+
44+
<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
45+
""".strip()
46+
47+
48+
class SchemaValidationError(Exception):
49+
"""Something is wrong with payload schema validation."""
50+
51+
852
class Payload(BaseModel):
953
"""Payload Deserialized Specification."""
1054

1155
description: str
1256
doc_schema: HttpUrl | dict[str, Any] | None = Field(default=None, alias="schema")
57+
examples: list[PayloadExample] = Field(default_factory=PayloadExample.default)
1358

1459
model_config = ConfigDict(extra="forbid")
60+
61+
def model_post_init(self, context: Any) -> None: # noqa: ANN401
62+
"""Validate the examples against the schema."""
63+
schema = None
64+
validator = None
65+
if isinstance(self.doc_schema, HttpUrl):
66+
if f"{self.doc_schema}" == "https://json-schema.org/draft-07/schema":
67+
schema = jsonschema.Draft7Validator.META_SCHEMA
68+
else:
69+
rich.print(f"Downloading Schema from: {self.doc_schema}")
70+
with urllib.request.urlopen(f"{self.doc_schema}") as response: # noqa: S310
71+
schema = json.loads(response.read())
72+
elif isinstance(self.doc_schema, dict):
73+
schema = self.doc_schema
74+
75+
if schema is not None:
76+
# Check that its valid jsonschema Draft 7.
77+
jsonschema.Draft7Validator.check_schema(schema)
78+
validator = jsonschema.Draft7Validator(schema, format_checker=jsonschema.draft7_format_checker)
79+
80+
for example in self.examples:
81+
if validator is None:
82+
msg = "No schema to validate payload examples."
83+
raise SchemaValidationError(msg)
84+
validator.validate(instance=example.example) # type: ignore # noqa: PGH003
85+
86+
return super().model_post_init(context)
87+
88+
def __str__(self) -> str:
89+
"""Get the examples properly formatted as markdown."""
90+
docs = self.description + "\n"
91+
92+
schema = self.doc_schema
93+
if schema is not None:
94+
if isinstance(schema, HttpUrl):
95+
if schema == "https://json-schema.org/draft-07/schema":
96+
docs += "\n**Must be a valid JSON Schema Draft 7 document.**"
97+
else:
98+
docs += f"\nMust be a valid according to <{schema}>."
99+
else:
100+
docs += f"""\n### Schema
101+
102+
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
103+
??? abstract
104+
105+
{textwrap.indent(self.description, " ")}
106+
107+
```json
108+
{textwrap.indent(json.dumps(schema, indent=2, sort_keys=True), " ")}
109+
```
110+
111+
<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
112+
"""
113+
114+
if len(self.examples) > 0:
115+
docs += "\n### Example"
116+
if len(self.examples) >= 2: # noqa: PLR2004
117+
docs += "s"
118+
docs += "\n"
119+
for example in self.examples:
120+
docs += f"{example}\n"
121+
122+
return docs.strip()

0 commit comments

Comments
 (0)