Skip to content
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

Now generating API method documentation from OpenAPI schema. #119

Merged
merged 63 commits into from
Mar 20, 2025
Merged
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3547a58
feat: rest-api generated with openapi.yaml
schuur Feb 18, 2025
3a7a8f8
docs: moved intro text on API to index.md. Rest of the text will be g…
schuur Feb 26, 2025
fe3e2ac
docs: typo and styling
schuur Feb 26, 2025
81c4d7f
feat: adapted script for generating api, included documentation
schuur Feb 26, 2025
4a667b8
docs: Action Events returns 200 with empty body
schuur Feb 26, 2025
c4f9601
docs: Error and FilterCriteria now standalone types, used by API
schuur Feb 26, 2025
46baee9
docs: Events plural typo
schuur Feb 26, 2025
7b9572c
fix: styling of dt code term
schuur Feb 26, 2025
e73f573
docs: included details on ListFootprints
schuur Feb 26, 2025
fd72b51
feat: generate parameter + type descriptions
schuur Feb 26, 2025
b04c0b5
docs: incorporated details on ListFootprints and GetFootprint from d…
schuur Feb 27, 2025
759e678
docs: clarified limit and filter parameters for ListFootprints
schuur Mar 4, 2025
56be0a6
feat: added generation of request bodies and recursive description of…
schuur Mar 4, 2025
122c476
docs: Event api objects now correctly structured
schuur Mar 4, 2025
71c7a56
docs: Error object sample added
schuur Mar 4, 2025
a001fea
docs: typos and references in openapi fixed
schuur Mar 4, 2025
295ed08
docs: added documentation on RequestCreatedEvent and RequestFulfilled…
schuur Mar 5, 2025
d2da4c1
docs: updated the CE event type identifiers to a new v3 version.
schuur Mar 5, 2025
ab2a18e
docs: clarification on empty result from ListFootprints
schuur Mar 5, 2025
2c682c8
docs: deduplication of filter parameters which are used by ListFootpr…
schuur Mar 5, 2025
7c66007
docs: clarification on async diagram
schuur Mar 5, 2025
2835434
fix: make sure to omit query string sample if not query parameters ar…
schuur Mar 5, 2025
2a8ed91
docs: added RequestRejected documentation
schuur Mar 5, 2025
23d2d83
fix: h4 heading in header-color and slighty bigger
schuur Mar 5, 2025
6ba6911
docs: completed GetFootprint description
schuur Mar 6, 2025
99c10ea
docs: removed duplicate example
schuur Mar 6, 2025
769270c
docs: added Error handling section
schuur Mar 7, 2025
f6cc633
docs: condensed host requirements
schuur Mar 7, 2025
60dbe67
docs: error handling refers to OAuth spec
schuur Mar 7, 2025
7c7774c
docs: simplified Authentication section
schuur Mar 7, 2025
b4f86ea
docs: references added in authentication flow.
schuur Mar 8, 2025
4e4f800
docs: condensed requirements
schuur Mar 8, 2025
34409a3
fix: removed hardcoded text from script, no relying on description in…
schuur Mar 8, 2025
fdc91b5
docs: expanded info on ListFootprints and GetFootprint responses
schuur Mar 8, 2025
0923b4c
docs: added requirement on having one fullfilled event per request e…
schuur Mar 8, 2025
f5cddf3
docs: fixed typo in RequestRejectedEvent sample
schuur Mar 8, 2025
1608936
docs: moved error object requirement to the individual responses
schuur Mar 8, 2025
739831b
fix: replace print with logging.debug
schuur Mar 9, 2025
5669510
fix: markdown fixes
schuur Mar 9, 2025
3de2a86
feat: write_property can now not define terms (with dfn)
schuur Mar 10, 2025
fb7e71c
docs: moved examples to index.md
schuur Mar 10, 2025
f9ff3d5
docs: removed optional operationId
schuur Mar 10, 2025
86df842
docs: fixed references to examples
schuur Mar 10, 2025
a9f2951
docs: fixed samples
schuur Mar 10, 2025
092f36d
docs: fixed event identifier in sample
schuur Mar 10, 2025
214cb04
docs: clean up of dead links
schuur Mar 10, 2025
296ac60
docs: adapting links
schuur Mar 10, 2025
cacbfe5
feat: custom.css now overrides markdown.css
schuur Mar 10, 2025
6c4101d
docs: updated dates
schuur Mar 10, 2025
648c2ef
docs: date updated
schuur Mar 10, 2025
516af81
docs: fixed heading level for examples
schuur Mar 10, 2025
317ec93
docs: intro api refers to general section on exchanging pcfs.
schuur Mar 10, 2025
3774a0f
docs: Fixed typo in link
schuur Mar 10, 2025
d9494de
fix: remove indententation for html elements in markdown. will maximi…
schuur Mar 11, 2025
8270af7
fix: include <dfn> for API operation definitions , so [= =] links wor…
schuur Mar 12, 2025
1d4385a
docs: fixed numerous typos, kudos @bhadley!
schuur Mar 12, 2025
a033513
docs: fixed HTTP samples
schuur Mar 13, 2025
5cc129e
docs: added retry information to RequestCreatedEvent and PublishedEve…
schuur Mar 13, 2025
d113e50
docs: clarify definition of change of property
schuur Mar 13, 2025
0c3ed0e
docs: fixed 2->3 url in sample
schuur Mar 13, 2025
7f48c23
docs: remove obsolete methodology links
schuur Mar 13, 2025
f0ccfff
docs: included retry description for callback events to data-recipients
schuur Mar 13, 2025
4045671
chore: changelog updated
schuur Mar 13, 2025
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
15 changes: 2 additions & 13 deletions assets/custom.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
/*
html,body {
font-family: var(--markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif)
}
h1,h2,h3,h4,h5,h6 {
font-weight: 500;
color: var(--heading-text, #333);
}
*/
h1 {
border-bottom-width: 0;
}
h4 {
font-size: 1.1em;
color: var(--heading-text);
}

pre,
code,
samp {
4 changes: 2 additions & 2 deletions scripts/excel.py
Original file line number Diff line number Diff line change
@@ -99,7 +99,7 @@ def format(item, style):
bold_style = dict(font = fontname, bold = True)
obsolete_style = dict(font = fontname, strike = True, fgcolor = "95261F")

logging.info(f"Columns: {columns}")
logging.debug(f"Columns: {columns}")

# Append the title and header rows
ws.append(["PACT Simplified Tech Specs" + status, "", "", "", "", "", "", "", "", "", ""])
@@ -167,7 +167,7 @@ def write_property(name, info, parent, level):
description = re.sub(r'\bproperty\b', 'attribute', description)

examples = info.get("examples", []) + ['','','']
print(examples)
logging.debug(examples)
mandatory = name in parent.get("required", [])

# Append a row to the worksheet
295 changes: 229 additions & 66 deletions scripts/openapi.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import os
import sys
import yaml
import json
import jsonref
import markdown
import logging


def get_property_type_spec(property):
"""
Generates a textual representation of a JSON schema property type.
Args:
property (dict): A dictionary representing the JSON schema property.
Returns:
str: A string describing the type of the property, including additional
information such as format, enum values, and deprecation status.
The function handles the following property types:
- object: Assumes the object is a reference to another type and uses its title.
- array: Recursively processes the items in the array.
- primitive types: Uses the type directly.
Additional property attributes handled:
- format: Appends the format to the type description.
- enum: Indicates that the property is an enumeration and lists the possible values.
- obsolete: Marks the property as obsolete.
- deprecated: Marks the property as deprecated.
- const: Indicates that the property has a constant value.
"""
text = ""
if property["type"] == "object":
# Assume that the object is a reference to another type.
@@ -30,17 +46,30 @@ def get_property_type_spec(property):

if "obsolete" in property:
text += "<span class='json-schema-obsolete'>obsolete</span>"
if "deprecated" in property:
text += "<span class='json-schema-obsolete'>deprecated</span>"

if "const" in property:
text += f" = `\"{property['const']}\"`"

if "enum" in property:
text += "<div class='json-schema-enum'>"
text += "<div class='json-schema-enum'>\n"
for enum in property["enum"]:
text += f" `\"{enum}\"`"
text += "</div>"
text += f"`\"{enum}\"` "
text += "\n</div>"

return text


def get_example_text(name, property):
"""
Generates a formatted JSON example text for a given property.
Args:
name (str): The name of the property.
property (dict): The property dictionary which may contain an "examples" key.
Returns:
str: A formatted string containing the JSON example wrapped in a code block.
"""
text = ""
example = None
if "examples" in property:
@@ -62,10 +91,112 @@ def get_example_text(name, property):

return text

def write_defs_start(output, namespace=None, headers=['Name', 'Description']):
if namespace:
output.write(f"<table class='data' dfn-for='{namespace}' dfn-type='element-attr'>\n")
else:
output.write("<table class='data'>\n")
if headers:
output.write("<thead>\n")
output.write("<tr>\n")
for header in headers:
output.write(f"<th>{header}</th>\n")
output.write("</tr>\n")
output.write("</thead>\n")
output.write("<tbody>\n")

def write_defs_end(output):
output.write("</table>\n\n")


def write_property(output, type, name, property, termdef=True, recursive=False):
"""
Writes an HTML table row representing a property in a JSON schema.
Args:
output (io.TextIOWrapper): The output stream to write the HTML to.
type (dict): The JSON schema type definition containing the property.
name (str): The name of the property.
property (dict): The property definition from the JSON schema.
Returns:
None
"""
output.write("<tr>\n")
if termdef:
output.write(f"<td><dfn>{name}</dfn></td>\n")
else:
output.write(f"<td>`{name}`</td>\n")
output.write("<td>\n")
output.write("<div class='json-schema-type'>")
if 'required' in type and name in type['required']:
output.write("<span class='json-schema-required'>required</span> ")
output.write(get_property_type_spec(property) + "</div>\n")
output.write("\n\n")
output.write(property["description"].strip().replace("\n\n\n", "\n\n\n\n"))
output.write(get_example_text(name, property))
output.write("\n")
if property.get("x-unit"):
output.write(f"\n<div class='json-schema-unit'>{property['x-unit']}")
if property.get("comment"):
output.write(f"<br>\n{sanitize(property['comment'])}")
output.write("</div>\n")
output.write("</td></tr>\n")
if recursive and property["type"] == "object" and "properties" in property and not "title" in property:
for sub_name, sub_property in property["properties"].items():
write_property(output, property, name + "." + sub_name, sub_property, termdef)



def write_parameter(output, name, parameter):
"""
Writes an HTML table row representing a parameter in a JSON schema.
Args:
output (io.TextIOWrapper): The output stream to write the HTML to.
type (dict): The JSON schema type definition containing the parameter.
name (str): The name of the parameter.
parameter (dict): The parameter definition from the JSON schema.
Returns:
None
"""
output.write("<tr>\n")
output.write(f"<td><dfn>{name}</dfn> ({parameter['in']})</td>\n")
output.write("<td>\n")
output.write("<div class='json-schema-type'>")
if 'required' in parameter and parameter['required'] == True:
output.write("<span class='json-schema-required'>required</span> ")
output.write(get_property_type_spec(parameter['schema']) + "</div>\n")
output.write("\n\n")
output.write(parameter["description"].strip().replace("\n\n\n", "\n\n\n\n"))
output.write(get_example_text(name, parameter))
output.write("\n")
output.write("</td></tr>\n")



def sanitize(text):
return text.replace("<", "&lt;").replace(">", "&gt;")

def generate_type_description(schema, type_name, type, output):

def generate_type_description(output, schema, type_name, type):
"""
Generates a markdown description for a given type schema and writes it to the provided output.
Args:
schema (dict): The entire schema containing the type definitions.
type_name (str): The name of the type being described.
type (dict): The type definition containing properties, descriptions, examples, etc.
output (TextIO): The output stream to write the markdown description to.
Writes:
A markdown formatted description of the type to the output stream, including sections for:
- Title
- Description
- Example
- Properties
- External Docs
- Examples
"""
title = type.get("title") or type_name
output.write(f"## <dfn element>{title}</dfn>\n")
if "description" in type:
@@ -76,63 +207,15 @@ def generate_type_description(schema, type_name, type, output):
if "example" in type:
output.write('<div class="example">\n')
output.write(type['example'])
output.write("\n</div>\n")
output.write("\n</div>\n\n")
if "properties" in type:
output.write("### Properties\n")
output.write("\n")
output.write(f"<table class='data' dfn-for='{title}' dfn-type='element-attr'>\n")
output.write("<thead>\n")
output.write("<tr>\n")
output.write(" <th>Name</th>\n")
output.write(" <th>Description</th>\n")
output.write("<tbody>\n")
output.write("### Properties\n\n")
write_defs_start(output, title)
for name, property in type["properties"].items():
if property.get("obsolete"):
continue
output.write("<tr>\n")
output.write(f" <td><dfn>{name}</dfn>\n")
output.write(" <td>\n")
output.write(" <div class='json-schema-type'>")
if 'required' in type and name in type['required']:
output.write(" <span class='json-schema-required'>required</span>\n")
output.write(get_property_type_spec(property) + "</div>\n")
output.write(" \n\n")
output.write(property["description"].strip().replace("\n\n\n", "\n\n\n\n"))
output.write(get_example_text(name, property))
output.write("\n")
if property.get("x-unit"):
output.write(f"<div class='json-schema-unit'>{property['x-unit']}")
if property.get("comment"):
output.write(f"<br>\n{sanitize(property['comment'])}")
output.write("</div>\n")

output.write("</table>\n\n")
if "allOf" in type:
output.write("### All Of\n")
output.write("\n")
for sub_type in type["allOf"]:
generate_type_description(schema, name, sub_type, output)
if "oneOf" in type:
output.write("### One Of\n")
output.write("\n")
for sub_type in type["oneOf"]:
generate_type_description(schema, name, sub_type, output)
if "anyOf" in type:
output.write("### Any Of\n")
output.write("\n")
for sub_type in type["anyOf"]:
generate_type_description(schema, name, sub_type, output)
if "enum" in type:
output.write("### Enum\n")
output.write("\n")
for value in type["enum"]:
output.write(f"* {value}\n")
output.write("\n")
# if "example" in type:
# output.write("### Example\n")
# output.write("\n")
# output.write(f"```json\n{json.dumps(type['example'], indent=2)}\n```\n")
# output.write("\n")
write_property(output, type, name, property)
write_defs_end(output)
if "x-externalDocs" in type:
output.write("### External Docs\n")
output.write("\n")
@@ -147,24 +230,104 @@ def generate_type_description(schema, type_name, type, output):
output.write(f"{example['description']}\n")
output.write("\n")

def generate_data_model(input, output):

def generate_data_model(input_path, output_path):
# Load the schema from the file
with open(input, encoding="utf-8") as file:
schema_unresolved = yaml.safe_load(file)
with open(input_path, encoding="utf-8") as input:
schema_unresolved = yaml.safe_load(input)
schema = jsonref.replace_refs(schema_unresolved, merge_props=True)

# Generate the data model in Bikeshed format
with open(output, "w", encoding="utf-8") as file:
with open(output_path, "w", encoding="utf-8") as output:
for name,type in schema["components"]["schemas"].items():
# Skip all types that are only used for requests or responses
if name.endswith("Event") or name.endswith("Request") or name.endswith("Response"):
continue
# Skip all types without a title
if not "title" in type:
continue
print(name)
logging.debug(name)
# for name in ["ProductFootprint", "CarbonFootprint"]:
generate_type_description(schema, name, type, file)
generate_type_description(output, schema, name, type)


def generate_operation(output, path, method, operation):
output.write(f"## <dfn>{operation['summary']}</dfn>")
output.write("\n")
query = "?params=value&..." if "parameters" in operation and any(p["in"] == "query" for p in operation["parameters"]) else ""
output.write(f"```HTTP\n{method.upper()} {path}{query}\n```\n")
output.write(f"{operation['description']}\n\n")
logging.debug(operation['description'])
if operation.get("parameters"):
output.write("### Parameters\n")
write_defs_start(output)
for param in operation["parameters"]:
#file.write(f"- **{param['name']}** ({param['in']}): {param['description']}\n")
# output.write(f"<tr>\n <td><dfn>{param['name']}</dfn> ({param['in']})\n")
# output.write(f" <td>{param['description']}\n")
write_parameter(output, param['name'], param)
write_defs_end(output)

if operation.get("requestBody"):
for content_type, content in operation["requestBody"]["content"].items():
if "oneOf" in content["schema"]:
for variant in content["schema"]["oneOf"]:
output.write(f"### {variant['title']}\n\n")
output.write(variant["description"])
output.write("\n\n**Request Body**\n\n")
output.write(f"`content-type: {content_type}`\n")
write_defs_start(output)
for name, property in variant["properties"].items():
write_property(output, variant, name, property, termdef=False, recursive=True)
write_defs_end(output)
if "examples" in variant:
output.write(variant['examples'][0])
else:
output.write("\n\n**Request Body**\n\n")
output.write(f"`content-type: {content_type}`\n")
write_defs_start(output)
for name, property in content["schema"]["properties"].items():
write_property(output, variant, name, property, termdef=False)
write_defs_end(output)


output.write("\n")
output.write("### Responses\n")
output.write("`content-type: application/json`\n")
write_defs_start(output, headers=['Status', 'Response'])
for status, response in operation["responses"].items():
output.write(f"<tr>\n")
output.write(f"<td>**{status}**</td>\n")
output.write(f"<td>{response['description']}\n\n")
if not "content" in response:
output.write("No content\n")
elif status >= "200" and status < "300":
for content_type, content in response["content"].items():
write_defs_start(output, headers=None)
for name, property in content["schema"]["properties"].items():
logging.debug(name)
write_property(output, content['schema'], name, property, termdef=False)
write_defs_end(output)
output.write("</td>\n</tr>\n")
write_defs_end(output)
output.write("\n")


def generate_rest_api(input_path, output_path):
# Load the schema from the file
with open(input_path, encoding="utf-8") as input:
schema_unresolved = yaml.safe_load(input)
schema = jsonref.replace_refs(schema_unresolved, merge_props=True)

# Generate the REST API in Bikeshed format
with open(output_path, "w", encoding="utf-8") as output:

for path, path_item in schema["paths"].items():
for method, operation in path_item.items():
generate_operation(output, path, method, operation)




def test(input):
with open(input, encoding="utf-8") as file:
Loading