mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-19 16:57:40 +03:00
[DPP-673][Self-service error-codes] Generate error code directory compatible with both pdf and html docs (#11794)
CHANGELOG_BEGIN CHANGELOG_END
This commit is contained in:
parent
5a2c070da4
commit
55191847cd
@ -18,7 +18,6 @@ exports_files(
|
||||
"source/tools/export/output-root/Export.daml",
|
||||
"source/tools/export/output-root/args.json",
|
||||
"source/tools/export/output-root/daml.yaml",
|
||||
"sphinx_ext/self_service_error_codes_extension.py",
|
||||
],
|
||||
)
|
||||
|
||||
@ -100,11 +99,14 @@ genrule(
|
||||
"//ledger-api/grpc-definitions:docs",
|
||||
"//:LICENSE",
|
||||
"//:NOTICES",
|
||||
":generate-docs-error-code-inventory-into-rst-file",
|
||||
],
|
||||
outs = ["source.tar.gz"],
|
||||
cmd = """
|
||||
cp -rL docs/source source
|
||||
|
||||
mkdir -p -- source/error-codes/self-service
|
||||
cp -- $(location //docs:generate-docs-error-code-inventory-into-rst-file) source/app-dev/grpc/error_codes_inventory.rst
|
||||
# Copy in Stdlib
|
||||
mkdir -p source/daml/stdlib
|
||||
tar xf $(location //compiler/damlc:daml-base-rst.tar.gz) \\
|
||||
@ -200,7 +202,6 @@ genrule(
|
||||
genrule(
|
||||
name = "docs-no-pdf",
|
||||
srcs = glob([
|
||||
"sphinx_ext/**",
|
||||
"configs/html/**",
|
||||
"configs/static/pygments_daml_lexer.py",
|
||||
"configs/static/typescript.py",
|
||||
@ -208,7 +209,6 @@ genrule(
|
||||
":sources",
|
||||
":theme",
|
||||
":hoogle_db.tar.gz",
|
||||
"//docs:generate-error-codes-json",
|
||||
"//language-support/java:javadoc",
|
||||
"//language-support/ts/daml-react:docs",
|
||||
"//language-support/ts/daml-ledger:docs",
|
||||
@ -219,7 +219,6 @@ genrule(
|
||||
"@daml-cheat-sheet//:site",
|
||||
":scripts/check-closing-quotes.sh",
|
||||
":scripts/check-closing-quotes.sh.allow",
|
||||
"//docs:error_codes_export.json",
|
||||
],
|
||||
outs = ["html-only.tar.gz"],
|
||||
cmd = ("""
|
||||
@ -271,7 +270,7 @@ genrule(
|
||||
# the PDF documentation due to issues with the FreeSerif font in the
|
||||
# fontspec package. So, for now we ignore `FutureWarning`.
|
||||
SPHINX_BUILD_EXIT_CODE=0
|
||||
SPHINX_BUILD_OUTPUT=$$(../$(location @sphinx_nix//:bin/sphinx-build) -D error_codes_json_export=../$(location //docs:error_codes_export.json) -c docs/configs/html docs/source html 2>&1) || SPHINX_BUILD_EXIT_CODE=$$?
|
||||
SPHINX_BUILD_OUTPUT=$$(../$(location @sphinx_nix//:bin/sphinx-build) -c docs/configs/html docs/source html 2>&1) || SPHINX_BUILD_EXIT_CODE=$$?
|
||||
if [ "$$SPHINX_BUILD_EXIT_CODE" -ne 0 ]; then
|
||||
>&2 echo "## SPHINX-BUILD OUTPUT:"
|
||||
>&2 echo "$$SPHINX_BUILD_OUTPUT"
|
||||
@ -315,7 +314,6 @@ genrule(
|
||||
tools = [
|
||||
"@sphinx_nix//:bin/sphinx-build",
|
||||
"//bazel_tools/sh:mktgz",
|
||||
"//docs:generate-error-codes-json",
|
||||
] + (["@glibc_locales//:locale-archive"] if is_linux else []),
|
||||
) if not is_windows else None
|
||||
|
||||
@ -666,6 +664,15 @@ genrule(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "generate-docs-error-code-inventory-into-rst-file",
|
||||
srcs = [],
|
||||
outs = ["error_codes_inventory.rst"],
|
||||
cmd = "$(location //ledger/error/generator:generate-docs-error-code-inventory-app) $(location error_codes_inventory.rst)",
|
||||
tools = ["//ledger/error/generator:generate-docs-error-code-inventory-app"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"source/daml-script/template-root/src/ScriptExample.daml",
|
||||
])
|
||||
|
@ -36,7 +36,6 @@ sys.path.insert(0, os.path.abspath('../../sphinx_ext'))
|
||||
extensions = [
|
||||
'sphinx.ext.extlinks',
|
||||
'sphinx_copybutton',
|
||||
'self_service_error_codes_extension',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
@ -52,9 +52,6 @@ TEMPLATES_DIR=$BUILD_DIR/source/_templates
|
||||
mkdir -p $TEMPLATES_DIR
|
||||
tar -zxf $BAZEL_BIN/templates/templates-tarball.tar.gz -C $TEMPLATES_DIR --strip-components=1
|
||||
|
||||
# Error codes: create JSON file with error codes information
|
||||
bazel build //docs:generate-error-codes-json
|
||||
|
||||
for arg in "$@"
|
||||
do
|
||||
if [ "$arg" = "--pdf" ]; then
|
||||
@ -63,6 +60,10 @@ do
|
||||
cp -L ../../bazel-bin/docs/DigitalAssetSDK.pdf $BUILD_DIR/gen/_downloads
|
||||
fi
|
||||
if [ "$arg" = "--gen" ]; then
|
||||
|
||||
bazel build //docs:generate-docs-error-code-inventory-into-rst-file
|
||||
cp -L ../../bazel-bin/docs/error_codes_inventory.rst $BUILD_DIR/source/app-dev/grpc/error_codes_inventory.rst
|
||||
|
||||
# Hoogle
|
||||
bazel build //compiler/damlc:daml-base-hoogle.txt
|
||||
mkdir -p $BUILD_DIR/gen/hoogle_db
|
||||
|
@ -353,11 +353,14 @@ but there is no guarantee given that additional information will be preserved ac
|
||||
Error Codes Inventory
|
||||
**********************
|
||||
|
||||
.. list-all-error-codes::
|
||||
|
||||
.. This file is generated:
|
||||
.. include:: error_codes_inventory.rst
|
||||
|
||||
|
||||
Error Codes Migration Guide
|
||||
---------------------------
|
||||
*****************************
|
||||
|
||||
|
||||
The Ledger API gRPC error codes change introduced in the Daml SDK 1.18 release involves breaking
|
||||
compatibility with previous releases for some service Ledger API endpoints.
|
||||
@ -366,7 +369,7 @@ The table below outlines all the cases and error conditions when a Ledger API se
|
||||
gRPC status code in comparison to the pre-1.18 releases.
|
||||
|
||||
Common Ledger API changes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
---------------------------
|
||||
|
||||
The table below outlines generic gRPC status code changes pertaining to the Ledger API
|
||||
and apply to all ledger backends. For changes specific to a ledger backend, check the next subsections.
|
||||
@ -517,7 +520,7 @@ and apply to all ledger backends. For changes specific to a ledger backend, chec
|
||||
+-----------------------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------+
|
||||
|
||||
Sandbox (classic)
|
||||
^^^^^^^^^^^^^^^^^
|
||||
---------------------------
|
||||
|
||||
The following gRPC status codes have changed for submission rejections in Sandbox classic.
|
||||
|
||||
@ -543,7 +546,7 @@ The following gRPC status codes have changed for submission rejections in Sandbo
|
||||
|
||||
|
||||
Daml Sandbox and VMBC
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
---------------------------
|
||||
|
||||
The following gRPC status codes have changed for submission rejections in the Ledger API backed by KV-based ledgers (Daml Sandbox and VMBC).
|
||||
|
||||
|
@ -7,11 +7,6 @@ gRPC
|
||||
####
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
error-codes
|
||||
|
||||
If you want to write an application for the ledger API in other languages, you'll need to use `gRPC <https://grpc.io>`__ directly.
|
||||
|
||||
If you're not familiar with gRPC and protobuf, we strongly recommend following the `gRPC quickstart <https://grpc.io/docs/quickstart/>`__ and `gRPC tutorials <https://grpc.io/docs/tutorials/>`__. This documentation is written assuming you already have an understanding of gRPC.
|
||||
@ -100,3 +95,9 @@ INTERNAL, UNKNOWN (when returned by the Command Service)
|
||||
Aside from the standard gRPC status codes, the failures returned by the Ledger API are enriched with details meant to help the application
|
||||
or the application developer to handle the error autonomously (e.g. by retrying on a retryable error).
|
||||
For more details on the rich error details see the :doc:`error-codes`
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
error-codes
|
||||
|
@ -1,216 +0,0 @@
|
||||
# Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from collections import defaultdict
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive
|
||||
from sphinx.writers.html import HTMLTranslator
|
||||
from typing import Dict, Any
|
||||
|
||||
import json
|
||||
|
||||
error_codes_data = {}
|
||||
group_data = {}
|
||||
|
||||
CONFIG_OPT = 'error_codes_json_export'
|
||||
|
||||
def load_data(app, config):
|
||||
global error_codes_data
|
||||
global group_data
|
||||
|
||||
if CONFIG_OPT in config:
|
||||
file_name = config[CONFIG_OPT]
|
||||
try:
|
||||
with open(file_name) as f:
|
||||
tmp = json.load(f)
|
||||
error_codes_data = {error["code"]: error for error in tmp["errorCodes"]}
|
||||
group_data = {group["className"]: group for group in tmp["groups"]}
|
||||
except EnvironmentError:
|
||||
print(f"Failed to open file: '{file_name}'")
|
||||
raise
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_config_value(CONFIG_OPT, '', 'env')
|
||||
app.connect('config-inited', load_data)
|
||||
app.add_node(error_code_node)
|
||||
app.add_directive('list-all-error-codes', ListAllErrorCodesDirective)
|
||||
# Callback functions for populating error code section after the doctree is resolved
|
||||
app.connect('doctree-resolved', process_error_code_nodes)
|
||||
# Overwriting standard Sphinx translator to allow linking of return types
|
||||
app.set_translator('html', PatchedHTMLTranslator)
|
||||
|
||||
|
||||
class error_code_node(nodes.General, nodes.Element):
|
||||
def __init__(self, codes, expl="", res=""):
|
||||
nodes.Element.__init__(self)
|
||||
print("found error node codes: %s" % codes)
|
||||
self.codes = codes
|
||||
self.expl = expl
|
||||
self.res = res
|
||||
|
||||
|
||||
class ListAllErrorCodesDirective(Directive):
|
||||
has_contents = True
|
||||
required_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
def run(self):
|
||||
return [error_code_node([], None, None)]
|
||||
|
||||
|
||||
def text_node(n, txt):
|
||||
# Doubling the parameter, as TextElements want the raw text and the text
|
||||
return n(txt, txt)
|
||||
|
||||
|
||||
def process_error_code_nodes(app, doctree, fromDocName):
|
||||
def build_indented_bold_and_non_bold_node(bold_text: str, non_bold_text: str):
|
||||
bold = text_node(n=nodes.strong, txt=bold_text)
|
||||
non_bold = text_node(n=nodes.inline, txt=non_bold_text)
|
||||
both = nodes.definition('', bold)
|
||||
both += non_bold
|
||||
return both
|
||||
|
||||
def item_to_node(item: Dict[str, Any]) -> nodes.definition_list_item:
|
||||
node = nodes.definition_list_item()
|
||||
term_node = text_node(nodes.term, "%s" % (item["code"]))
|
||||
definition_node = nodes.definition('', text_node(nodes.paragraph, ''))
|
||||
if item["deprecation"]:
|
||||
definition_node += build_indented_bold_and_non_bold_node(
|
||||
bold_text="Deprecated: ",
|
||||
non_bold_text=item['deprecation'])
|
||||
if item["explanation"]:
|
||||
definition_node += build_indented_bold_and_non_bold_node(
|
||||
bold_text="Explanation: ",
|
||||
non_bold_text=item['explanation'])
|
||||
definition_node += build_indented_bold_and_non_bold_node(
|
||||
bold_text="Category: ",
|
||||
non_bold_text=item['category'])
|
||||
if item["conveyance"]:
|
||||
definition_node += build_indented_bold_and_non_bold_node(
|
||||
bold_text="Conveyance: ",
|
||||
non_bold_text=item['conveyance'])
|
||||
if item["resolution"]:
|
||||
definition_node += build_indented_bold_and_non_bold_node(
|
||||
bold_text="Resolution: ",
|
||||
non_bold_text=item['resolution'])
|
||||
permalink_node = build_permalink(
|
||||
app=app,
|
||||
fromDocName=fromDocName,
|
||||
term=item["code"],
|
||||
# NOTE: This is the path to the docs file in Sphinx's source dir
|
||||
docname='error-codes/self-service/index',
|
||||
node_to_permalink_to=term_node)
|
||||
node += [permalink_node, definition_node]
|
||||
|
||||
return node
|
||||
|
||||
def group_explanation_to_node(text: str) -> nodes.definition_list_item:
|
||||
return text_node(nodes.paragraph, text)
|
||||
|
||||
# A node of this tree is a dict that can contain
|
||||
# 1. further nodes and/or
|
||||
# 2. 'leaves' in the form of a list of error (code) data
|
||||
# Thus, the resulting tree is very similar to a trie
|
||||
def build_hierarchical_tree_of_error_data(data) -> defaultdict:
|
||||
create_node = lambda: defaultdict(create_node)
|
||||
root = defaultdict(create_node)
|
||||
for error_data in data:
|
||||
current = root
|
||||
for grouping in error_data['hierarchicalGrouping']:
|
||||
current = current[grouping['docName']]
|
||||
current['explanation'] = group_data[grouping['className']]['explanation']
|
||||
if 'error-codes' in current:
|
||||
current['error-codes'].append(error_data)
|
||||
else:
|
||||
current['error-codes'] = [error_data]
|
||||
return root
|
||||
|
||||
# DFS to traverse the error code data tree from `build_hierarchical_tree_of_error_data`
|
||||
# While traversing the tree, the presentation of the error codes on the documentation is built
|
||||
def dfs(tree, node, numeric_prefix: str, topic_prefix: str) -> None:
|
||||
if 'explanation' in tree and tree['explanation']:
|
||||
node += group_explanation_to_node(tree['explanation'])
|
||||
if 'error-codes' in tree:
|
||||
dlist = nodes.definition_list()
|
||||
for code in tree['error-codes']:
|
||||
dlist += item_to_node(item=code)
|
||||
node += dlist
|
||||
i = 1
|
||||
for subtopic, subtree in tree.items():
|
||||
if subtopic in ['error-codes', 'explanation']:
|
||||
continue
|
||||
subtree_node_numeric_prefix = f"{numeric_prefix}{i}."
|
||||
i += 1
|
||||
topic = subtopic
|
||||
if topic_prefix != "":
|
||||
topic = topic_prefix + " / " + subtopic
|
||||
subtree_node_header = subtree_node_numeric_prefix + " " + topic
|
||||
subtree_node = text_node(n=nodes.rubric, txt = subtree_node_header)
|
||||
dfs(tree=subtree, node=subtree_node, numeric_prefix=subtree_node_numeric_prefix, topic_prefix=topic)
|
||||
node += subtree_node
|
||||
|
||||
for node in doctree.traverse(error_code_node):
|
||||
# Valid error codes given to the .. error-codes:: directive as argument
|
||||
# given_error_codes = [error_codes_data[code] for code in node.codes if code in error_codes_data]
|
||||
# Code for manually overwriting the explanation or resolution of an error code
|
||||
|
||||
section = nodes.section()
|
||||
root = nodes.rubric(rawsource = "", text = "")
|
||||
section += root
|
||||
tree = build_hierarchical_tree_of_error_data(data=error_codes_data.values())
|
||||
dfs(tree=tree, node=root, numeric_prefix="", topic_prefix="")
|
||||
node.replace_self(new=[section])
|
||||
|
||||
|
||||
# Build a permalink/anchor to a specific command/metric
|
||||
def build_permalink(app, fromDocName, term, docname, node_to_permalink_to):
|
||||
reference_node = nodes.reference('', '')
|
||||
reference_node['refuri'] = app.builder.get_relative_uri(fromDocName, docname) + '#' + term
|
||||
|
||||
reference_node += node_to_permalink_to
|
||||
|
||||
target_node = nodes.target('', '', ids=[term])
|
||||
node_to_permalink_to += target_node
|
||||
return reference_node
|
||||
|
||||
|
||||
class PatchedHTMLTranslator(HTMLTranslator):
|
||||
# We overwrite this method as otherwise an assertion fails whenever we create a reference whose parent is
|
||||
# not a TextElement. Concretely, this enables using method `build_return_type_node` for creating links from
|
||||
# return types to the appropriate scaladocs
|
||||
# Similar to solution from https://stackoverflow.com/a/61669375
|
||||
|
||||
def visit_reference(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
atts = {'class': 'reference'}
|
||||
if node.get('internal') or 'refuri' not in node:
|
||||
atts['class'] += ' internal'
|
||||
else:
|
||||
atts['class'] += ' external'
|
||||
if 'refuri' in node:
|
||||
atts['href'] = node['refuri'] or '#'
|
||||
if self.settings.cloak_email_addresses and \
|
||||
atts['href'].startswith('mailto:'):
|
||||
atts['href'] = self.cloak_mailto(atts['href'])
|
||||
self.in_mailto = 1
|
||||
else:
|
||||
assert 'refid' in node, \
|
||||
'References must have "refuri" or "refid" attribute.'
|
||||
atts['href'] = '#' + node['refid']
|
||||
if not isinstance(node.parent, nodes.TextElement):
|
||||
# ---------------------
|
||||
# Commenting out this assertion is the only change compared to Sphinx version 3.4.3
|
||||
# assert len(node) == 1 and isinstance(node[0], nodes.image)
|
||||
# ---------------------
|
||||
atts['class'] += ' image-reference'
|
||||
if 'reftitle' in node:
|
||||
atts['title'] = node['reftitle']
|
||||
if 'target' in node:
|
||||
atts['target'] = node['target']
|
||||
self.body.append(self.starttag(node, 'a', '', **atts))
|
||||
|
||||
if node.get('secnumber'):
|
||||
self.body.append(('%s' + self.secnumber_suffix) %
|
||||
'.'.join(map(str, node['secnumber'])))
|
||||
|
@ -126,14 +126,11 @@ genrule(
|
||||
srcs = [
|
||||
"README.rst",
|
||||
"//docs:theme",
|
||||
"//docs:sphinx_ext/self_service_error_codes_extension.py",
|
||||
"//docs:configs/html/conf.py",
|
||||
"//docs:configs/static/pygments_daml_lexer.py",
|
||||
"//docs:configs/static/typescript.py",
|
||||
"//docs:scripts/check-closing-quotes.sh",
|
||||
"//docs:scripts/check-closing-quotes.sh.allow",
|
||||
"//docs:generate-error-codes-json",
|
||||
"//docs:error_codes_export.json",
|
||||
],
|
||||
outs = ["html.tar.gz"],
|
||||
cmd = """
|
||||
@ -142,8 +139,6 @@ genrule(
|
||||
|
||||
mkdir -p build/docs/configs/html
|
||||
cp $(location //docs:configs/html/conf.py) build/docs/configs/html/conf.py
|
||||
mkdir -p build/docs/sphinx_ext
|
||||
cp $(location //docs:sphinx_ext/self_service_error_codes_extension.py) build/docs/sphinx_ext/self_service_error_codes_extension.py
|
||||
mkdir -p build/docs/configs/static
|
||||
cp $(location //docs:configs/static/pygments_daml_lexer.py) build/docs/configs/static/pygments_daml_lexer.py
|
||||
cp $(location //docs:configs/static/typescript.py) build/docs/configs/static/typescript.py
|
||||
@ -175,7 +170,7 @@ genrule(
|
||||
# the PDF documentation due to issues with the FreeSerif font in the
|
||||
# fontspec package. So, for now we ignore `FutureWarning`.
|
||||
SPHINX_BUILD_EXIT_CODE=0
|
||||
SPHINX_BUILD_OUTPUT=$$(../$(location @sphinx_nix//:bin/sphinx-build) -D error_codes_json_export=../$(location //docs:error_codes_export.json) -c docs/configs/html docs/source html 2>&1) || SPHINX_BUILD_EXIT_CODE=$$?
|
||||
SPHINX_BUILD_OUTPUT=$$(../$(location @sphinx_nix//:bin/sphinx-build) -c docs/configs/html docs/source html 2>&1) || SPHINX_BUILD_EXIT_CODE=$$?
|
||||
if [ "$$SPHINX_BUILD_EXIT_CODE" -ne 0 ]; then
|
||||
>&2 echo "## SPHINX-BUILD OUTPUT:"
|
||||
>&2 echo "$$SPHINX_BUILD_OUTPUT"
|
||||
|
@ -29,6 +29,26 @@ da_scala_binary(
|
||||
],
|
||||
)
|
||||
|
||||
da_scala_binary(
|
||||
name = "generate-docs-error-code-inventory-app",
|
||||
srcs = glob(["app/src/main/scala/**/*.scala"]),
|
||||
main_class = "com.daml.error.generator.app.ErrorCodeInventoryDocsGenApp",
|
||||
resources = glob(["src/main/resources/**/*"]),
|
||||
scala_deps = [
|
||||
"@maven//:io_circe_circe_core",
|
||||
"@maven//:org_typelevel_cats_core",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
runtime_deps = [
|
||||
# Add the KVErrors to the classpath so they can be picked up by the generator
|
||||
"//ledger/participant-state/kvutils",
|
||||
],
|
||||
deps = [
|
||||
"//ledger/error",
|
||||
"//ledger/error/generator:lib",
|
||||
],
|
||||
)
|
||||
|
||||
da_scala_library(
|
||||
name = "lib",
|
||||
srcs = glob(["lib/src/main/scala/**/*.scala"]),
|
||||
|
@ -0,0 +1,204 @@
|
||||
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.error.generator.app
|
||||
|
||||
import java.nio.file.{Files, Paths, StandardOpenOption}
|
||||
|
||||
import com.daml.error.{ErrorClass, Grouping}
|
||||
import com.daml.error.generator.{ErrorCodeDocumentationGenerator, ErrorDocItem, GroupDocItem}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
/** Generates error codes inventory as a reStructuredText
|
||||
*/
|
||||
object ErrorCodeInventoryDocsGenApp {
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val text = {
|
||||
val (errorDocItems, groupDocItems): (Seq[ErrorDocItem], Seq[GroupDocItem]) =
|
||||
new ErrorCodeDocumentationGenerator().getDocItems
|
||||
|
||||
val groupSegmentsToExplanationMap: Map[List[Grouping], Option[String]] =
|
||||
groupDocItems.map { groupDocItem: GroupDocItem =>
|
||||
groupDocItem.errorClass.groupings -> groupDocItem.explanation.map(_.explanation)
|
||||
}.toMap
|
||||
|
||||
val errorCodes: Seq[ErrorCodeValue] = errorDocItems.map { (errorDocItem: ErrorDocItem) =>
|
||||
ErrorCodeValue(
|
||||
category = errorDocItem.category,
|
||||
errorGroupPath = errorDocItem.hierarchicalGrouping,
|
||||
conveyance = errorDocItem.conveyance.getOrElse("").replace('\n', ' '),
|
||||
code = errorDocItem.code,
|
||||
deprecationO = errorDocItem.deprecation.map(_.deprecation.replace('\n', ' ')),
|
||||
explanation = errorDocItem.explanation.fold("")(_.explanation).replace('\n', ' '),
|
||||
resolution = errorDocItem.resolution.fold("")(_.resolution).replace('\n', ' '),
|
||||
)
|
||||
}
|
||||
|
||||
val root = ErrorGroupTree.empty()
|
||||
|
||||
// Build trie like structure of error groups and error codes.
|
||||
errorCodes.foreach(errorCode =>
|
||||
root.insertErrorCode(errorCode, groupSegmentsToExplanationMap)
|
||||
)
|
||||
// Traverse the trie to emit error code text.
|
||||
ErrorGroupTree
|
||||
.collectErrorCodesAsReStructuredTextSubsections(root)
|
||||
.mkString("\n\n")
|
||||
}
|
||||
|
||||
if (args.length >= 1) {
|
||||
val outputFile = Paths.get(args(0))
|
||||
val _ = Files.write(outputFile, text.getBytes, StandardOpenOption.CREATE_NEW)
|
||||
} else {
|
||||
println(text)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class ErrorCodeValue(
|
||||
code: String,
|
||||
errorGroupPath: ErrorClass,
|
||||
category: String,
|
||||
explanation: String,
|
||||
resolution: String,
|
||||
conveyance: String,
|
||||
deprecationO: Option[String],
|
||||
)
|
||||
|
||||
class ErrorGroupTree(
|
||||
val name: String,
|
||||
val explanation: Option[String] = None,
|
||||
children: mutable.Map[Grouping, ErrorGroupTree] =
|
||||
new mutable.HashMap[Grouping, ErrorGroupTree](),
|
||||
errorCodes: mutable.Map[String, ErrorCodeValue] = new mutable.HashMap[String, ErrorCodeValue](),
|
||||
) {
|
||||
|
||||
def sortedSubGroups(): List[ErrorGroupTree] = {
|
||||
children.values.toList.sortBy(_.name)
|
||||
}
|
||||
|
||||
def sortedErrorCodes(): List[ErrorCodeValue] = {
|
||||
errorCodes.values.toList.sortBy(_.code)
|
||||
}
|
||||
|
||||
def insertErrorCode(
|
||||
errorCode: ErrorCodeValue,
|
||||
getExplanation: (List[Grouping]) => Option[String],
|
||||
): Unit = {
|
||||
insert(
|
||||
remaining = errorCode.errorGroupPath.groupings,
|
||||
path = Nil,
|
||||
errorCode = errorCode,
|
||||
getExplanation = getExplanation,
|
||||
)
|
||||
}
|
||||
|
||||
private def insert(
|
||||
remaining: List[Grouping],
|
||||
errorCode: ErrorCodeValue,
|
||||
path: List[Grouping],
|
||||
getExplanation: (List[Grouping]) => Option[String],
|
||||
): Unit = {
|
||||
|
||||
remaining match {
|
||||
case Nil =>
|
||||
assert(!errorCodes.contains(errorCode.code), s"Code: ${errorCode.code} is already present!")
|
||||
errorCodes.put(errorCode.code, errorCode): Unit
|
||||
case headGroup :: tail =>
|
||||
val newPath = path :+ headGroup
|
||||
if (!children.contains(headGroup)) {
|
||||
children.put(
|
||||
headGroup,
|
||||
new ErrorGroupTree(
|
||||
name = headGroup.docName,
|
||||
explanation = getExplanation(newPath),
|
||||
),
|
||||
)
|
||||
}
|
||||
children(headGroup).insert(
|
||||
remaining = tail,
|
||||
errorCode = errorCode,
|
||||
path = newPath,
|
||||
getExplanation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ErrorGroupTree {
|
||||
def empty(): ErrorGroupTree = new ErrorGroupTree(
|
||||
name = "",
|
||||
explanation = None,
|
||||
)
|
||||
|
||||
def collectErrorCodesAsReStructuredTextSubsections(root: ErrorGroupTree): List[String] = {
|
||||
|
||||
// in-order tree traversal
|
||||
def iter(
|
||||
tree: ErrorGroupTree,
|
||||
path: List[String],
|
||||
groupHierarchicalIndex: List[Int],
|
||||
): List[String] = {
|
||||
val newPath = path :+ tree.name
|
||||
val textBuffer: mutable.ArrayBuffer[String] = new ArrayBuffer[String]()
|
||||
|
||||
// Add group text
|
||||
textBuffer.addOne(s"""${groupHierarchicalIndex.mkString(".")}. ${newPath.mkString(" / ")}
|
||||
|-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|${tree.explanation.getOrElse("")}
|
||||
|""".stripMargin)
|
||||
// Add error codes in this group
|
||||
textBuffer.addAll(
|
||||
tree
|
||||
.sortedErrorCodes()
|
||||
.map(handleErrorCode)
|
||||
)
|
||||
// Recurse to sub-groups
|
||||
textBuffer.addAll(
|
||||
tree
|
||||
.sortedSubGroups()
|
||||
.zipWithIndex
|
||||
.flatMap { case (subGroup: ErrorGroupTree, index: Int) =>
|
||||
iter(
|
||||
subGroup,
|
||||
newPath,
|
||||
groupHierarchicalIndex = groupHierarchicalIndex :+ (index + 1),
|
||||
)
|
||||
}
|
||||
)
|
||||
textBuffer.toList
|
||||
}
|
||||
|
||||
root
|
||||
.sortedSubGroups()
|
||||
.zipWithIndex
|
||||
.flatMap { case (subGroup, i) =>
|
||||
iter(subGroup, path = List(), groupHierarchicalIndex = List(i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
private def handleErrorCode(e: ErrorCodeValue): String = {
|
||||
val deprecationText = e.deprecationO.fold("")(d => s"""
|
||||
| **Deprecation**: ${d}
|
||||
| """.stripMargin)
|
||||
s"""${e.code}
|
||||
|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| $deprecationText
|
||||
| **Explanation**: ${e.explanation}
|
||||
|
|
||||
| **Category**: ${e.category}
|
||||
|
|
||||
| **Conveyance**: ${e.conveyance}
|
||||
|
|
||||
| **Resolution**: ${e.resolution}
|
||||
|
|
||||
|""".stripMargin
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ object Main {
|
||||
)(i =>
|
||||
(
|
||||
i.docName,
|
||||
i.group.map(_.fullClassName),
|
||||
i.fullClassName,
|
||||
)
|
||||
)
|
||||
|
||||
@ -41,12 +41,12 @@ object Main {
|
||||
(
|
||||
i.className,
|
||||
i.category,
|
||||
i.hierarchicalGrouping,
|
||||
i.hierarchicalGrouping.groupings,
|
||||
i.conveyance,
|
||||
i.code,
|
||||
i.deprecation.deprecation,
|
||||
i.explanation.explanation,
|
||||
i.resolution.resolution,
|
||||
i.deprecation.fold("")(_.deprecation),
|
||||
i.explanation.fold("")(_.explanation),
|
||||
i.resolution.fold("")(_.resolution),
|
||||
)
|
||||
)
|
||||
|
||||
@ -57,7 +57,7 @@ object Main {
|
||||
)(i =>
|
||||
(
|
||||
i.className,
|
||||
i.explanation.explanation,
|
||||
i.explanation.fold("")(_.explanation),
|
||||
)
|
||||
)
|
||||
|
||||
@ -66,10 +66,15 @@ object Main {
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val (errorCodes, groups) = new ErrorCodeDocumentationGenerator().getDocItems
|
||||
val outputFile = Paths.get(args(0))
|
||||
val output = Output(errorCodes, groups)
|
||||
val outputText: String = output.asJson.spaces2
|
||||
val outputBytes = outputText.getBytes
|
||||
val _ = Files.write(outputFile, outputBytes, StandardOpenOption.CREATE_NEW)
|
||||
|
||||
if (args.length >= 1) {
|
||||
val outputFile = Paths.get(args(0))
|
||||
Files.write(outputFile, outputText.getBytes, StandardOpenOption.CREATE_NEW): Unit
|
||||
} else {
|
||||
println(outputText)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -65,12 +65,12 @@ class ErrorCodeDocumentationGenerator(prefixes: Array[String] = Array("com.daml"
|
||||
ErrorDocItem(
|
||||
className = error.getClass.getName,
|
||||
category = simpleClassName(error.category),
|
||||
hierarchicalGrouping = error.parent.groupings.filter(_.docName.nonEmpty),
|
||||
conveyance = error.errorConveyanceDocString.getOrElse(""),
|
||||
hierarchicalGrouping = error.parent,
|
||||
conveyance = error.errorConveyanceDocString,
|
||||
code = error.id,
|
||||
deprecation = deprecation.getOrElse(Deprecation("")),
|
||||
explanation = explanation.getOrElse(Explanation("")),
|
||||
resolution = resolution.getOrElse(Resolution("")),
|
||||
deprecation = deprecation,
|
||||
explanation = explanation,
|
||||
resolution = resolution,
|
||||
)
|
||||
}
|
||||
|
||||
@ -79,8 +79,9 @@ class ErrorCodeDocumentationGenerator(prefixes: Array[String] = Array("com.daml"
|
||||
getGroupDocumentationAnnotations(group)
|
||||
|
||||
GroupDocItem(
|
||||
className = group.getClass.getName,
|
||||
explanation = explanation.getOrElse(Explanation("")),
|
||||
errorClass = group.errorClass,
|
||||
className = group.fullClassName,
|
||||
explanation = explanation,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.error.generator
|
||||
|
||||
import com.daml.error.{Deprecation, Explanation, Grouping, Resolution}
|
||||
import com.daml.error.{Deprecation, ErrorClass, Explanation, Resolution}
|
||||
|
||||
/** Contains error presentation data to be used for documentation rendering on the website.
|
||||
*
|
||||
@ -19,10 +19,10 @@ import com.daml.error.{Deprecation, Explanation, Grouping, Resolution}
|
||||
case class ErrorDocItem(
|
||||
className: String, // TODO error codes: Rename to `errorCodeName` or `errorCodeClassName` to prevent confusion
|
||||
category: String,
|
||||
hierarchicalGrouping: List[Grouping],
|
||||
conveyance: String,
|
||||
hierarchicalGrouping: ErrorClass,
|
||||
conveyance: Option[String],
|
||||
code: String,
|
||||
deprecation: Deprecation,
|
||||
explanation: Explanation,
|
||||
resolution: Resolution,
|
||||
deprecation: Option[Deprecation],
|
||||
explanation: Option[Explanation],
|
||||
resolution: Option[Resolution],
|
||||
)
|
||||
|
@ -3,14 +3,16 @@
|
||||
|
||||
package com.daml.error.generator
|
||||
|
||||
import com.daml.error.Explanation
|
||||
import com.daml.error.{ErrorClass, Explanation}
|
||||
|
||||
/** Contains error presentation data to be used for documentation rendering on the website.
|
||||
*
|
||||
* @param className The group class name (see [[com.daml.error.ErrorGroup]]).
|
||||
* @param explanation The detailed error explanation.
|
||||
* @param errorClass Hierarchical grouping of this error group.
|
||||
*/
|
||||
case class GroupDocItem(
|
||||
className: String,
|
||||
explanation: Explanation,
|
||||
explanation: Option[Explanation],
|
||||
errorClass: ErrorClass,
|
||||
)
|
||||
|
@ -3,18 +3,22 @@
|
||||
|
||||
package com.daml.error
|
||||
|
||||
/** A grouping of errors.
|
||||
/** A component of [[ErrorClass]]
|
||||
*
|
||||
* @param docName The name that will appear in the generated documentation for the grouping.
|
||||
* @param group If the grouping is defined by an [[ErrorGroup]], the associated instance.
|
||||
* @param fullClassName Full class name of the corresponding [[ErrorGroup]].
|
||||
*/
|
||||
case class Grouping(
|
||||
docName: String,
|
||||
group: Option[ErrorGroup],
|
||||
)
|
||||
fullClassName: String,
|
||||
) {
|
||||
require(
|
||||
docName.trim.nonEmpty,
|
||||
s"Grouping.docName must be non empty and must contain not only whitespace characters, but was: |${docName}|!",
|
||||
)
|
||||
}
|
||||
|
||||
/** The classes [[ErrorClass]] and [[ErrorGroup]] are used to hierarchically structure error codes (their
|
||||
* hierarchical structure affects how they are displayed on the website)
|
||||
/** Used to hierarchically structure error codes in the official documentation.
|
||||
*/
|
||||
case class ErrorClass(groupings: List[Grouping]) {
|
||||
def extend(grouping: Grouping): ErrorClass =
|
||||
|
@ -21,6 +21,6 @@ abstract class ErrorGroup()(implicit parent: ErrorClass) {
|
||||
s"Could not parse full class name: '${fullClassName}' for the error class name"
|
||||
)
|
||||
)
|
||||
parent.extend(Grouping(name, Some(this)))
|
||||
parent.extend(Grouping(docName = name, fullClassName = fullClassName))
|
||||
}
|
||||
}
|
||||
|
@ -11,29 +11,25 @@ class ErrorGroupSpec extends AnyFlatSpec with Matchers with BeforeAndAfter {
|
||||
|
||||
object ErrorGroupBar extends ErrorGroup()(ErrorClass.root())
|
||||
|
||||
object ErrorGroupsFoo {
|
||||
private implicit val errorClass: ErrorClass = ErrorClass.root()
|
||||
|
||||
object ErrorGroupFoo1 extends ErrorGroup() {
|
||||
object ErrorGroupFoo2 extends ErrorGroup() {
|
||||
object ErrorGroupFoo3 extends ErrorGroup()
|
||||
}
|
||||
object ErrorGroupFoo1 extends ErrorGroup()(ErrorClass.root()) {
|
||||
object ErrorGroupFoo2 extends ErrorGroup() {
|
||||
object ErrorGroupFoo3 extends ErrorGroup()
|
||||
}
|
||||
}
|
||||
|
||||
it should "resolve correct error group names" in {
|
||||
ErrorGroupsFoo.ErrorGroupFoo1.ErrorGroupFoo2.ErrorGroupFoo3.errorClass shouldBe ErrorClass(
|
||||
ErrorGroupFoo1.ErrorGroupFoo2.ErrorGroupFoo3.errorClass shouldBe ErrorClass(
|
||||
List(
|
||||
Grouping("ErrorGroupFoo1", Some(ErrorGroupsFoo.ErrorGroupFoo1)),
|
||||
Grouping("ErrorGroupFoo2", Some(ErrorGroupsFoo.ErrorGroupFoo1.ErrorGroupFoo2)),
|
||||
Grouping("ErrorGroupFoo1", ErrorGroupFoo1.fullClassName),
|
||||
Grouping("ErrorGroupFoo2", ErrorGroupFoo1.ErrorGroupFoo2.fullClassName),
|
||||
Grouping(
|
||||
"ErrorGroupFoo3",
|
||||
Some(ErrorGroupsFoo.ErrorGroupFoo1.ErrorGroupFoo2.ErrorGroupFoo3),
|
||||
ErrorGroupFoo1.ErrorGroupFoo2.ErrorGroupFoo3.fullClassName,
|
||||
),
|
||||
)
|
||||
)
|
||||
ErrorGroupBar.errorClass shouldBe ErrorClass(
|
||||
List(Grouping("ErrorGroupBar", Some(ErrorGroupBar)))
|
||||
List(Grouping("ErrorGroupBar", ErrorGroupBar.fullClassName))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,9 @@ import com.daml.logging.LoggingContext
|
||||
|
||||
@Explanation("Groups mild errors together")
|
||||
object MildErrors
|
||||
extends ErrorGroup()(parent = ErrorClass.root().extend(Grouping("Some grouping", None))) {
|
||||
extends ErrorGroup()(
|
||||
parent = ErrorClass.root().extend(Grouping("Some grouping", "full.class.Name123"))
|
||||
) {
|
||||
|
||||
@Explanation("Test: Things like this always happen.")
|
||||
@Resolution("Test: Why not ignore?")
|
||||
|
Loading…
Reference in New Issue
Block a user