@redpanda-data/docs-extensions-and-macros 4.11.0 β 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/doc-tools.js +4 -2
- package/extensions/convert-to-markdown.js +17 -1
- package/package.json +3 -1
- package/tools/property-extractor/COMPUTED_CONSTANTS.md +173 -0
- package/tools/property-extractor/Makefile +12 -1
- package/tools/property-extractor/README.adoc +828 -97
- package/tools/property-extractor/compare-properties.js +38 -13
- package/tools/property-extractor/constant_resolver.py +610 -0
- package/tools/property-extractor/file_pair.py +42 -0
- package/tools/property-extractor/generate-handlebars-docs.js +41 -8
- package/tools/property-extractor/helpers/gt.js +9 -0
- package/tools/property-extractor/helpers/includes.js +17 -0
- package/tools/property-extractor/helpers/index.js +3 -0
- package/tools/property-extractor/helpers/isEnterpriseEnum.js +24 -0
- package/tools/property-extractor/helpers/renderPropertyExample.js +6 -5
- package/tools/property-extractor/overrides.json +248 -0
- package/tools/property-extractor/parser.py +254 -32
- package/tools/property-extractor/property_bag.py +40 -0
- package/tools/property-extractor/property_extractor.py +1417 -430
- package/tools/property-extractor/requirements.txt +1 -0
- package/tools/property-extractor/templates/property-backup.hbs +161 -0
- package/tools/property-extractor/templates/property.hbs +104 -49
- package/tools/property-extractor/templates/topic-property-backup.hbs +148 -0
- package/tools/property-extractor/templates/topic-property.hbs +72 -34
- package/tools/property-extractor/tests/test_known_values.py +617 -0
- package/tools/property-extractor/tests/transformers_test.py +81 -6
- package/tools/property-extractor/topic_property_extractor.py +23 -10
- package/tools/property-extractor/transformers.py +2191 -369
- package/tools/property-extractor/type_definition_extractor.py +669 -0
- package/tools/property-extractor/definitions.json +0 -245
|
@@ -105,7 +105,17 @@ function registerPartials() {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
* Generate
|
|
108
|
+
* Generate AsciiDoc partial files grouping input properties by scope (cluster, topic, broker, object-storage).
|
|
109
|
+
*
|
|
110
|
+
* Reads property and topic templates, groups provided properties by their config_scope (treating keys as authoritative property names),
|
|
111
|
+
* renders each property into the appropriate template, writes combined partial files to "<partialsDir>/properties/<type>-properties.adoc",
|
|
112
|
+
* and invokes the optional onRender callback for every rendered property name. Entries missing a name or config_scope are skipped;
|
|
113
|
+
* duplicate keys are detected, warned about, and skipped.
|
|
114
|
+
*
|
|
115
|
+
* @param {Object<string, Object>} properties - Map of property key β property object; the map key is used as the property's name.
|
|
116
|
+
* @param {string} partialsDir - Destination directory under which a "properties" subdirectory will be created for output files.
|
|
117
|
+
* @param {(name: string) => void} [onRender] - Optional callback invoked with each rendered property's name.
|
|
118
|
+
* @returns {number} The total number of properties rendered and written to partial files.
|
|
109
119
|
*/
|
|
110
120
|
function generatePropertyPartials(properties, partialsDir, onRender) {
|
|
111
121
|
console.log(`π Generating consolidated property partials in ${partialsDir}β¦`);
|
|
@@ -122,9 +132,23 @@ function generatePropertyPartials(properties, partialsDir, onRender) {
|
|
|
122
132
|
|
|
123
133
|
const propertyGroups = { cluster: [], topic: [], broker: [], 'object-storage': [] };
|
|
124
134
|
|
|
125
|
-
|
|
135
|
+
// Track processed property keys to detect duplicates by unique key
|
|
136
|
+
const processedKeys = new Set();
|
|
137
|
+
|
|
138
|
+
Object.entries(properties).forEach(([key, prop]) => {
|
|
126
139
|
if (!prop.name || !prop.config_scope) return;
|
|
127
140
|
|
|
141
|
+
// Skip if we've already processed this key
|
|
142
|
+
if (processedKeys.has(key)) {
|
|
143
|
+
console.warn(`β οΈ Duplicate key detected: ${key}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
processedKeys.add(key);
|
|
147
|
+
|
|
148
|
+
// Ensure the property uses the key as its name for consistency
|
|
149
|
+
// This fixes issues where key != name field due to bugs in the source code
|
|
150
|
+
prop.name = key;
|
|
151
|
+
|
|
128
152
|
switch (prop.config_scope) {
|
|
129
153
|
case 'topic':
|
|
130
154
|
propertyGroups.topic.push(prop);
|
|
@@ -243,19 +267,28 @@ function generateErrorReports(properties, documentedProperties = []) {
|
|
|
243
267
|
const documentedSet = new Set(documentedProperties);
|
|
244
268
|
const undocumented = [];
|
|
245
269
|
|
|
246
|
-
|
|
270
|
+
Object.entries(properties).forEach(([key, p]) => {
|
|
247
271
|
const name = p.name || key;
|
|
248
|
-
|
|
272
|
+
const desc = p.description;
|
|
273
|
+
|
|
249
274
|
if (p.is_deprecated) deprecatedProperties.push(name);
|
|
275
|
+
|
|
276
|
+
// Ensure description is a non-empty string (exclude deprecated properties)
|
|
277
|
+
if (!p.is_deprecated && (typeof desc !== 'string' || desc.trim().length === 0)) {
|
|
278
|
+
emptyDescriptions.push(name);
|
|
279
|
+
}
|
|
280
|
+
|
|
250
281
|
if (!documentedSet.has(name)) undocumented.push(name);
|
|
251
282
|
});
|
|
252
283
|
|
|
284
|
+
|
|
253
285
|
const total = allKeys.length;
|
|
254
|
-
const
|
|
286
|
+
const nonDeprecatedTotal = total - deprecatedProperties.length;
|
|
287
|
+
const pctEmpty = nonDeprecatedTotal ? ((emptyDescriptions.length / nonDeprecatedTotal) * 100).toFixed(2) : '0.00';
|
|
255
288
|
const pctDeprecated = total ? ((deprecatedProperties.length / total) * 100).toFixed(2) : '0.00';
|
|
256
289
|
const pctUndocumented = total ? ((undocumented.length / total) * 100).toFixed(2) : '0.00';
|
|
257
290
|
|
|
258
|
-
console.log(`π Empty descriptions: ${emptyDescriptions.length} (${pctEmpty}%)`);
|
|
291
|
+
console.log(`π Empty descriptions: ${emptyDescriptions.length} (${pctEmpty}%) (excludes deprecated)`);
|
|
259
292
|
console.log(`πΈοΈ Deprecated: ${deprecatedProperties.length} (${pctDeprecated}%)`);
|
|
260
293
|
console.log(`π« Not documented: ${undocumented.length} (${pctUndocumented}%)`);
|
|
261
294
|
|
|
@@ -309,7 +342,7 @@ function generateAllDocs(inputFile, outputDir) {
|
|
|
309
342
|
console.log(` Deprecated properties: ${deprecatedCount}`);
|
|
310
343
|
|
|
311
344
|
if (notRendered > 0) {
|
|
312
|
-
console.log('
|
|
345
|
+
console.log('Ignored:\n ' + errors.undocumented_properties.join('\n '));
|
|
313
346
|
}
|
|
314
347
|
|
|
315
348
|
return {
|
|
@@ -349,4 +382,4 @@ if (require.main === module) {
|
|
|
349
382
|
console.error(`β Error: ${err.message}`);
|
|
350
383
|
process.exit(1);
|
|
351
384
|
}
|
|
352
|
-
}
|
|
385
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handlebars helper to check if an array includes a value.
|
|
5
|
+
*
|
|
6
|
+
* Usage: {{#if (includes array value)}}...{{/if}}
|
|
7
|
+
*
|
|
8
|
+
* @param {Array} array - The array to search
|
|
9
|
+
* @param {*} value - The value to find
|
|
10
|
+
* @returns {boolean} True if array includes value
|
|
11
|
+
*/
|
|
12
|
+
module.exports = function(array, value) {
|
|
13
|
+
if (!Array.isArray(array)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return array.includes(value);
|
|
17
|
+
};
|
|
@@ -4,9 +4,12 @@ module.exports = {
|
|
|
4
4
|
join: require('./join.js'),
|
|
5
5
|
eq: require('./eq.js'),
|
|
6
6
|
ne: require('./ne.js'),
|
|
7
|
+
gt: require('./gt.js'),
|
|
7
8
|
and: require('./and.js'),
|
|
8
9
|
or: require('./or.js'),
|
|
9
10
|
not: require('./not.js'),
|
|
11
|
+
includes: require('./includes.js'),
|
|
12
|
+
isEnterpriseEnum: require('./isEnterpriseEnum.js'),
|
|
10
13
|
formatPropertyValue: require('./formatPropertyValue.js'),
|
|
11
14
|
renderPropertyExample: require('./renderPropertyExample.js'),
|
|
12
15
|
formatUnits: require('./formatUnits.js'),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handlebars helper to check if an enum value is marked as enterprise.
|
|
5
|
+
*
|
|
6
|
+
* Usage: {{#if (isEnterpriseEnum enumValue x-enum-metadata)}}(Enterprise){{/if}}
|
|
7
|
+
* Usage in template: {{#each enum}}`{{this}}`{{#if (isEnterpriseEnum this ../x-enum-metadata)}} (Enterprise){{/if}}{{/each}}
|
|
8
|
+
*
|
|
9
|
+
* @param {string} enumValue - The enum value to check
|
|
10
|
+
* @param {Object} metadata - The x-enum-metadata object mapping values to {is_enterprise: boolean}
|
|
11
|
+
* @returns {boolean} True if the enum value is enterprise-only
|
|
12
|
+
*/
|
|
13
|
+
module.exports = function(enumValue, metadata) {
|
|
14
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const valueMetadata = metadata[enumValue];
|
|
19
|
+
if (!valueMetadata || typeof valueMetadata !== 'object') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return valueMetadata.is_enterprise === true;
|
|
24
|
+
};
|
|
@@ -18,8 +18,8 @@ module.exports = function renderPropertyExample(property) {
|
|
|
18
18
|
if (property.example.includes('.Example') || property.example.includes('[,yaml]')) {
|
|
19
19
|
exampleContent = property.example;
|
|
20
20
|
} else {
|
|
21
|
-
//
|
|
22
|
-
exampleContent =
|
|
21
|
+
// Wrap simple string examples in backticks for inline code formatting
|
|
22
|
+
exampleContent = `\`${property.example}\``;
|
|
23
23
|
}
|
|
24
24
|
} else if (Array.isArray(property.example)) {
|
|
25
25
|
// Multiline array example
|
|
@@ -34,9 +34,10 @@ module.exports = function renderPropertyExample(property) {
|
|
|
34
34
|
exampleContent += `[,yaml]\n----\n${JSON.stringify(property.example.config, null, 2)}\n----`;
|
|
35
35
|
}
|
|
36
36
|
} else {
|
|
37
|
-
// Fallback: JSON stringify
|
|
38
|
-
|
|
37
|
+
// Fallback: JSON stringify and wrap in backticks for inline code
|
|
38
|
+
const jsonStr = JSON.stringify(property.example, null, 2);
|
|
39
|
+
exampleContent = `\`${jsonStr}\``;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
return new handlebars.SafeString(
|
|
42
|
+
return new handlebars.SafeString(exampleContent);
|
|
42
43
|
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Overrides file for Redpanda property extraction. Supports two top-level keys: 'properties' for property overrides (descriptions, examples, etc.) and 'definitions' for type definition overrides (structs, enums with user-friendly names, etc.). Override definitions take precedence over dynamically extracted ones.",
|
|
3
|
+
"definitions": {
|
|
4
|
+
"client_group_quota": {
|
|
5
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/client_group_byte_rate_quota.h#L29",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"group_name": {
|
|
9
|
+
"type": "string"
|
|
10
|
+
},
|
|
11
|
+
"clients_prefix": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
"quota": {
|
|
15
|
+
"type": "integer",
|
|
16
|
+
"minimum": -9223372036854775808,
|
|
17
|
+
"maximum": 9223372036854775807
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"config::broker_authn_endpoint": {
|
|
22
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/broker_authn_endpoint.h#L42",
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"name": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"address": {
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
"port": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"minimum": 0,
|
|
34
|
+
"maximum": 4294967295
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"config::endpoint_tls_config": {
|
|
39
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/endpoint_tls_config.h#L21",
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"name": {
|
|
43
|
+
"type": "string"
|
|
44
|
+
},
|
|
45
|
+
"config": {
|
|
46
|
+
"$ref": "#/definitions/config::tls_config"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"config::tls_config": {
|
|
51
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/tls_config.h#L49",
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"enabled": {
|
|
55
|
+
"type": "boolean"
|
|
56
|
+
},
|
|
57
|
+
"require_client_auth": {
|
|
58
|
+
"type": "boolean"
|
|
59
|
+
},
|
|
60
|
+
"key_file": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
"cert_file": {
|
|
64
|
+
"type": "string"
|
|
65
|
+
},
|
|
66
|
+
"truststore_file": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"tls_config": {
|
|
72
|
+
"$ref": "#/definitions/config::tls_config"
|
|
73
|
+
},
|
|
74
|
+
"config::rest_authn_endpoint": {
|
|
75
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/rest_authn_endpoint.h#L42",
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"name": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
},
|
|
81
|
+
"address": {
|
|
82
|
+
"type": "string"
|
|
83
|
+
},
|
|
84
|
+
"port": {
|
|
85
|
+
"type": "integer",
|
|
86
|
+
"minimum": 0,
|
|
87
|
+
"maximum": 4294967295
|
|
88
|
+
},
|
|
89
|
+
"authentication_method": {
|
|
90
|
+
"$ref": "#/definitions/config::rest_authn_method"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"config::rest_authn_method": {
|
|
95
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/rest_authn_endpoint.h#L31",
|
|
96
|
+
"enum": [
|
|
97
|
+
"none",
|
|
98
|
+
"http_basic"
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
"endpoint_tls_config": {
|
|
102
|
+
"$ref": "#/definitions/config::endpoint_tls_config"
|
|
103
|
+
},
|
|
104
|
+
"model::broker_endpoint": {
|
|
105
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L88",
|
|
106
|
+
"type": "object",
|
|
107
|
+
"properties": {
|
|
108
|
+
"name": {
|
|
109
|
+
"type": "string"
|
|
110
|
+
},
|
|
111
|
+
"address": {
|
|
112
|
+
"type": "string"
|
|
113
|
+
},
|
|
114
|
+
"port": {
|
|
115
|
+
"type": "integer",
|
|
116
|
+
"minimum": 0,
|
|
117
|
+
"maximum": 4294967295
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"model::cleanup_policy_bitflags": {
|
|
122
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/fundamental.h#L72",
|
|
123
|
+
"enum": [
|
|
124
|
+
"none",
|
|
125
|
+
"delete",
|
|
126
|
+
"compact"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"model::cloud_credentials_source": {
|
|
130
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L454",
|
|
131
|
+
"enum": [
|
|
132
|
+
"config_file",
|
|
133
|
+
"aws_instance_metadata",
|
|
134
|
+
"sts",
|
|
135
|
+
"gcp_instance_metadata"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
"model::cloud_storage_backend": {
|
|
139
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L481",
|
|
140
|
+
"enum": [
|
|
141
|
+
"aws",
|
|
142
|
+
"google_s3_compat",
|
|
143
|
+
"azure",
|
|
144
|
+
"minio",
|
|
145
|
+
"unknown"
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"model::cloud_storage_chunk_eviction_strategy": {
|
|
149
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L524",
|
|
150
|
+
"enum": [
|
|
151
|
+
"eager",
|
|
152
|
+
"capped",
|
|
153
|
+
"predictive"
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
"model::compression": {
|
|
157
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/compression.h#L36",
|
|
158
|
+
"enum": [
|
|
159
|
+
"none",
|
|
160
|
+
"gzip",
|
|
161
|
+
"snappy",
|
|
162
|
+
"lz4",
|
|
163
|
+
"zstd",
|
|
164
|
+
"producer"
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"model::leader_balancer_mode": {
|
|
168
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L504",
|
|
169
|
+
"enum": [
|
|
170
|
+
"greedy_balanced_shards",
|
|
171
|
+
"random_hill_climbing"
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
"model::node_id": {
|
|
175
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L36",
|
|
176
|
+
"type": "integer",
|
|
177
|
+
"minimum": -2147483648,
|
|
178
|
+
"maximum": 2147483647
|
|
179
|
+
},
|
|
180
|
+
"model::partition_autobalancing_mode": {
|
|
181
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L463",
|
|
182
|
+
"enum": [
|
|
183
|
+
"off",
|
|
184
|
+
"node_add",
|
|
185
|
+
"continuous"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
"model::rack_id": {
|
|
189
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L60",
|
|
190
|
+
"type": "string"
|
|
191
|
+
},
|
|
192
|
+
"model::timestamp_type": {
|
|
193
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/timestamp.h#L30",
|
|
194
|
+
"enum": [
|
|
195
|
+
"create_time",
|
|
196
|
+
"append_time"
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
"net::unresolved_address": {
|
|
200
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/net/unresolved_address.h#L27",
|
|
201
|
+
"properties": {
|
|
202
|
+
"address": {
|
|
203
|
+
"type": "string"
|
|
204
|
+
},
|
|
205
|
+
"port": {
|
|
206
|
+
"type": "integer",
|
|
207
|
+
"minimum": 0,
|
|
208
|
+
"maximum": 4294967295
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"pandaproxy::schema_registry::schema_id_validation_mode": {
|
|
213
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/pandaproxy/schema_registry/schema_id_validation.h#L22",
|
|
214
|
+
"enum": [
|
|
215
|
+
"none",
|
|
216
|
+
"redpanda",
|
|
217
|
+
"compat"
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
"retention_duration_property": {
|
|
221
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/property.h#L878",
|
|
222
|
+
"type": "integer",
|
|
223
|
+
"minimum": -2147483648,
|
|
224
|
+
"maximum": 2147483647
|
|
225
|
+
},
|
|
226
|
+
"seed_server": {
|
|
227
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/seed_server.h#L24",
|
|
228
|
+
"type": "object",
|
|
229
|
+
"properties": {
|
|
230
|
+
"host": {
|
|
231
|
+
"$ref": "#/definitions/net::unresolved_address"
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"throughput_control_group": {
|
|
236
|
+
"defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/throughput_control_group.h#L36",
|
|
237
|
+
"type": "object",
|
|
238
|
+
"properties": {
|
|
239
|
+
"name": {
|
|
240
|
+
"type": "string"
|
|
241
|
+
},
|
|
242
|
+
"client_id": {
|
|
243
|
+
"type": "string"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|