Skip to content

feat(docs): Add payload examples #342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -199,49 +199,106 @@ States:
`hide` is only actioned if sent by the author,
for a collaborator it identified that they do not wish to be listed as a `collaborator`.

Schema :
<!-- markdownlint-disable MD013 -->
```json
{
"$id": "https://raw.githubusercontent.com/input-output-hk/catalyst-libs/refs/heads/main/specs/signed_docs/docs/payload_schemas/proposal_submission_action.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"action": {
"description": "The action being performed on the Proposal.",
"enum": [
"final",
"draft",
"hide"
### Schema

<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
??? abstract

The kind of action is controlled by this payload.
The Payload is a [JSON][RFC8259] Document, and must conform to this schema.

States:

* `final` : All collaborators must publish a `final` status for the proposal to be `final`.
* `draft` : Reverses the previous `final` state for a signer and accepts collaborator status to a document.
* `hide` : Requests the proposal be hidden (not final, but a hidden draft).
`hide` is only actioned if sent by the author,
for a collaborator it identified that they do not wish to be listed as a `collaborator`.

```json
{
"$id": "https://raw.githubusercontent.com/input-output-hk/catalyst-libs/refs/heads/main/specs/signed_docs/docs/payload_schemas/proposal_submission_action.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"action": {
"description": "The action being performed on the Proposal.",
"enum": [
"final",
"draft",
"hide"
],
"type": "string"
}
},
"description": "Structure of the payload of a Proposal Submission Action.",
"maintainers": [
{
"name": "Catalyst Team",
"url": "https://projectcatalyst.io/"
}
],
"type": "string"
"properties": {
"action": {
"$ref": "#/definitions/action"
}
},
"required": [
"action"
],
"title": "Proposal Submission Action Payload Schema",
"type": "object",
"x-changelog": {
"2025-03-01": [
"First Version Created."
]
}
}
```

<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->

### Examples
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
??? example "Example: Final Proposal Submission"

This document indicates the linked proposal is final and requested to proceed for further consideration.

```json
{
"action": "final"
}
},
"description": "Structure of the payload of a Proposal Submission Action.",
"maintainers": [
```

<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
??? example "Example: Draft Proposal Submission"

This document indicates the linked proposal is no longer final and should not proceed for further consideration.
It is also used by collaborators to accept that they are a collaborator on a document.

```json
{
"name": "Catalyst Team",
"url": "https://projectcatalyst.io/"
"action": "draft"
}
],
"properties": {
"action": {
"$ref": "#/definitions/action"
```

<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
??? example "Example: Hidden Proposal Submission"

If submitted by the proposal author the document is hidden, it is still public but not shown as
a proposal being drafted.
If submitted by a collaborator, that collaborator is declaring they do not wish to be listed as
a collaborator on the proposal.

```json
{
"action": "hide"
}
},
"required": [
"action"
],
"title": "Proposal Submission Action Payload Schema",
"type": "object",
"x-changelog": {
"2025-03-01": [
"First Version Created."
]
}
}
```
<!-- markdownlint-enable MD013 -->
```

<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->

## Signers

Expand Down
22 changes: 1 addition & 21 deletions specs/gen_docs/gen/docs_page_md.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
"""Generate the individual pages docs/<doc_name>.md file."""

import argparse
import json
import typing

from pydantic import HttpUrl

from gen.doc_generator import DocGenerator
from gen.doc_relationship_diagrams import DocRelationshipFile
from spec.signed_doc import SignedDoc
Expand Down Expand Up @@ -62,24 +59,7 @@ def document_payload(self) -> str:
if self._doc.payload is None:
return self.TODO_MSG

payload_docs = self._doc.payload.description + "\n"

schema = self._doc.payload.doc_schema
if schema is not None:
if isinstance(schema, HttpUrl):
if schema == "https://json-schema.org/draft-07/schema":
payload_docs += "\n**Must be a valid JSON Schema Draft 7 document.**"
else:
payload_docs += f"\nMust be a valid according to <{schema}>."
else:
payload_docs += f"""\nSchema :
<!-- markdownlint-disable MD013 -->
```json
{json.dumps(schema, indent=2, sort_keys=True)}
```
<!-- markdownlint-enable MD013 -->
"""
return payload_docs.strip()
return f"{self._doc.payload}"

def document_signers(self) -> str:
"""Generate documentation about who may sign this documents."""
Expand Down
1 change: 1 addition & 0 deletions specs/gen_docs/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ description = "Generate Signed Document documentation files."
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"jsonschema[format]>=4.23.0",
"pydantic>=2.11.4",
"pydot>=3.0.4",
"rich>=14.0.0",
Expand Down
108 changes: 108 additions & 0 deletions specs/gen_docs/spec/payload.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,122 @@
"""Payload Specification."""

import json
import textwrap
import urllib
import urllib.request
from typing import Any

import jsonschema
import rich
from pydantic import BaseModel, ConfigDict, Field, HttpUrl


class PayloadExample(BaseModel):
"""An Example of the payload."""

title: str
description: str
example: dict[str, Any]

model_config = ConfigDict(extra="forbid")

@classmethod
def default(cls) -> list["PayloadExample"]:
"""Return Default list."""
return []

def __str__(self) -> str:
"""Get the example properly formatted as markdown."""
example = json.dumps(self.example, indent=2, sort_keys=True)
textwrap.indent(example, " ")

return f"""
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
??? example "Example: {self.title}"
{textwrap.indent(self.description, " ")}
```json
{textwrap.indent(example, " ")}
```
<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
""".strip()


class SchemaValidationError(Exception):
"""Something is wrong with payload schema validation."""


class Payload(BaseModel):
"""Payload Deserialized Specification."""

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

model_config = ConfigDict(extra="forbid")

def model_post_init(self, context: Any) -> None: # noqa: ANN401
"""Validate the examples against the schema."""
schema = None
validator = None
if isinstance(self.doc_schema, HttpUrl):
if f"{self.doc_schema}" == "https://json-schema.org/draft-07/schema":
schema = jsonschema.Draft7Validator.META_SCHEMA
else:
rich.print(f"Downloading Schema from: {self.doc_schema}")
with urllib.request.urlopen(f"{self.doc_schema}") as response: # noqa: S310
schema = json.loads(response.read())
elif isinstance(self.doc_schema, dict):
schema = self.doc_schema

if schema is not None:
# Check that its valid jsonschema Draft 7.
jsonschema.Draft7Validator.check_schema(schema)
validator = jsonschema.Draft7Validator(schema, format_checker=jsonschema.draft7_format_checker)

for example in self.examples:
if validator is None:
msg = "No schema to validate payload examples."
raise SchemaValidationError(msg)
validator.validate(instance=example.example) # type: ignore # noqa: PGH003

return super().model_post_init(context)

def __str__(self) -> str:
"""Get the examples properly formatted as markdown."""
docs = self.description + "\n"

schema = self.doc_schema
if schema is not None:
if isinstance(schema, HttpUrl):
if schema == "https://json-schema.org/draft-07/schema":
docs += "\n**Must be a valid JSON Schema Draft 7 document.**"
else:
docs += f"\nMust be a valid according to <{schema}>."
else:
docs += f"""\n### Schema
<!-- markdownlint-disable MD013 MD046 max-one-sentence-per-line -->
??? abstract
{textwrap.indent(self.description, " ")}
```json
{textwrap.indent(json.dumps(schema, indent=2, sort_keys=True), " ")}
```
<!-- markdownlint-enable MD013 MD046 max-one-sentence-per-line -->
"""

if len(self.examples) > 0:
docs += "\n### Example"
if len(self.examples) >= 2: # noqa: PLR2004
docs += "s"
docs += "\n"
for example in self.examples:
docs += f"{example}\n"

return docs.strip()
Loading
Loading