1use {
7 crate::schema_md::render_sectioned_field_table,
8 krabka_broker::{
9 api_catalog,
10 api_catalog::{ClientMetricsReceiver, ListenerKind, VersionGates},
11 file_config,
12 },
13 std::fmt::Write,
14};
15
16#[must_use]
26pub fn server_config_md() -> String {
27 let value = file_config::config_schema();
28 let mut out = String::from(
29 "Krabka brokers are configured from a TOML file. Each section below maps \
30 to a top-level key or `[table]` of that file.\n\n",
31 );
32 out.push_str(&render_sectioned_field_table(&value));
33 out
34}
35
36#[must_use]
38pub fn topic_configs_md() -> String {
39 let mut out = String::from(
40 "These dynamic per-topic configs are accepted by `AlterConfigs` / \
41 `IncrementalAlterConfigs`. Unknown keys are rejected with `INVALID_CONFIG`.\n\n\
42 | Key | Type | Default | KIP | Description |\n\
43 |-----|------|---------|-----|-------------|\n",
44 );
45 for d in krabka_broker::topic_config_docs() {
46 let default = d.default.map_or_default(|x| format!("`{x}`"));
47 let kip = d.kip.unwrap_or("—");
48 let _ = writeln!(
49 out,
50 "| `{}` | {} | {} | {} | {} |",
51 d.key, d.value_type, default, kip, d.description
52 );
53 }
54 out
55}
56
57struct ApiSpec {
60 label: &'static str,
61 url: &'static str,
62}
63
64fn api_spec(api_key: i16) -> ApiSpec {
75 const PROTOCOL_GUIDE: ApiSpec = ApiSpec {
78 label: "Protocol guide",
79 url: "https://kafka.apache.org/43/design/protocol/",
80 };
81 const KIP_848: ApiSpec = ApiSpec {
82 label: "KIP-848",
83 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-848%3A+The+Next+Generation+of+the+Consumer+Rebalance+Protocol",
84 };
85 const KIP_932: ApiSpec = ApiSpec {
86 label: "KIP-932",
87 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-932%3A+Queues+for+Kafka",
88 };
89 const KIP_1071: ApiSpec = ApiSpec {
90 label: "KIP-1071",
91 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-1071%3A+Streams+Rebalance+Protocol",
92 };
93 const KIP_595: ApiSpec = ApiSpec {
94 label: "KIP-595",
95 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-595%3A+A+Raft+Protocol+for+the+Metadata+Quorum",
96 };
97 const KIP_630: ApiSpec = ApiSpec {
98 label: "KIP-630",
99 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-630%3A+Kafka+Raft+Snapshot",
100 };
101 const KIP_853: ApiSpec = ApiSpec {
102 label: "KIP-853",
103 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-853%3A+KRaft+Controller+Membership+Changes",
104 };
105 const KIP_98: ApiSpec = ApiSpec {
106 label: "KIP-98",
107 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-98+-+Exactly+Once+Delivery+and+Transactional+Messaging",
108 };
109 const KIP_500: ApiSpec = ApiSpec {
110 label: "KIP-500",
111 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum",
112 };
113 const KIP_631: ApiSpec = ApiSpec {
114 label: "KIP-631",
115 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-631%3A+The+Quorum-based+Kafka+Controller",
116 };
117 const KIP_714: ApiSpec = ApiSpec {
118 label: "KIP-714",
119 url: "https://cwiki.apache.org/confluence/display/KAFKA/KIP-714%3A+Client+metrics+and+observability",
120 };
121
122 match api_key {
127 22 | 24..=28 => KIP_98,
130 52..=56 => KIP_595,
133 59 => KIP_630,
135 62..=64 | 70 => KIP_631,
137 68 | 69 => KIP_848,
139 71 | 72 => KIP_714,
141 76..=79 | 83..=87 | 90..=92 => KIP_932,
143 80..=82 => KIP_853,
145 88 | 89 => KIP_1071,
147 1020 => KIP_500,
150 _ => PROTOCOL_GUIDE,
151 }
152}
153
154#[must_use]
163pub fn protocol_apis_md() -> String {
164 use krabka_protocol::api_key::ApiKey;
165 let mut out = String::from(
166 "The Kafka protocol APIs this broker advertises to a client in its \
167 `ApiVersions` (key 18) response, with the supported version range for \
168 each. The wire protocol is specified by the [Apache Kafka protocol \
169 guide](https://kafka.apache.org/protocol); the **Spec** column links \
170 each API to the KIP (or protocol-guide section) that defines it.\n\n\
171 | API Key | Name | Min Version | Max Version | Spec |\n\
172 |---------|------|-------------|-------------|------|\n",
173 );
174 let mut apis = api_catalog::supported_apis(
175 ListenerKind::Client,
176 ClientMetricsReceiver::Configured,
177 VersionGates::default(),
178 );
179 apis.sort_by_key(|a| a.api_key);
180 for a in apis {
181 let name: &'static str = ApiKey::from_i16(a.api_key).map_or("?", <&'static str>::from);
182 let ApiSpec { label, url } = api_spec(a.api_key);
183 let _ = writeln!(
184 out,
185 "| {} | {} | {} | {} | [{}]({}) |",
186 a.api_key, name, a.min_version, a.max_version, label, url
187 );
188 }
189 out
190}
191
192#[cfg(test)]
193mod tests {
194 use {super::*, assert2::check};
195 #[test]
196 fn server_config_page_has_sectioned_field_tables() {
197 let md = server_config_md();
198 for needle in [
201 "| Field | Type | Required | Default | Description |",
202 "## ",
203 "\n---\n",
204 "## tls_config",
205 ] {
206 assert2::assert!(md.contains(needle));
207 }
208 }
209 #[test]
210 fn topic_configs_page_lists_retention_ms() {
211 let md = topic_configs_md();
212 assert2::assert!(md.contains("`retention.ms`"));
213 assert2::assert!(md.contains("| Key | Type | Default | KIP | Description |"));
214 }
215 #[test]
216 fn protocol_apis_page_lists_named_apis() {
217 let md = protocol_apis_md();
218 check!(md.contains("| API Key | Name | Min Version | Max Version | Spec |"));
219 check!(md.contains("| 18 | ApiVersions |"));
220 check!(!md.contains("| ? |"));
221 }
222 #[test]
223 fn protocol_apis_page_links_canonical_spec_and_kips() {
224 let md = protocol_apis_md();
225 for needle in [
226 "https://kafka.apache.org/protocol",
228 "[KIP-",
230 "cwiki.apache.org/confluence/display/KAFKA/KIP-",
231 ] {
232 assert2::assert!(md.contains(needle));
233 }
234 let row = md
236 .lines()
237 .find(|l| l.contains("| 68 |"))
238 .expect("ConsumerGroupHeartbeat row");
239 assert2::assert!(row.contains("[KIP-848]"));
240 let share = md
242 .lines()
243 .find(|l| l.contains("| 78 |"))
244 .expect("ShareFetch row");
245 assert2::assert!(share.contains("[KIP-932]"));
246 }
247}