From 385a600e98ce6dae6266e0c3b95881734336d026 Mon Sep 17 00:00:00 2001 From: Vincent Botbol Date: Wed, 7 Mar 2018 10:32:53 +0100 Subject: [PATCH] Docs: add error documentation generation --- .gitignore | 3 +- docs/Makefile | 11 +- docs/doc_gen/errors/error_doc.ml | 204 +++++++++++++++++++++ docs/doc_gen/errors/jbuild | 20 ++ docs/index.rst | 1 + src/proto_alpha/lib_protocol/src/baking.ml | 9 +- 6 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 docs/doc_gen/errors/error_doc.ml create mode 100644 docs/doc_gen/errors/jbuild diff --git a/.gitignore b/.gitignore index 88c3ed1f4..9a9c7fd37 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ __pycache__ /scripts/opam-test-all.sh.DONE /docs/introduction/readme.rst +/docs/api/errors.rst +/docs/_extensions/*.pyc *.install .merlin @@ -30,4 +32,3 @@ __pycache__ *.rej *.orig - diff --git a/docs/Makefile b/docs/Makefile index 877094f99..1eaa81055 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -5,18 +5,25 @@ SPHINXPROJ = Tezos SOURCEDIR = . BUILDDIR = _build +DOCGENDIR = doc_gen +DOCERRORDIR = $(DOCGENDIR)/errors + all: html linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck "$(SOURCEDIR)" "$(BUILDDIR)" +api/errors.rst: $(DOCERRORDIR)/error_doc.ml + @jbuilder build $(DOCERRORDIR)/error_doc.exe + ../_build/default/docs/$(DOCERRORDIR)/error_doc.exe > api/errors.rst + .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -html: Makefile +html: Makefile api/errors.rst @$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) clean: @-rm -Rf "$(BUILDDIR)" - @-rm -Rf introduction/readme.rst + @-rm -Rf introduction/readme.rst api/errors.rst diff --git a/docs/doc_gen/errors/error_doc.ml b/docs/doc_gen/errors/error_doc.ml new file mode 100644 index 000000000..d30ffe014 --- /dev/null +++ b/docs/doc_gen/errors/error_doc.ml @@ -0,0 +1,204 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2018. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +(* Todo : add section descriptions *) + +let default_section_id = "default" +let default_section_title = "Miscellaneous" + +(* Association list where keys are set of identifier's prefixes that + maps to a section title. The ordering of sections in the rst output + depends on their position in this list. + + e.g. : an error which id is 'utils.Timeout' will be documented + under the `Miscellaneous` section which will be displayed at the + bottom of the document. Unprefixed ids or unreferenced prefixes + will default to `Miscellaneous` *) +let section_titles = + [ + [ "proto" ], "Protocol Alpha"; + [ "baking" ], "Baking" ; + [ "contract" ], "Smart Contracts" ; + [ "distributed_db" ], "Database" ; + [ "micheline" ; "michelson" ], "Smart Contracts" ; + (* [ "michelson" ], "Michelson" ; *) + [ "node" ], "Client Node" ; + [ "operation" ], "Operations" ; + [ "prevalidation" ], "Prevalidation" ; + [ "raw_store" ], "Store" ; + [ "rpc_client" ], "RPC Client" ; + [ "tez" ], "Tezos operations" ; + [ "validator" ], "Validator" ; + [ "worker" ], "Worker" ; + (* [ "cli" ], "Command Line" ; *) + [ "cli"; "utils"; default_section_id ], default_section_title ; + ] + +let categories_detail = + [ "temporary", "An error resulting from an operation that might be \ + valid in the future, for example, a contract’s balance \ + being too low to execute the intended operation. This \ + can be fixed by adding more to the contract’s balance." + ; "branch", "An error that occurs in one branch of the chain, but may not \ + occur in a different one. For example, receiving an \ + operation for an old or future protocol version." + ; "permanent", "An error that is not recoverable because the operation \ + is never going to be valid. For example, an invalid ꜩ \ + notation." ] + +let pp_rst_title ~char ppf title = + let sub = String.map (fun _ -> char) title in + Format.fprintf ppf "@[%s@\n@]@[%s@\n@\n@]" title sub + +let pp_rst_h1 = pp_rst_title ~char:'#' +let pp_rst_h2 = pp_rst_title ~char:'*' +let pp_rst_h3 = pp_rst_title ~char:'=' +let pp_rst_h4 = pp_rst_title ~char:'`' + +let string_of_err_category = + let open Error_monad in function + | `Branch -> "branch" + | `Temporary -> "temporary" + | `Permanent -> "permanent" + +let pp_info_to_rst + ppf + { Error_monad.id ; title ; category ; description ; schema } = + let open Format in + + fprintf ppf "@[- **%s**@,@," + (if title = "" then "" else title) ; + + fprintf ppf "@[%s@\n@\n@]" + (if description = "" then "Not description available" else description) ; + + fprintf ppf "@[* *Id* : %s@\n@\n@]" id ; + + fprintf ppf "@[* *Category* : %s@\n@\n@]" (string_of_err_category category) ; + + fprintf ppf "@[.. container:: schema-button@\n@\n" ; + fprintf ppf "@[Show schema@]@]@\n@\n" ; + + fprintf ppf "@[.. container:: schema@\n@\n" ; + fprintf ppf "@[.. code-block:: json@\n@\n" ; + + fprintf ppf "@[%a@]@]@]@]" Json_schema.pp schema + +module ErrorSet = Set.Make(struct + type t = Error_monad.error_info + let compare { Error_monad.id ; _ } { Error_monad.id = id' ; _ } = + String.compare id id' + end) + +module ErrorPartition = struct + include Map.Make(struct + include String + let titles = List.map snd section_titles + + let compare s s' = + let idx s = + let rec loop acc = function + | [] -> assert false + | h::_ when h = s -> acc + | _::t -> loop (acc + 1) t + in loop 0 titles + in + Pervasives.compare (idx s) (idx s') + end) + + let add_error (id : key) (error : Error_monad.error_info) (map : 'a t) = + let lr_opt = Stringext.cut id ~on:"." in + + let id_prefix = + match lr_opt with + | None -> default_section_id + | Some (prefix, _r) -> prefix + in + + let title = + try + snd (List.find + (fun (id_set, _) -> List.mem id_prefix id_set) + section_titles) + with + | Not_found -> default_section_title + in + + let set = + try + find title map + with + | Not_found -> ErrorSet.empty + in + + add title (ErrorSet.add error set) map + +end + +let pp_error_map ppf (map : ErrorSet.t ErrorPartition.t) : unit = + let open Format in + ErrorPartition.iter (fun section_title set -> + fprintf ppf "%a" pp_rst_h2 section_title ; + + ErrorSet.iter + (fun error_repr -> + fprintf ppf "@[%a@]@\n@\n" pp_info_to_rst error_repr + ) set + ) map + +let print_script ppf = + (* HACK : show/hide JSON schemas + style *) + Format.fprintf ppf "@[.. raw:: html@\n@\n" ; + Format.fprintf ppf "@[%s%s@]@\n@\n@]" + "" + "" + +(* Main *) +let () = + let open Format in + let ppf = std_formatter in + + (* Header *) + let title = "Tezos Client Errors" in + fprintf ppf "%a" pp_rst_h1 title ; + + print_script ppf ; + + fprintf ppf "This document references possible errors.@\n@\n" ; + + fprintf ppf "There are three categories of error :@\n@\n" ; + + List.iter (fun (cat, descr) -> + fprintf ppf "- :literal:`%s` - %s@\n@\n" cat descr) categories_detail ; + + fprintf ppf "See `The Error Monad`_ for further details.@\n@\n" ; + fprintf + ppf ".. _The Error Monad: \ + ../tutorials/error_monad.html#the-actual-tezos-error-monad@\n@\n" ; + + (* Body *) + let map = + let all_errors = + Error_monad.get_registered_errors () in + List.fold_left + (fun acc ( Error_monad.{ id ; _ } as error ) -> + ErrorPartition.add_error id error acc + ) ErrorPartition.empty all_errors + in + + fprintf ppf "%a" pp_error_map map diff --git a/docs/doc_gen/errors/jbuild b/docs/doc_gen/errors/jbuild new file mode 100644 index 000000000..cbc469f98 --- /dev/null +++ b/docs/doc_gen/errors/jbuild @@ -0,0 +1,20 @@ +(jbuild_version 1) + +(executable + ((name error_doc) + (libraries + (tezos-shell + tezos-embedded-protocol-alpha)) + (flags (:standard -w -9+27-30-32-40@8 + -open Tezos_base + -open Tezos_error_monad + -open Tezos_data_encoding + -open Tezos_embedded_protocol_alpha + -open Tezos_embedded_protocol_environment_alpha + -safe-string + -linkall)))) + +(alias + ((name runtest_indent) + (deps ((glob_files *.ml*))) + (action (run bash ${libexec:tezos-stdlib:test-ocp-indent.sh} ${^})))) diff --git a/docs/index.rst b/docs/index.rst index b5eeb5216..626a408bf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -83,6 +83,7 @@ license when the main network lunches. README api/api-inline api/cli-commands + api/errors Indices and tables ================== diff --git a/src/proto_alpha/lib_protocol/src/baking.ml b/src/proto_alpha/lib_protocol/src/baking.ml index 1a5722d10..e786ca6ea 100644 --- a/src/proto_alpha/lib_protocol/src/baking.ml +++ b/src/proto_alpha/lib_protocol/src/baking.ml @@ -19,6 +19,8 @@ type error += Cannot_freeze_endorsement_deposit (* `Permanent *) type error += Inconsistent_endorsement of public_key_hash list (* `Permanent *) type error += Empty_endorsement type error += Invalid_block_signature of Block_hash.t * Ed25519.Public_key_hash.t (* `Permanent *) +type error += Invalid_signature (* `Permanent *) +type error += Invalid_stamp (* `Permanent *) let () = register_error_kind @@ -108,7 +110,9 @@ let () = (req "block" Block_hash.encoding) (req "expected" Ed25519.Public_key_hash.encoding)) (function Invalid_block_signature (block, pkh) -> Some (block, pkh) | _ -> None) - (fun (block, pkh) -> Invalid_block_signature (block, pkh)) + (fun (block, pkh) -> Invalid_block_signature (block, pkh)); + register_error_kind + `Permanent ~id:"baking.invalid_signature" ~title:"Invalid block signature" ~description:"The block's signature is invalid" @@ -251,9 +255,6 @@ let check_header_hash header stamp_threshold = let hash = Block_header.hash header in check_hash hash stamp_threshold -type error += - | Invalid_stamp - let check_proof_of_work_stamp ctxt block = let proof_of_work_threshold = Constants.proof_of_work_threshold ctxt in if check_header_hash block proof_of_work_threshold then