Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/docs/api
diff options
context:
space:
mode:
authorLeitwolf112026-10-03 18:46:59 +0300
committerLeitwolf112026-10-03 18:46:59 +0300
commitb84a2216c26cc5392d0dc300b7a605ccb288d8c8 (patch)
tree048ae623f49cc5f2f94234d747351f9e659f5267 /docs/api
parentec774db15976385a6d5ea6f48d3622fe507d3843 (diff)
parentcf444eb3bccf99c10f59ed5330b61c0397e0aeaa (diff)
downloadprogmaweb-fix/api-without-google.tar.gz
Merge remote-tracking branch 'origin/main' into fix/api-without-googlefix/api-without-google
Diffstat (limited to 'docs/api')
-rw-r--r--docs/api/CheckDocumentation.cs118
-rw-r--r--docs/api/docfx.json22
-rw-r--r--docs/api/filterConfig.yml14
-rw-r--r--docs/api/index.md23
-rw-r--r--docs/api/namespaces/Progmasoft.Progmaweb.Api.Accounts.md10
-rw-r--r--docs/api/namespaces/Progmasoft.Progmaweb.Api.Security.md10
6 files changed, 191 insertions, 6 deletions
diff --git a/docs/api/CheckDocumentation.cs b/docs/api/CheckDocumentation.cs
new file mode 100644
index 0000000..f359661
--- /dev/null
+++ b/docs/api/CheckDocumentation.cs
@@ -0,0 +1,118 @@
+// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+// Documentation coverage gate of the API reference.
+//
+// DocFX builds a reference from whatever comments exist and does not report a declaration that has none. This
+// program reads the metadata DocFX generated and fails when a namespace, type or member is undocumented, so the
+// reference cannot contain an entry without a description.
+//
+// Usage, after `dotnet docfx metadata docs/api/docfx.json`:
+//
+// dotnet run docs/api/CheckDocumentation.cs -- docs/api
+//
+// The exit code is 0 when everything is documented, 1 when something is not, and 2 when the metadata is missing.
+
+using System.Text.RegularExpressions;
+
+string root = args.Length > 0 ? args[0] : "docs/api";
+string referenceDirectory = Path.Combine(root, "reference");
+string namespaceDirectory = Path.Combine(root, "namespaces");
+
+if (!Directory.Exists(referenceDirectory))
+{
+ Console.Error.WriteLine($"No metadata in '{referenceDirectory}'. Run 'dotnet docfx metadata' first.");
+ return 2;
+}
+
+// A namespace has no declaration that could carry a comment. Its description is an overwrite file named after it.
+HashSet<string> describedNamespaces = Directory.Exists(namespaceDirectory)
+ ? Directory.EnumerateFiles(namespaceDirectory, "*.md")
+ .Where(HasDescription)
+ .Select(path => Path.GetFileNameWithoutExtension(path)!)
+ .ToHashSet(StringComparer.Ordinal)
+ : [];
+
+List<string> undocumented = [];
+int checkedItems = 0;
+
+foreach (string file in Directory.EnumerateFiles(referenceDirectory, "*.yml").Order(StringComparer.Ordinal))
+{
+ if (Path.GetFileName(file) == "toc.yml")
+ {
+ continue;
+ }
+
+ // The items a file defines come first; the `references` section after them repeats names of other files.
+ string text = File.ReadAllText(file).ReplaceLineEndings("\n");
+ int references = text.IndexOf("\nreferences:", StringComparison.Ordinal);
+ string definitions = references < 0 ? text : text[..references];
+
+ foreach (string item in ItemStart().Split(definitions).Skip(1))
+ {
+ string uid = item[..item.IndexOf('\n')].Trim();
+ string type = ItemType().Match(item) is { Success: true } match ? match.Groups[1].Value : "Item";
+ checkedItems++;
+
+ bool documented = type == "Namespace"
+ ? describedNamespaces.Contains(uid)
+ : ItemSummary().IsMatch(item);
+ if (!documented)
+ {
+ undocumented.Add($"{type} {uid}");
+ }
+ }
+}
+
+if (checkedItems == 0)
+{
+ Console.Error.WriteLine($"The metadata in '{referenceDirectory}' defines nothing; the gate would pass vacuously.");
+ return 2;
+}
+
+if (undocumented.Count != 0)
+{
+ Console.Error.WriteLine($"{undocumented.Count} of {checkedItems} API items have no documentation:");
+ foreach (string entry in undocumented)
+ {
+ Console.Error.WriteLine($" {entry}");
+ }
+
+ Console.Error.WriteLine("Add a <summary> to each declaration, or a file in docs/api/namespaces for a namespace.");
+ return 1;
+}
+
+Console.WriteLine($"All {checkedItems} API items are documented.");
+return 0;
+
+// An overwrite file describes its namespace when it has text after its front matter and comment header.
+static bool HasDescription(string path)
+{
+ string text = File.ReadAllText(path).ReplaceLineEndings("\n");
+ int frontMatterEnd = text.IndexOf("\n---", 3, StringComparison.Ordinal);
+ if (!text.StartsWith("---\n", StringComparison.Ordinal) || frontMatterEnd < 0)
+ {
+ return false;
+ }
+
+ string body = HtmlComment().Replace(text[(frontMatterEnd + 4)..], string.Empty);
+ return text[..frontMatterEnd].Contains($"uid: {Path.GetFileNameWithoutExtension(path)}", StringComparison.Ordinal)
+ && !string.IsNullOrWhiteSpace(body);
+}
+
+internal static partial class Program
+{
+ // Every defined item starts with its identifier at the left margin.
+ [GeneratedRegex(@"^- uid: ", RegexOptions.Multiline)]
+ private static partial Regex ItemStart();
+
+ [GeneratedRegex(@"^ type: (\w+)", RegexOptions.Multiline)]
+ private static partial Regex ItemType();
+
+ // A summary with content: text on the same line, or a block scalar followed by an indented line.
+ [GeneratedRegex(@"^ summary: (?:[^\s>|].*|[>|][-+]?\n \S)", RegexOptions.Multiline)]
+ private static partial Regex ItemSummary();
+
+ [GeneratedRegex(@"<!--.*?-->", RegexOptions.Singleline)]
+ private static partial Regex HtmlComment();
+}
diff --git a/docs/api/docfx.json b/docs/api/docfx.json
index e10a9b0..eb2ffe7 100644
--- a/docs/api/docfx.json
+++ b/docs/api/docfx.json
@@ -4,23 +4,37 @@
"src": [
{
"src": "../../apps/api",
- "files": ["Progmaweb.Api.csproj"]
+ "files": [
+ "Progmaweb.Api.csproj"
+ ]
}
],
"dest": "reference",
- "includePrivateMembers": true
+ "includePrivateMembers": true,
+ "filter": "filterConfig.yml"
}
],
"build": {
"content": [
{
- "files": ["index.md", "toc.yml", "reference/**/*.yml"]
+ "files": [
+ "index.md",
+ "toc.yml",
+ "reference/**/*.yml"
+ ]
}
],
"dest": "_site",
"globalMetadata": {
"_appTitle": "Progmaweb API",
"_enableSearch": false
- }
+ },
+ "overwrite": [
+ {
+ "files": [
+ "namespaces/*.md"
+ ]
+ }
+ ]
}
}
diff --git a/docs/api/filterConfig.yml b/docs/api/filterConfig.yml
new file mode 100644
index 0000000..5b7b327
--- /dev/null
+++ b/docs/api/filterConfig.yml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+# Code that a source generator writes at build time is not part of the
+# reference: nobody maintains it, and it cannot carry documentation.
+apiRules:
+ - exclude:
+ uidRegex: ^System\.Text\.RegularExpressions\.Generated
+ # The regular-expression generator also writes the body of this partial
+ # method, and its generated comment replaces the one on the declaration, so
+ # the summary in the source never reaches the reference.
+ - exclude:
+ uidRegex: ^Progmasoft\.Progmaweb\.Api\.Accounts\.AccountNamePolicy\.CanonicalPattern$
+ type: Method
diff --git a/docs/api/index.md b/docs/api/index.md
index 868fff1..71b532b 100644
--- a/docs/api/index.md
+++ b/docs/api/index.md
@@ -12,8 +12,27 @@ The service is an application, not a library, so most of its types are
internal. The reference includes them: it documents the code for the people
who maintain it.
-Build it from the repository root:
+Build and check it from the repository root:
```sh
-docfx docs/api/docfx.json --warningsAsErrors
+dotnet tool restore
+dotnet docfx docs/api/docfx.json --warningsAsErrors
+dotnet run docs/api/CheckDocumentation.cs -- docs/api
```
+
+## What the gate requires
+
+Three things fail the check, locally and in CI:
+
+- **A broken link or an unresolved reference.** DocFX runs with every warning treated as an error.
+- **An undocumented public declaration or a wrong `cref`.** The API project compiles its XML documentation, and the
+ build treats the compiler's documentation warnings as errors.
+- **Any undocumented item of the reference.** `CheckDocumentation.cs` reads the metadata DocFX generated and fails
+ when a namespace, type or member has no summary. This covers internal and private members too, which the compiler
+ does not check.
+
+A namespace has no declaration that could hold a comment. Its description is a file in `namespaces/`, named after
+the namespace.
+
+`filterConfig.yml` lists what is left out of the reference: code written by a source generator, which nobody
+maintains and which cannot carry documentation. Do not add an entry there to get past the gate; write the summary.
diff --git a/docs/api/namespaces/Progmasoft.Progmaweb.Api.Accounts.md b/docs/api/namespaces/Progmasoft.Progmaweb.Api.Accounts.md
new file mode 100644
index 0000000..926cd64
--- /dev/null
+++ b/docs/api/namespaces/Progmasoft.Progmaweb.Api.Accounts.md
@@ -0,0 +1,10 @@
+---
+uid: Progmasoft.Progmaweb.Api.Accounts
+summary: *content
+---
+
+<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> -->
+<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 -->
+
+Accounts and sessions: the HTTP endpoints, the rules for account names, email addresses and passwords, the
+registration and sign-in logic, and the store that keeps accounts and session digests.
diff --git a/docs/api/namespaces/Progmasoft.Progmaweb.Api.Security.md b/docs/api/namespaces/Progmasoft.Progmaweb.Api.Security.md
new file mode 100644
index 0000000..c4cf45a
--- /dev/null
+++ b/docs/api/namespaces/Progmasoft.Progmaweb.Api.Security.md
@@ -0,0 +1,10 @@
+---
+uid: Progmasoft.Progmaweb.Api.Security
+summary: *content
+---
+
+<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> -->
+<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 -->
+
+Request-pipeline policy that is not specific to accounts: which forwarded headers the service trusts and which
+security headers every response carries.