Skip to content

Commit 9e44b81

Browse files
authored
docs: add json examples to the endpoints (#1765)
* Create examples in docsv2 * Add data schemas below example * styling
1 parent 7e3b409 commit 9e44b81

File tree

4 files changed

+608
-4
lines changed

4 files changed

+608
-4
lines changed

docs-v2/dist/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs-v2/scripts/components.js

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ export class UIComponents {
215215
<h4 class="media-type">${mediaType}</h4>
216216
${mediaContent.schema ? this.renderSchema(mediaContent.schema) : ''}
217217
${mediaContent.example ? this.renderExample(mediaContent.example, mediaType) : ''}
218+
${mediaContent.schema ? this.renderSchemaExample(mediaContent.schema, mediaType) : ''}
218219
</div>
219220
`).join('')}
220221
</div>
@@ -305,6 +306,162 @@ export class UIComponents {
305306
`;
306307
}
307308

309+
/**
310+
* Render example generated from schema
311+
*/
312+
renderSchemaExample(schema, mediaType = 'application/json') {
313+
if (!schema) {
314+
return '';
315+
}
316+
317+
try {
318+
// Generate example from schema using parser
319+
const exampleData = this.parser.generateExampleFromSchema(schema);
320+
321+
if (!exampleData) {
322+
return `
323+
<div class="generated-example-section">
324+
<div class="example-header">
325+
<span>Generated Example Response</span>
326+
</div>
327+
<div class="no-example">Unable to generate example from this schema</div>
328+
</div>
329+
`;
330+
}
331+
332+
const formattedExample = this.formatExample(exampleData, mediaType);
333+
334+
return `
335+
<div class="generated-example-section">
336+
<div class="example-header">
337+
<span>Generated Example Response</span>
338+
<button class="copy-button" data-copy="${this.escapeHtml(formattedExample)}" title="Copy example">
339+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
340+
<path d="M13.5 4.5H6.5C5.67157 4.5 5 5.17157 5 6V13C5 13.8284 5.67157 14.5 6.5 14.5H13.5C14.3284 14.5 15 13.8284 15 13V6C15 5.17157 14.3284 4.5 13.5 4.5Z" stroke="currentColor" stroke-width="1.5"/>
341+
<path d="M3 10.5C2.17157 10.5 1.5 9.82843 1.5 9V2C1.5 1.17157 2.17157 0.5 3 0.5H10C10.8284 0.5 11.5 1.17157 11.5 2" stroke="currentColor" stroke-width="1.5"/>
342+
</svg>
343+
</button>
344+
</div>
345+
<div class="code-block">
346+
<pre><code class="language-json">${this.escapeHtml(formattedExample)}</code></pre>
347+
</div>
348+
<div class="example-note">
349+
<small>💡 This example was automatically generated from the API schema</small>
350+
</div>
351+
${this.renderDataSchemaSection(schema)}
352+
</div>
353+
`;
354+
} catch (error) {
355+
console.error('Error generating example from schema:', error, schema);
356+
return `
357+
<div class="generated-example-section">
358+
<div class="example-header">
359+
<span>Generated Example Response</span>
360+
</div>
361+
<div class="example-error">
362+
<span>⚠️ Error generating example</span>
363+
<small>Schema processing failed: ${error.message}</small>
364+
</div>
365+
</div>
366+
`;
367+
}
368+
}
369+
370+
/**
371+
* Render data schema section with links to schema definitions
372+
*/
373+
renderDataSchemaSection(schema) {
374+
if (!schema) return '';
375+
376+
const schemaReferences = this.extractSchemaReferences(schema);
377+
378+
if (schemaReferences.length === 0) {
379+
return '';
380+
}
381+
382+
return `
383+
<div class="data-schema-section">
384+
<div class="data-schema-header">
385+
<span>📋 Data Schema</span>
386+
</div>
387+
<div class="schema-links">
388+
${schemaReferences.map(ref => `
389+
<a href="#schema-${ref}" class="schema-link" data-schema="${ref}">
390+
<span class="schema-link-icon">🔗</span>
391+
<span class="schema-link-name">${ref}</span>
392+
</a>
393+
`).join('')}
394+
</div>
395+
<div class="schema-links-note">
396+
<small>Click on schema names to view their detailed definitions</small>
397+
</div>
398+
</div>
399+
`;
400+
}
401+
402+
/**
403+
* Extract all schema references from a schema
404+
*/
405+
extractSchemaReferences(schema, visited = new Set()) {
406+
const references = new Set();
407+
408+
if (!schema || typeof schema !== 'object') {
409+
return Array.from(references);
410+
}
411+
412+
// Handle direct $ref
413+
if (schema.$ref) {
414+
const refName = this.parser.resolveRef(schema.$ref);
415+
if (refName && !visited.has(refName)) {
416+
references.add(refName);
417+
visited.add(refName);
418+
419+
// Recursively check referenced schema
420+
const referencedSchema = this.parser.getSchema(refName);
421+
if (referencedSchema) {
422+
const nestedRefs = this.extractSchemaReferences(referencedSchema, visited);
423+
nestedRefs.forEach(ref => references.add(ref));
424+
}
425+
}
426+
}
427+
428+
// Handle oneOf, anyOf, allOf
429+
if (schema.oneOf) {
430+
schema.oneOf.forEach(subSchema => {
431+
const nestedRefs = this.extractSchemaReferences(subSchema, visited);
432+
nestedRefs.forEach(ref => references.add(ref));
433+
});
434+
}
435+
if (schema.anyOf) {
436+
schema.anyOf.forEach(subSchema => {
437+
const nestedRefs = this.extractSchemaReferences(subSchema, visited);
438+
nestedRefs.forEach(ref => references.add(ref));
439+
});
440+
}
441+
if (schema.allOf) {
442+
schema.allOf.forEach(subSchema => {
443+
const nestedRefs = this.extractSchemaReferences(subSchema, visited);
444+
nestedRefs.forEach(ref => references.add(ref));
445+
});
446+
}
447+
448+
// Handle array items
449+
if (schema.items) {
450+
const nestedRefs = this.extractSchemaReferences(schema.items, visited);
451+
nestedRefs.forEach(ref => references.add(ref));
452+
}
453+
454+
// Handle object properties
455+
if (schema.properties) {
456+
Object.values(schema.properties).forEach(propSchema => {
457+
const nestedRefs = this.extractSchemaReferences(propSchema, visited);
458+
nestedRefs.forEach(ref => references.add(ref));
459+
});
460+
}
461+
462+
return Array.from(references);
463+
}
464+
308465
/**
309466
* Render example request
310467
*/

docs-v2/scripts/parser.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export class OpenAPIParser {
1010
this.endpoints = new Map();
1111
this.schemas = new Map();
1212
this.tags = new Map();
13+
this.exampleCache = new Map(); // Cache for generated examples
1314
}
1415

1516
/**
@@ -382,4 +383,195 @@ export class OpenAPIParser {
382383
};
383384
return colors[method] || 'method-default';
384385
}
386+
387+
/**
388+
* Generate example response data from schema
389+
*/
390+
generateExampleFromSchema(schema, depth = 0, visitedRefs = new Set()) {
391+
if (depth > 10) return '...'; // Prevent infinite recursion
392+
if (!schema) return null;
393+
394+
// Create cache key for this schema at root level only
395+
if (depth === 0) {
396+
const cacheKey = JSON.stringify(schema);
397+
if (this.exampleCache.has(cacheKey)) {
398+
return this.exampleCache.get(cacheKey);
399+
}
400+
401+
// Generate example and cache it
402+
const example = this._generateExampleFromSchemaInternal(schema, depth, visitedRefs);
403+
this.exampleCache.set(cacheKey, example);
404+
return example;
405+
}
406+
407+
// For nested calls, don't use cache to avoid issues with circular refs
408+
return this._generateExampleFromSchemaInternal(schema, depth, visitedRefs);
409+
}
410+
411+
/**
412+
* Internal method for generating examples (without caching)
413+
*/
414+
_generateExampleFromSchemaInternal(schema, depth = 0, visitedRefs = new Set()) {
415+
if (depth > 10) return '...'; // Prevent infinite recursion
416+
if (!schema) return null;
417+
418+
// Handle schema reference
419+
if (schema.$ref) {
420+
const refName = this.resolveRef(schema.$ref);
421+
422+
if (!refName || visitedRefs.has(refName)) {
423+
return `[Reference to ${refName}]`;
424+
}
425+
426+
visitedRefs.add(refName);
427+
const referencedSchema = this.schemas.get(refName);
428+
429+
if (referencedSchema) {
430+
const result = this._generateExampleFromSchemaInternal(referencedSchema, depth + 1, visitedRefs);
431+
visitedRefs.delete(refName);
432+
return result;
433+
}
434+
return `[${refName} schema not found]`;
435+
}
436+
437+
// Handle oneOf, anyOf, allOf
438+
if (schema.oneOf && schema.oneOf.length > 0) {
439+
return this._generateExampleFromSchemaInternal(schema.oneOf[0], depth + 1, visitedRefs);
440+
}
441+
if (schema.anyOf && schema.anyOf.length > 0) {
442+
return this._generateExampleFromSchemaInternal(schema.anyOf[0], depth + 1, visitedRefs);
443+
}
444+
if (schema.allOf && schema.allOf.length > 0) {
445+
// Merge all schemas in allOf
446+
const merged = {};
447+
for (const subSchema of schema.allOf) {
448+
const example = this._generateExampleFromSchemaInternal(subSchema, depth + 1, visitedRefs);
449+
if (example && typeof example === 'object') {
450+
Object.assign(merged, example);
451+
}
452+
}
453+
return Object.keys(merged).length > 0 ? merged : null;
454+
}
455+
456+
// Handle arrays
457+
if (schema.type === 'array' && schema.items) {
458+
const itemExample = this._generateExampleFromSchemaInternal(schema.items, depth + 1, visitedRefs);
459+
return [itemExample];
460+
}
461+
462+
// Handle objects
463+
if (schema.type === 'object') {
464+
const example = {};
465+
466+
if (schema.properties) {
467+
Object.entries(schema.properties).forEach(([propName, propSchema]) => {
468+
example[propName] = this._generateExampleFromSchemaInternal(propSchema, depth + 1, visitedRefs);
469+
});
470+
}
471+
472+
return example;
473+
}
474+
475+
// Handle primitive types
476+
return this.generateExampleForPrimitive(schema);
477+
}
478+
479+
/**
480+
* Generate example for primitive types
481+
*/
482+
generateExampleForPrimitive(schema) {
483+
const type = schema.type;
484+
const format = schema.format;
485+
486+
// Use example if provided
487+
if (schema.example !== undefined) {
488+
return schema.example;
489+
}
490+
491+
// Use enum if available
492+
if (schema.enum && schema.enum.length > 0) {
493+
return schema.enum[0];
494+
}
495+
496+
// Generate based on format
497+
if (format) {
498+
switch (format) {
499+
case 'SS58':
500+
return '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5';
501+
case 'unsignedInteger':
502+
return '1000000000000';
503+
case '$hex':
504+
return '0x1234567890abcdef';
505+
case 'date-time':
506+
return '2023-01-01T12:00:00.000Z';
507+
case 'uuid':
508+
return '123e4567-e89b-12d3-a456-426614174000';
509+
case 'email':
510+
511+
case 'uri':
512+
return 'https://example.com';
513+
case 'binary':
514+
return 'base64EncodedData';
515+
}
516+
}
517+
518+
// Generate based on type
519+
switch (type) {
520+
case 'string':
521+
if (schema.description && schema.description.toLowerCase().includes('hash')) {
522+
return '0x1234567890abcdef1234567890abcdef12345678';
523+
}
524+
if (schema.description && schema.description.toLowerCase().includes('address')) {
525+
return '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5';
526+
}
527+
return 'example_string';
528+
case 'number':
529+
case 'integer':
530+
return 123;
531+
case 'boolean':
532+
return true;
533+
case 'null':
534+
return null;
535+
default:
536+
return null;
537+
}
538+
}
539+
540+
/**
541+
* Generate example response for an endpoint
542+
*/
543+
generateExampleResponse(endpoint, statusCode = '200') {
544+
if (!endpoint.responses || !endpoint.responses[statusCode]) {
545+
return null;
546+
}
547+
548+
const response = endpoint.responses[statusCode];
549+
const content = response.content?.['application/json'];
550+
551+
if (!content || !content.schema) {
552+
return null;
553+
}
554+
555+
return this.generateExampleFromSchema(content.schema);
556+
}
557+
558+
/**
559+
* Get all example responses for an endpoint
560+
*/
561+
getAllExampleResponses(endpoint) {
562+
if (!endpoint.responses) return {};
563+
564+
const examples = {};
565+
Object.entries(endpoint.responses).forEach(([statusCode, response]) => {
566+
const example = this.generateExampleResponse(endpoint, statusCode);
567+
if (example !== null) {
568+
examples[statusCode] = {
569+
description: response.description,
570+
example: example
571+
};
572+
}
573+
});
574+
575+
return examples;
576+
}
385577
}

0 commit comments

Comments
 (0)