diff --git a/src/ApiMethodDocumentation.js b/src/ApiMethodDocumentation.js
index 9bdee2d..416338a 100644
--- a/src/ApiMethodDocumentation.js
+++ b/src/ApiMethodDocumentation.js
@@ -239,7 +239,11 @@ export class ApiMethodDocumentation extends AmfHelperMixin(LitElement) {
* Bindings for the type document.
* This is a map of the type name to the binding name.
*/
- bindings: {type: Array}
+ bindings: {type: Array},
+ /**
+ * Controls whether the metadata section is opened
+ */
+ metadataOpened: { type: Boolean }
};
}
@@ -864,17 +868,93 @@ export class ApiMethodDocumentation extends AmfHelperMixin(LitElement) {
method
} = this;
return html`
- ${this._getTitleTemplate()}
+ ${this._getEnhancedSummaryTemplate()}
${this._deprecatedWarningTemplate()}
${this._getUrlTemplate()}
${this._getTraitsTemplate()}
${hasCustomProperties ? html`` : ''}
${this._getDescriptionTemplate()}
- ${this._getRequestTemplate()}
- ${this._getReturnsTemplate()}
+ ${this._getEnhancedRequestTemplate()}
+ ${this._getEnhancedResponseTemplate()}
+ ${this._getEnhancedSecurityTemplate()}
+ ${this._getEnhancedExamplesTemplate()}
+ ${this._getEnhancedMetadataTemplate()}
${this._getNavigationTemplate()}`;
}
+ /**
+ * Enhanced summary template with gRPC support and better organization
+ */
+ _getEnhancedSummaryTemplate() {
+ if (this._titleHidden) {
+ return '';
+ }
+
+ const { method, methodName, noTryIt, compatibility, methodSummary, operationId } = this;
+ const isGrpc = this.isGrpcOp(method);
+ const isAsyncApi = this._isAsyncAPI(this.amf);
+
+ return html`
+
+
+
+ ${noTryIt ? '' : html`
`}
+
+ ${methodSummary ? html`${methodSummary}
` : ''}
+ ${operationId && !isAsyncApi ? html`Operation ID: ${operationId}` : ''}
+
+ `;
+ }
+
+ /**
+ * Gets method badge template for REST/gRPC operations
+ */
+ _getMethodBadgeTemplate(method, isGrpc) {
+ if (!method) {
+ return '';
+ }
+
+ const httpMethod = this._getValue(method, this.ns.aml.vocabularies.apiContract.method);
+
+ if (isGrpc) {
+ const streamType = this.getGrpcStreamType(method);
+ return html`
+
+ gRPC
+ ${streamType !== 'unary' ? html`${this._formatStreamType(streamType)}` : ''}
+
+ `;
+ }
+
+ return html`
+
+ ${httpMethod?.toUpperCase()}
+
+ `;
+ }
+
+ /**
+ * Formats stream type for display
+ */
+ _formatStreamType(streamType) {
+ const typeMap = {
+ 'client_streaming': 'Client Stream',
+ 'server_streaming': 'Server Stream',
+ 'bidi_streaming': 'Bidirectional Stream',
+ 'unary': 'Unary'
+ };
+ return typeMap[streamType] || streamType;
+ }
+
_getTitleTemplate() {
const isAsyncApi = this._isAsyncAPI(this.amf)
if (this._titleHidden) {
@@ -1108,6 +1188,204 @@ export class ApiMethodDocumentation extends AmfHelperMixin(LitElement) {
>`;
}
+ /**
+ * Enhanced request template with structured information
+ */
+ _getEnhancedRequestTemplate() {
+ const { method } = this;
+ if (!method) {
+ return '';
+ }
+
+ const expects = this._computeExpects(method);
+ if (!expects) {
+ return '';
+ }
+
+ const isGrpc = this.isGrpcOp(method);
+ const payloads = this.getPayloads(expects);
+ const params = this.getParams(method);
+ const hasParams = Object.values(params).some(paramArray => paramArray.length > 0);
+
+ return html`
+
+ Request
+
+ ${this._getRequestPayloadTemplate(payloads, isGrpc)}
+ ${hasParams ? this._getEnhancedParametersTemplate(params) : ''}
+ ${this._getRequestHeadersTemplate(expects)}
+
+
+ ${this._getAsyncSecurityMethodTemplate()}
+ ${this._getMessagesTemplate()}
+ ${this._getCodeSnippetsTemplate()}
+ ${this._getAgentTemplate()}
+ ${this._callbacksTemplate()}
+
+ `;
+ }
+
+ /**
+ * Gets request payload template
+ */
+ _getRequestPayloadTemplate(payloads, isGrpc) {
+ if (!payloads || !Array.isArray(payloads) || payloads.length === 0) {
+ return html`No request body
`;
+ }
+
+ return html`
+
+
Body
+ ${payloads.map(payload => this._getPayloadTemplate(payload, isGrpc, 'request'))}
+
+ `;
+ }
+
+ /**
+ * Gets payload template for request/response
+ */
+ _getPayloadTemplate(payload, isGrpc, type = 'request') {
+ const mediaType = this.getMediaType(payload);
+ const schema = this._getValue(payload, this.ns.aml.vocabularies.shapes.schema);
+ const examples = this.getExamples(payload);
+
+ let schemaName = 'Unknown';
+ if (schema && schema.length > 0) {
+ schemaName = this.getSchemaName(schema[0]) || 'Schema';
+ }
+
+ return html`
+
+
+
+ ${schema && schema.length > 0 ? this._getSchemaTemplate(schema[0], isGrpc) : ''}
+ ${examples.length > 0 ? this._getPayloadExamplesTemplate(examples, type) : ''}
+
+ `;
+ }
+
+ /**
+ * Gets schema template
+ */
+ _getSchemaTemplate(schema, isGrpc) {
+ const schemaName = this.getSchemaName(schema);
+ const description = this._getValue(schema, this.ns.aml.vocabularies.core.description);
+
+ if (isGrpc) {
+ // Resolve schema if it's a link
+ const resolvedSchema = this._resolveSchemaLink(schema);
+ if (!resolvedSchema) {
+ return html`
+
+
${schemaName} Message
+
Schema definition not found
+
+ `;
+ }
+
+ // For gRPC, show message structure from resolved schema
+ const properties = this._ensureArray(this._getValue(resolvedSchema, this.ns.w3.shacl.property));
+ if (properties && properties.length > 0) {
+ return html`
+
+
${schemaName} Message
+ ${description ? html`
${description}
` : ''}
+
+ ${properties.map(prop => this._getGrpcFieldTemplate(prop))}
+
+
+ `;
+ }
+
+ return html`
+
+
${schemaName} Message
+ ${description ? html`
${description}
` : ''}
+
No fields defined
+
+ `;
+ }
+
+ // For REST, use existing body document component
+ return html`
+
+
+ `;
+ }
+
+ /**
+ * Gets gRPC field template
+ */
+ _getGrpcFieldTemplate(property) {
+ const name = this._getValue(property, this.ns.w3.shacl.name);
+ const range = this._getValue(property, this.ns.aml.vocabularies.shapes.range);
+ const minCount = this._getValue(property, this.ns.w3.shacl.minCount);
+ const required = minCount && minCount > 0;
+
+ let fieldType = this._getFieldTypeFromRange(range);
+
+ return html`
+
+ ${name}
+ ${fieldType}
+ ${required ? html`required` : ''}
+
+ `;
+ }
+
+ /**
+ * Gets field type string from range object
+ * @param {Object} range AMF Range object
+ * @returns {string} Field type description
+ */
+ _getFieldTypeFromRange(range) {
+ if (!range) {
+ return 'string';
+ }
+
+ // Check if it's an array
+ if (this._hasType(range, 'http://a.ml/vocabularies/shapes#ArrayShape')) {
+ const items = this._getValue(range, this.ns.aml.vocabularies.shapes.items);
+ if (items) {
+ const itemType = this._getFieldTypeFromRange(items);
+ return `${itemType}[]`;
+ }
+ return 'array';
+ }
+
+ // Check if it's a scalar
+ if (this._hasType(range, 'http://a.ml/vocabularies/shapes#ScalarShape')) {
+ const dataType = this._getValue(range, this.ns.w3.shacl.datatype);
+ if (dataType) {
+ const typeUri = dataType['@id'] || dataType;
+ if (typeUri.includes('#string')) return 'string';
+ if (typeUri.includes('#int')) return 'int32';
+ if (typeUri.includes('#long')) return 'int64';
+ if (typeUri.includes('#float')) return 'float';
+ if (typeUri.includes('#double')) return 'double';
+ if (typeUri.includes('#boolean')) return 'bool';
+ return typeUri.split('#').pop() || 'string';
+ }
+ }
+
+ // Check if it's a node shape (message type)
+ if (this._hasType(range, 'http://www.w3.org/ns/shacl#NodeShape')) {
+ const name = this._getValue(range, this.ns.w3.shacl.name) ||
+ this._getValue(range, this.ns.aml.vocabularies.core.name);
+ return name || 'message';
+ }
+
+ return 'string';
+ }
+
_getRequestTemplate() {
return html`
${this._getAsyncSecurityMethodTemplate()}
@@ -1207,6 +1485,184 @@ export class ApiMethodDocumentation extends AmfHelperMixin(LitElement) {
this.expects = this.message[event.target.selected]
}
+ /**
+ * Enhanced parameters template with better organization
+ */
+ _getEnhancedParametersTemplate(params) {
+ const hasAnyParams = Object.values(params).some(paramArray => paramArray.length > 0);
+ if (!hasAnyParams) {
+ return '';
+ }
+
+ return html`
+
+
Parameters
+ ${params.path.length > 0 ? this._getParameterGroupTemplate('Path', params.path) : ''}
+ ${params.query.length > 0 ? this._getParameterGroupTemplate('Query', params.query) : ''}
+ ${params.header.length > 0 ? this._getParameterGroupTemplate('Header', params.header) : ''}
+ ${params.cookie.length > 0 ? this._getParameterGroupTemplate('Cookie', params.cookie) : ''}
+
+ `;
+ }
+
+ /**
+ * Gets parameter group template
+ */
+ _getParameterGroupTemplate(groupName, parameters) {
+ return html`
+
+
${groupName} Parameters
+
+ ${parameters.map(param => this._getParameterItemTemplate(param))}
+
+
+ `;
+ }
+
+ /**
+ * Gets parameter item template
+ */
+ _getParameterItemTemplate(param) {
+ return html`
+
+
+ ${param.description ? html`
${param.description}
` : ''}
+ ${param.defaultValue ? html`
Default: ${param.defaultValue}
` : ''}
+ ${param.examples.length > 0 ? html`
Examples: ${param.examples.map(ex => html`${ex}
`).join(', ')}
` : ''}
+
+ `;
+ }
+
+ /**
+ * Gets request headers template
+ */
+ _getRequestHeadersTemplate(expects) {
+ const headers = this._ensureArray(this._getValue(expects, this.ns.aml.vocabularies.apiContract.header));
+ if (!headers || headers.length === 0) {
+ return '';
+ }
+
+ return html`
+
+ `;
+ }
+
+ /**
+ * Enhanced response template
+ */
+ _getEnhancedResponseTemplate() {
+ const { returns, method } = this;
+ if (!returns || !returns.length || this._isAsyncAPI(this.amf)) {
+ return '';
+ }
+
+ const isGrpc = this.isGrpcOp(method);
+
+ return html`
+
+ Response
+
+ ${isGrpc ? this._getGrpcResponseTemplate(returns) : this._getRestResponseTemplate(returns)}
+
+ `;
+ }
+
+ /**
+ * Gets gRPC response template
+ */
+ _getGrpcResponseTemplate(returns) {
+ const response = returns[0]; // gRPC typically has one response
+ const payloads = this.getPayloads(response);
+
+ if (!payloads || !Array.isArray(payloads)) {
+ return '';
+ }
+
+ return html`
+
+ ${payloads.map(payload => this._getPayloadTemplate(payload, true, 'response'))}
+
+ `;
+ }
+
+ /**
+ * Gets REST response template
+ */
+ _getRestResponseTemplate(returns) {
+ return html`
+
+ `;
+ }
+
+ /**
+ * Gets payload examples template
+ */
+ _getPayloadExamplesTemplate(examples, type) {
+ if (!examples || !Array.isArray(examples) || examples.length === 0) {
+ return '';
+ }
+
+ return html`
+
+
Example${examples.length > 1 ? 's' : ''}
+ ${examples.map((example, index) => this._getExampleTemplate(example, index, type))}
+
+ `;
+ }
+
+ /**
+ * Gets example template
+ */
+ _getExampleTemplate(example, index, type) {
+ const value = this._getValue(example, this.ns.aml.vocabularies.core.value);
+ const name = this._getValue(example, this.ns.aml.vocabularies.core.name) || `Example ${index + 1}`;
+
+ if (!value) {
+ return '';
+ }
+
+ const isLongExample = value.length > 200;
+ const displayValue = isLongExample ? value.substring(0, 200) + '...' : value;
+
+ return html`
+
+
+
+
${displayValue}
+ ${isLongExample ? html`
+
this._toggleFullExample(index, type)}">
+ Show full example
+
+ ` : ''}
+
+
+ `;
+ }
+
_getReturnsTemplate() {
const { returns } = this;
if (!returns || !returns.length || this._isAsyncAPI(this.amf)) {
@@ -1305,42 +1761,293 @@ export class ApiMethodDocumentation extends AmfHelperMixin(LitElement) {
this.httpMethod = event.detail.method;
}
- isNonHttpProtocol() {
- const { protocol } = this;
- if (!protocol) {
- return false;
+ /**
+ * Enhanced security template
+ */
+ _getEnhancedSecurityTemplate() {
+ const { method } = this;
+ const securityRequirements = this.getSecurityRequirements(method);
+
+ if (!securityRequirements || securityRequirements.length === 0) {
+ return '';
}
- const lowerCase = protocol.toLowerCase();
- return lowerCase !== 'http' && lowerCase !== 'https';
+
+ return html`
+
+ Security
+
+ ${securityRequirements.map(requirement => this._getSecurityRequirementTemplate(requirement))}
+
+
+ `;
}
- _getNavigationTemplate() {
- const { next, previous, noNavigation } = this;
- if (!next && !previous || noNavigation) {
+ /**
+ * Gets security requirement template
+ */
+ _getSecurityRequirementTemplate(requirement) {
+ const scheme = this._getValue(requirement, this.ns.aml.vocabularies.security.scheme);
+ if (!scheme || !scheme.length) {
return '';
}
- const { compatibility } = this;
- return html`
- ${previous ? html`
-
-
-
-
${previous.label}
-
` : ''}
-
- ${next ? html`` : ''}
- `;
+
+ return html`
+
+
+ `;
}
- _computeMethodParametersUri(method) {
- let queryParams = '';
+ /**
+ * Enhanced examples template
+ */
+ _getEnhancedExamplesTemplate() {
+ const { method } = this;
if (!method) {
- return queryParams;
+ return '';
+ }
+
+ const isGrpc = this.isGrpcOp(method);
+ const examples = this._collectAllExamples(method);
+ const hasExamples = examples.request.length > 0 || examples.response.length > 0;
+
+ // Generate gRPC examples if it's a gRPC API and we don't have existing examples
+ let grpcExamples = null;
+ if (isGrpc && !hasExamples) {
+ grpcExamples = this._generateGrpcExamples();
+ }
+
+ if (!hasExamples && !grpcExamples) {
+ return '';
+ }
+
+ return html`
+
+ Examples
+
+ ${isGrpc && grpcExamples ? this._getGrpcExamplesTemplate(grpcExamples) : ''}
+
+ ${examples.request && Array.isArray(examples.request) && examples.request.length > 0 ? html`
+
+
Request Examples
+ ${examples.request.map((example, index) => this._getExampleTemplate(example, index, 'request'))}
+
+ ` : ''}
+
+ ${examples.response && Array.isArray(examples.response) && examples.response.length > 0 ? html`
+
+
Response Examples
+ ${examples.response.map((example, index) => this._getExampleTemplate(example, index, 'response'))}
+
+ ` : ''}
+
+ `;
+ }
+
+ /**
+ * Collects all examples from request and response
+ */
+ _collectAllExamples(method) {
+ const examples = { request: [], response: [] };
+
+ // Collect request examples
+ const expects = this._computeExpects(method);
+ if (expects) {
+ const payloads = this.getPayloads(expects);
+ if (payloads && Array.isArray(payloads)) {
+ payloads.forEach(payload => {
+ examples.request.push(...this.getExamples(payload));
+ });
+ }
+ }
+
+ // Collect response examples
+ const returns = this._computeReturns(method);
+ if (returns && returns.length > 0) {
+ returns.forEach(response => {
+ const payloads = this.getPayloads(response);
+ if (payloads && Array.isArray(payloads)) {
+ payloads.forEach(payload => {
+ examples.response.push(...this.getExamples(payload));
+ });
+ }
+ });
+ }
+
+ return examples;
+ }
+
+ /**
+ * Enhanced metadata template (collapsible)
+ */
+ _getEnhancedMetadataTemplate() {
+ const { method } = this;
+ if (!method) {
+ return '';
+ }
+
+ const metadata = this._collectMetadata(method);
+ const hasMetadata = Object.values(metadata).some(value =>
+ Array.isArray(value) ? value.length > 0 : Boolean(value)
+ );
+
+ if (!hasMetadata) {
+ return '';
+ }
+
+ const { compatibility } = this;
+ const opened = this.metadataOpened || false;
+ const label = this._computeToggleActionLabel(opened);
+ const buttonState = this._computeToggleButtonState(opened);
+ const iconClass = this._computeToggleIconClass(opened);
+
+ return html`
+
+ `;
+ }
+
+ /**
+ * Collects metadata from operation
+ */
+ _collectMetadata(method) {
+ const tags = this._ensureArray(this._getValue(method, this.ns.aml.vocabularies.core.tag));
+ const servers = this._ensureArray(this._getValue(method, this.ns.aml.vocabularies.apiContract.server));
+
+ return {
+ operationId: this._getValue(method, this.ns.aml.vocabularies.apiContract.operationId),
+ tags: Array.isArray(tags) ? tags : [],
+ externalDocs: this._getValue(method, this.ns.aml.vocabularies.core.documentation),
+ deprecated: this._getValue(method, this.ns.aml.vocabularies.core.deprecated),
+ servers: Array.isArray(servers) ? servers : [],
+ callbacks: this._computeCallbacks(method)
+ };
+ }
+
+ /**
+ * Gets metadata content template
+ */
+ _getMetadataContentTemplate(metadata) {
+ return html`
+
+ ${metadata.operationId ? html`
+
+ Operation ID:
+ ${metadata.operationId}
+
+ ` : ''}
+
+ ${metadata.tags && Array.isArray(metadata.tags) && metadata.tags.length > 0 ? html`
+
+ ` : ''}
+
+ ${metadata.externalDocs ? html`
+
+ ` : ''}
+
+ ${metadata.deprecated ? html`
+
+ Status:
+ Deprecated
+
+ ` : ''}
+
+ ${metadata.servers && Array.isArray(metadata.servers) && metadata.servers.length > 0 ? html`
+
+ ` : ''}
+
+ `;
+ }
+
+ /**
+ * Toggles metadata section
+ */
+ _toggleMetadata() {
+ this.metadataOpened = !this.metadataOpened;
+ }
+
+ /**
+ * Toggles full example display
+ */
+ _toggleFullExample(index, type) {
+ // Implementation for showing full examples
+ console.log(`Toggle full example ${index} for ${type}`);
+ }
+
+ isNonHttpProtocol() {
+ const { protocol } = this;
+ if (!protocol) {
+ return false;
+ }
+ const lowerCase = protocol.toLowerCase();
+ return lowerCase !== 'http' && lowerCase !== 'https';
+ }
+
+ _getNavigationTemplate() {
+ const { next, previous, noNavigation } = this;
+ if (!next && !previous || noNavigation) {
+ return '';
+ }
+ const { compatibility } = this;
+ return html`
+ ${previous ? html`
+
+
+
+
${previous.label}
+
` : ''}
+
+ ${next ? html`` : ''}
+ `;
+ }
+
+ _computeMethodParametersUri(method) {
+ let queryParams = '';
+ if (!method) {
+ return queryParams;
}
const expects = this._computeExpects(method);
@@ -1430,6 +2137,266 @@ export class ApiMethodDocumentation extends AmfHelperMixin(LitElement) {
return params;
}
+ // ========== Enhanced gRPC and AMF Helper Methods ==========
+
+ /**
+ * Detects if an operation is a gRPC operation
+ * @param {Object} operation AMF Operation model
+ * @returns {boolean} True if it's a gRPC operation
+ */
+ isGrpcOp(operation) {
+ if (!operation) {
+ return false;
+ }
+
+ // Check method type for gRPC-specific methods
+ const method = this._getValue(operation, this.ns.aml.vocabularies.apiContract.method);
+ if (method && ['publish', 'subscribe', 'pubsub'].includes(method.toLowerCase())) {
+ return true;
+ }
+
+ // Check request payload media type for application/grpc
+ const expects = this._computeExpects(operation);
+ if (expects) {
+ const payloads = this.getPayloads(expects);
+ if (payloads && payloads.length > 0) {
+ const mediaType = this.getMediaType(payloads[0]);
+ if (mediaType === 'application/grpc') {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the gRPC stream type for an operation
+ * @param {Object} operation AMF Operation model
+ * @returns {string} Stream type: 'unary', 'client_streaming', 'server_streaming', 'bidi_streaming'
+ */
+ getGrpcStreamType(operation) {
+ if (!operation) {
+ return 'unary';
+ }
+
+ const method = this._getValue(operation, this.ns.aml.vocabularies.apiContract.method);
+ if (!method) {
+ return 'unary';
+ }
+
+ // Map gRPC methods to stream types
+ switch (method.toLowerCase()) {
+ case 'post':
+ return 'unary'; // Standard unary RPC
+ case 'publish':
+ return 'client_streaming'; // Client sends stream
+ case 'subscribe':
+ return 'server_streaming'; // Server sends stream
+ case 'pubsub':
+ return 'bidi_streaming'; // Bidirectional streaming
+ default:
+ return 'unary';
+ }
+ }
+
+ /**
+ * Extracts payloads from a request or response node
+ * @param {Object} node AMF Request or Response model
+ * @returns {Array} Array of payload objects
+ */
+ getPayloads(node) {
+ if (!node) {
+ return [];
+ }
+ const payloads = this._ensureArray(this._getValue(node, this.ns.aml.vocabularies.apiContract.payload));
+ return Array.isArray(payloads) ? payloads : [];
+ }
+
+ /**
+ * Gets the media type from a payload
+ * @param {Object} payload AMF Payload model
+ * @returns {string|undefined} Media type string
+ */
+ getMediaType(payload) {
+ if (!payload) {
+ return undefined;
+ }
+ return this._getValue(payload, this.ns.aml.vocabularies.core.mediaType);
+ }
+
+ /**
+ * Gets the schema name from a shape
+ * @param {Object} shape AMF Shape model
+ * @returns {string|undefined} Schema name
+ */
+ getSchemaName(shape) {
+ if (!shape) {
+ return undefined;
+ }
+ // Try w3 name first, then core name
+ return this._getValue(shape, this.ns.w3.shacl.name) ||
+ this._getValue(shape, this.ns.aml.vocabularies.core.name);
+ }
+
+ /**
+ * Gets examples from a node (supports both single example and examples array)
+ * @param {Object} node AMF node with examples
+ * @returns {Array} Array of example objects
+ */
+ getExamples(node) {
+ if (!node) {
+ return [];
+ }
+
+ // Try examples array first
+ const examples = this._ensureArray(this._getValue(node, this.ns.aml.vocabularies.core.examples));
+ if (examples && Array.isArray(examples) && examples.length > 0) {
+ return examples;
+ }
+
+ // Try single example
+ const example = this._getValue(node, this.ns.aml.vocabularies.core.example);
+ if (example) {
+ return [example];
+ }
+
+ return [];
+ }
+
+ /**
+ * Gets parameters from operation and endpoint (path, query, header, cookie)
+ * @param {Object} operation AMF Operation model
+ * @returns {Object} Grouped parameters by type
+ */
+ getParams(operation) {
+ const params = {
+ path: [],
+ query: [],
+ header: [],
+ cookie: []
+ };
+
+ if (!operation) {
+ return params;
+ }
+
+ // Get parameters from expects (request)
+ const expects = this._computeExpects(operation);
+ if (expects) {
+ // Query parameters
+ const queryParams = this._ensureArray(this._getValue(expects, this.ns.aml.vocabularies.apiContract.parameter));
+ if (queryParams && Array.isArray(queryParams)) {
+ queryParams.forEach(param => {
+ const binding = this._getValue(param, this.ns.aml.vocabularies.apiBinding.binding);
+ const paramType = binding ? binding.toLowerCase() : 'query';
+
+ if (params[paramType]) {
+ params[paramType].push(this._processParameter(param));
+ }
+ });
+ }
+
+ // Header parameters
+ const headers = this._ensureArray(this._getValue(expects, this.ns.aml.vocabularies.apiContract.header));
+ if (headers && Array.isArray(headers)) {
+ headers.forEach(header => {
+ params.header.push(this._processParameter(header));
+ });
+ }
+ }
+
+ // Get path parameters from endpoint
+ if (this.endpoint) {
+ const pathParams = this._ensureArray(this._getValue(this.endpoint, this.ns.aml.vocabularies.apiContract.parameter));
+ if (pathParams && Array.isArray(pathParams)) {
+ pathParams.forEach(param => {
+ params.path.push(this._processParameter(param));
+ });
+ }
+ }
+
+ return params;
+ }
+
+ /**
+ * Processes a parameter to extract relevant information
+ * @param {Object} param AMF Parameter model
+ * @returns {Object} Processed parameter info
+ */
+ _processParameter(param) {
+ if (!param) {
+ return {};
+ }
+
+ const name = this._getValue(param, this.ns.aml.vocabularies.core.name);
+ const required = this._getValue(param, this.ns.aml.vocabularies.apiContract.required);
+ const schema = this._getValue(param, this.ns.aml.vocabularies.shapes.schema);
+ const description = this._getValue(param, this.ns.aml.vocabularies.core.description);
+
+ let type = 'string';
+ let defaultValue;
+ let examples = [];
+
+ if (schema) {
+ const schemaArray = this._ensureArray(schema);
+ if (schemaArray.length > 0) {
+ const schemaObj = schemaArray[0];
+ const dataType = this._getValue(schemaObj, this.ns.w3.shacl.datatype);
+ if (dataType) {
+ // Extract type from XML Schema URI
+ type = dataType.split('#').pop() || 'string';
+ }
+
+ defaultValue = this._getValue(schemaObj, this.ns.w3.shacl.defaultValue);
+ examples = this.getExamples(schemaObj);
+ }
+ }
+
+ return {
+ name,
+ type,
+ required: Boolean(required),
+ defaultValue,
+ description,
+ examples: examples.map(ex => this._getValue(ex, this.ns.aml.vocabularies.core.value)).filter(Boolean)
+ };
+ }
+
+ /**
+ * Gets security requirements for operation with fallback to API level
+ * @param {Object} operation AMF Operation model
+ * @returns {Array} Array of security requirements
+ */
+ getSecurityRequirements(operation) {
+ if (!operation) {
+ return [];
+ }
+
+ // Try operation-level security first
+ let security = this._computeSecurity(operation);
+
+ // Fallback to server/API level security
+ if (!security || security.length === 0) {
+ security = this._computeSecurity(this.server) || this._computeSecurity(this.amf);
+ }
+
+ return this._ensureArray(security);
+ }
+
+ /**
+ * Compacts a value using AMF key resolution
+ * @param {Object} node AMF node
+ * @param {string} key Property key
+ * @returns {*} Compacted value
+ */
+ compactValue(node, key) {
+ if (!node || !key) {
+ return undefined;
+ }
+ return this._getValue(node, this._getAmfKey(key));
+ }
+
/**
* Returns message value depending on operation node method
* Subscribe -> returns
@@ -1465,4 +2432,353 @@ export class ApiMethodDocumentation extends AmfHelperMixin(LitElement) {
* @param {String} selected
* @param {String} type
*/
+
+ /**
+ * Generates gRPC JSON example from payload schema
+ * @param {Object} payload AMF Payload model
+ * @returns {Object} Generated example with JSON and grpcurl command
+ */
+ generateGrpcExample(payload) {
+ if (!payload) {
+ return { json: '{}', grpcurl: '' };
+ }
+
+ const schema = this._getValue(payload, this.ns.aml.vocabularies.shapes.schema);
+ if (!schema) {
+ return { json: '{}', grpcurl: '' };
+ }
+
+ // Generate JSON example from schema
+ const jsonExample = this._generateJsonFromSchema(schema);
+
+ // Get service and method name for grpcurl
+ const serviceName = this._getServiceName();
+ const methodName = this._getMethodName();
+ const serverUrl = this._getGrpcServerUrl();
+
+ const grpcurlCommand = `grpcurl -plaintext -d '${JSON.stringify(jsonExample)}' ${serverUrl} ${serviceName}.${methodName}`;
+
+ return {
+ json: JSON.stringify(jsonExample, null, 2),
+ grpcurl: grpcurlCommand
+ };
+ }
+
+ /**
+ * Generates JSON example from AMF schema
+ * @param {Object} schema AMF Shape model
+ * @returns {Object} Generated JSON object
+ */
+ _generateJsonFromSchema(schema) {
+ if (!schema) {
+ return {};
+ }
+
+ // Resolve schema if it's a link
+ const resolvedSchema = this._resolveSchemaLink(schema);
+ if (!resolvedSchema) {
+ return {};
+ }
+
+ const properties = this._ensureArray(this._getValue(resolvedSchema, this.ns.w3.shacl.property));
+ if (!properties || !Array.isArray(properties)) {
+ return {};
+ }
+
+ const example = {};
+
+ properties.forEach(property => {
+ const name = this._getValue(property, this.ns.w3.shacl.name) ||
+ this._getValue(property, this.ns.aml.vocabularies.core.name);
+ const range = this._getValue(property, this.ns.aml.vocabularies.shapes.range);
+
+ if (name) {
+ example[name] = this._getExampleValueFromRange(range, name);
+ }
+ });
+
+ return example;
+ }
+
+ /**
+ * Gets example value based on data type
+ * @param {string} dataType Schema data type
+ * @param {string} fieldName Field name for context
+ * @returns {any} Example value
+ */
+ _getExampleValueForType(dataType, fieldName) {
+ if (!dataType) {
+ return fieldName.toLowerCase().includes('name') ? 'example' : 'value';
+ }
+
+ const typeStr = dataType.toString().toLowerCase();
+
+ if (typeStr.includes('string')) {
+ if (fieldName.toLowerCase().includes('name')) return 'world';
+ if (fieldName.toLowerCase().includes('message')) return 'Hello World!';
+ if (fieldName.toLowerCase().includes('email')) return 'user@example.com';
+ return 'example';
+ }
+
+ if (typeStr.includes('int') || typeStr.includes('number')) {
+ return 42;
+ }
+
+ if (typeStr.includes('bool')) {
+ return true;
+ }
+
+ if (typeStr.includes('array')) {
+ return ['item1', 'item2'];
+ }
+
+ return 'value';
+ }
+
+ /**
+ * Gets gRPC service name from current endpoint
+ * @returns {string} Service name
+ */
+ _getServiceName() {
+ if (this.endpoint) {
+ const serviceName = this._getValue(this.endpoint, this.ns.aml.vocabularies.core.name);
+ if (serviceName) {
+ return serviceName;
+ }
+ }
+ return 'greeter';
+ }
+
+ /**
+ * Gets gRPC method name from current method
+ * @returns {string} Method name
+ */
+ _getMethodName() {
+ if (this.method) {
+ const methodName = this._getValue(this.method, this.ns.aml.vocabularies.core.name);
+ if (methodName) {
+ return methodName;
+ }
+ }
+ return 'SayHello';
+ }
+
+ /**
+ * Gets gRPC server URL
+ * @returns {string} Server URL
+ */
+ _getGrpcServerUrl() {
+ // Try to get server from AMF model
+ if (this.amf) {
+ const servers = this._ensureArray(this._getValue(this.amf, this.ns.aml.vocabularies.apiContract.server));
+ if (servers && Array.isArray(servers) && servers.length > 0) {
+ const serverUrl = this._getValue(servers[0], this.ns.aml.vocabularies.core.url);
+ if (serverUrl) {
+ return serverUrl;
+ }
+ }
+ }
+
+ // Default gRPC server
+ return 'localhost:50051';
+ }
+
+ /**
+ * Checks if current API is gRPC
+ * @returns {boolean} True if gRPC API
+ */
+ isGrpcApi() {
+ return this.isGrpcOp(this.method);
+ }
+
+ /**
+ * Resolves schema link to actual schema definition
+ * @param {Object} schema AMF Schema that might be a link
+ * @returns {Object} Resolved schema or original if not a link
+ */
+ _resolveSchemaLink(schema) {
+ if (!schema) {
+ return null;
+ }
+
+ // Check if this schema has a link-target (reference to another schema)
+ const linkTarget = this._getValue(schema, this.ns.aml.vocabularies.document.linkTarget);
+ if (linkTarget) {
+ // Find the actual schema definition in the AMF model
+ return this._findSchemaById(linkTarget['@id'] || linkTarget);
+ }
+
+ return schema;
+ }
+
+ /**
+ * Finds schema definition by ID in the AMF model
+ * @param {string} schemaId The ID of the schema to find
+ * @returns {Object} Schema definition or null if not found
+ */
+ _findSchemaById(schemaId) {
+ if (!this.amf || !schemaId) {
+ return null;
+ }
+
+ // Look through all documents in the AMF model
+ const documents = Array.isArray(this.amf) ? this.amf : [this.amf];
+
+ for (const doc of documents) {
+ // Check declares section
+ const declares = this._ensureArray(this._getValue(doc, this.ns.aml.vocabularies.document.declares));
+ if (declares && Array.isArray(declares)) {
+ for (const declaration of declares) {
+ if (declaration['@id'] === schemaId) {
+ return declaration;
+ }
+ }
+ }
+
+ // Check references section
+ const references = this._ensureArray(this._getValue(doc, this.ns.aml.vocabularies.document.references));
+ if (references && Array.isArray(references)) {
+ for (const reference of references) {
+ const found = this._findSchemaById(schemaId);
+ if (found) return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets example value from range (handles arrays, scalars, etc.)
+ * @param {Object} range AMF Range object
+ * @param {string} fieldName Field name for context
+ * @returns {any} Example value
+ */
+ _getExampleValueFromRange(range, fieldName) {
+ if (!range) {
+ return this._getExampleValueForType(null, fieldName);
+ }
+
+ // Check if it's an array
+ if (this._hasType(range, 'http://a.ml/vocabularies/shapes#ArrayShape')) {
+ const items = this._getValue(range, this.ns.aml.vocabularies.shapes.items);
+ if (items) {
+ const itemValue = this._getExampleValueFromRange(items, fieldName);
+ return [itemValue];
+ }
+ return [];
+ }
+
+ // Check if it's a scalar
+ if (this._hasType(range, 'http://a.ml/vocabularies/shapes#ScalarShape')) {
+ const dataType = this._getValue(range, this.ns.w3.shacl.datatype);
+ if (dataType) {
+ return this._getExampleValueForType(dataType['@id'] || dataType, fieldName);
+ }
+ }
+
+ // Fallback to default value based on field name
+ return this._getExampleValueForType(null, fieldName);
+ }
+
+ /**
+ * Checks if an object has a specific type
+ * @param {Object} obj AMF object
+ * @param {string} type Type URI to check
+ * @returns {boolean} True if object has the type
+ */
+ _hasType(obj, type) {
+ if (!obj || !obj['@type']) {
+ return false;
+ }
+ const types = Array.isArray(obj['@type']) ? obj['@type'] : [obj['@type']];
+ return types.includes(type);
+ }
+
+ /**
+ * Generates gRPC examples for request and response
+ * @returns {Object} Generated gRPC examples
+ */
+ _generateGrpcExamples() {
+ const expects = this._computeExpects(this.method);
+ const returns = this._computeReturns(this.method);
+
+ let requestExample = null;
+ let responseExample = null;
+
+ // Generate request example
+ if (expects) {
+ const requestPayloads = this.getPayloads(expects);
+ if (requestPayloads && requestPayloads.length > 0) {
+ requestExample = this.generateGrpcExample(requestPayloads[0]);
+ }
+ }
+
+ // Generate response example
+ if (returns && returns.length > 0) {
+ const responsePayloads = this.getPayloads(returns[0]);
+ if (responsePayloads && responsePayloads.length > 0) {
+ responseExample = this.generateGrpcExample(responsePayloads[0]);
+ }
+ }
+
+ return {
+ request: requestExample,
+ response: responseExample
+ };
+ }
+
+ /**
+ * Gets gRPC examples template
+ * @param {Object} grpcExamples Generated gRPC examples
+ * @returns {import('lit-html').TemplateResult} Template for gRPC examples
+ */
+ _getGrpcExamplesTemplate(grpcExamples) {
+ const serviceName = this._getServiceName();
+ const methodName = this._getMethodName();
+ const serverUrl = this._getGrpcServerUrl();
+
+ return html`
+
+
gRPC Examples
+
+ ${grpcExamples.request ? html`
+
+
Request JSON (Proto3)
+
+
${grpcExamples.request.json}
+
+
+
+
+
+
grpcurl Command
+
+
${grpcExamples.request.grpcurl}
+
+
+
+ ` : ''}
+
+ ${grpcExamples.response ? html`
+
+
Response JSON (Proto3)
+
+
${grpcExamples.response.json}
+
+
+
+ ` : ''}
+
+
+
Server Information
+
+
Service: ${serviceName}
+
Method: ${methodName}
+
Server: ${serverUrl}
+
+
+
+ `;
+ }
}
diff --git a/src/Styles.js b/src/Styles.js
index 4805190..d247977 100644
--- a/src/Styles.js
+++ b/src/Styles.js
@@ -333,4 +333,506 @@ api-security-documentation:last-of-type {
.async-method-security{
margin-top: 17px;
}
+
+/* Enhanced API Method Documentation Styles */
+
+/* Operation Summary */
+.operation-summary {
+ margin-bottom: 24px;
+}
+
+.operation-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.method-badges {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.method-badge {
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ color: white;
+ display: inline-block;
+}
+
+/* HTTP Method Colors */
+.method-badge.http-method-get {
+ background-color: var(--api-method-documentation-method-get-color, #61affe);
+}
+
+.method-badge.http-method-post {
+ background-color: var(--api-method-documentation-method-post-color, #49cc90);
+}
+
+.method-badge.http-method-put {
+ background-color: var(--api-method-documentation-method-put-color, #fca130);
+}
+
+.method-badge.http-method-delete {
+ background-color: var(--api-method-documentation-method-delete-color, #f93e3e);
+}
+
+.method-badge.http-method-patch {
+ background-color: var(--api-method-documentation-method-patch-color, #50e3c2);
+}
+
+.method-badge.http-method-head {
+ background-color: var(--api-method-documentation-method-head-color, #9012fe);
+}
+
+.method-badge.http-method-options {
+ background-color: var(--api-method-documentation-method-options-color, #0d5aa7);
+}
+
+/* gRPC Badge */
+.method-badge.grpc-badge {
+ background-color: var(--api-method-documentation-grpc-color, #4285f4);
+}
+
+.stream-badge {
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-size: 10px;
+ font-weight: 500;
+ background-color: var(--api-method-documentation-stream-badge-bg, #e8f0fe);
+ color: var(--api-method-documentation-stream-badge-color, #1a73e8);
+ border: 1px solid var(--api-method-documentation-stream-badge-border, #dadce0);
+}
+
+/* Enhanced Request/Response */
+.no-request-body {
+ color: var(--api-method-documentation-muted-color, #666);
+ font-style: italic;
+ margin: 8px 0;
+}
+
+.request-payload,
+.enhanced-parameters,
+.request-headers {
+ margin: 16px 0;
+}
+
+.payload-item {
+ border: 1px solid var(--api-method-documentation-border-color, #e0e0e0);
+ border-radius: 4px;
+ margin: 8px 0;
+ overflow: hidden;
+}
+
+.payload-header {
+ background-color: var(--api-method-documentation-payload-header-bg, #f5f5f5);
+ padding: 8px 12px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ border-bottom: 1px solid var(--api-method-documentation-border-color, #e0e0e0);
+}
+
+.media-type-badge {
+ padding: 2px 6px;
+ background-color: var(--api-method-documentation-media-type-bg, #e3f2fd);
+ color: var(--api-method-documentation-media-type-color, #1976d2);
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 500;
+}
+
+.schema-name {
+ font-weight: 600;
+ color: var(--api-method-documentation-schema-name-color, #333);
+}
+
+/* gRPC Message Styles */
+.grpc-message {
+ padding: 12px;
+}
+
+.schema-title {
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: var(--api-method-documentation-schema-title-color, #333);
+}
+
+.schema-description {
+ color: var(--api-method-documentation-description-color, #666);
+ margin-bottom: 12px;
+ font-size: 14px;
+}
+
+.message-fields {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.grpc-field {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 8px;
+ background-color: var(--api-method-documentation-field-bg, #fafafa);
+ border-radius: 3px;
+}
+
+.field-name {
+ font-weight: 500;
+ color: var(--api-method-documentation-field-name-color, #333);
+}
+
+.field-type {
+ color: var(--api-method-documentation-field-type-color, #666);
+ font-size: 12px;
+ font-family: var(--arc-font-code-family, monospace);
+}
+
+.required-badge {
+ padding: 1px 4px;
+ background-color: var(--api-method-documentation-required-bg, #ff5722);
+ color: white;
+ border-radius: 2px;
+ font-size: 10px;
+ font-weight: 500;
+}
+
+/* Enhanced Parameters */
+.parameter-group {
+ margin: 12px 0;
+}
+
+.parameter-group-title {
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: var(--api-method-documentation-param-group-color, #333);
+}
+
+.parameters-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.parameter-item {
+ border: 1px solid var(--api-method-documentation-border-color, #e0e0e0);
+ border-radius: 4px;
+ padding: 8px;
+}
+
+.parameter-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 4px;
+}
+
+.parameter-name {
+ font-weight: 500;
+ color: var(--api-method-documentation-param-name-color, #333);
+}
+
+.parameter-type {
+ color: var(--api-method-documentation-param-type-color, #666);
+ font-size: 12px;
+ font-family: var(--arc-font-code-family, monospace);
+}
+
+.parameter-description {
+ color: var(--api-method-documentation-description-color, #666);
+ font-size: 14px;
+ margin: 4px 0;
+}
+
+.parameter-default,
+.parameter-examples {
+ font-size: 12px;
+ color: var(--api-method-documentation-muted-color, #666);
+ margin: 2px 0;
+}
+
+.parameter-default code,
+.parameter-examples code {
+ background-color: var(--api-method-documentation-code-bg, #f5f5f5);
+ padding: 1px 3px;
+ border-radius: 2px;
+ font-family: var(--arc-font-code-family, monospace);
+}
+
+/* Examples */
+.payload-examples,
+.enhanced-examples {
+ margin: 16px 0;
+}
+
+.examples-title {
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: var(--api-method-documentation-examples-title-color, #333);
+}
+
+.example-item {
+ border: 1px solid var(--api-method-documentation-border-color, #e0e0e0);
+ border-radius: 4px;
+ margin: 8px 0;
+ overflow: hidden;
+}
+
+.example-header {
+ background-color: var(--api-method-documentation-example-header-bg, #f8f9fa);
+ padding: 6px 8px;
+ border-bottom: 1px solid var(--api-method-documentation-border-color, #e0e0e0);
+}
+
+.example-name {
+ font-weight: 500;
+ font-size: 12px;
+ color: var(--api-method-documentation-example-name-color, #333);
+}
+
+.example-content {
+ position: relative;
+}
+
+.example-content pre {
+ margin: 0;
+ padding: 12px;
+ background-color: var(--api-method-documentation-code-bg, #f8f9fa);
+ overflow-x: auto;
+ font-size: 12px;
+ line-height: 1.4;
+}
+
+.example-content code {
+ font-family: var(--arc-font-code-family, monospace);
+ color: var(--api-method-documentation-code-color, #333);
+}
+
+.show-full-example {
+ position: absolute;
+ bottom: 8px;
+ right: 8px;
+ font-size: 11px;
+ padding: 4px 8px;
+}
+
+/* Enhanced Security */
+.enhanced-security {
+ margin: 24px 0;
+}
+
+.security-requirements {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+/* Enhanced Metadata */
+.enhanced-metadata {
+ margin: 24px 0;
+}
+
+.metadata-content {
+ padding: 16px;
+}
+
+.metadata-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin: 8px 0;
+}
+
+.metadata-item.deprecated {
+ align-items: center;
+}
+
+.metadata-label {
+ font-weight: 600;
+ color: var(--api-method-documentation-metadata-label-color, #333);
+ min-width: 120px;
+}
+
+.metadata-value {
+ color: var(--api-method-documentation-metadata-value-color, #666);
+ font-family: var(--arc-font-code-family, monospace);
+ font-size: 13px;
+}
+
+.metadata-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+}
+
+.tag-badge {
+ padding: 2px 6px;
+ background-color: var(--api-method-documentation-tag-bg, #e8f5e8);
+ color: var(--api-method-documentation-tag-color, #2e7d32);
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 500;
+}
+
+.deprecated-badge {
+ padding: 2px 6px;
+ background-color: var(--api-method-documentation-deprecated-bg, #ffebee);
+ color: var(--api-method-documentation-deprecated-color, #c62828);
+ border-radius: 3px;
+ font-size: 11px;
+ font-weight: 500;
+ border: 1px solid var(--api-method-documentation-deprecated-border, #ffcdd2);
+}
+
+.server-list {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.server-item {
+ font-family: var(--arc-font-code-family, monospace);
+ font-size: 12px;
+ color: var(--api-method-documentation-server-color, #666);
+ padding: 2px 4px;
+ background-color: var(--api-method-documentation-server-bg, #f5f5f5);
+ border-radius: 2px;
+}
+
+/* gRPC Examples Styles */
+.grpc-examples {
+ margin: 24px 0;
+}
+
+.example-section {
+ margin: 16px 0;
+}
+
+.example-snippet {
+ position: relative;
+ border: 1px solid var(--api-method-documentation-border-color, #e0e0e0);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.example-snippet pre {
+ margin: 0;
+ padding: 16px;
+ background-color: var(--api-method-documentation-code-bg, #f8f9fa);
+ overflow-x: auto;
+ font-family: var(--arc-font-code-family, 'Consolas', 'Monaco', 'Courier New', monospace);
+ font-size: 13px;
+ line-height: 1.4;
+}
+
+.example-snippet code {
+ color: var(--api-method-documentation-code-color, #333);
+}
+
+.grpcurl-command pre {
+ background-color: var(--api-method-documentation-terminal-bg, #2d3748);
+}
+
+.grpcurl-command code {
+ color: var(--api-method-documentation-terminal-color, #e2e8f0);
+}
+
+.example-snippet clipboard-copy {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ --anypoint-button-background-color: var(--api-method-documentation-copy-button-bg, #ffffff);
+ --anypoint-button-color: var(--api-method-documentation-copy-button-color, #333);
+}
+
+.server-info {
+ margin: 20px 0;
+ padding: 16px;
+ background-color: var(--api-method-documentation-info-bg, #f0f8ff);
+ border: 1px solid var(--api-method-documentation-info-border, #b3d9ff);
+ border-radius: 4px;
+}
+
+.server-details p {
+ margin: 4px 0;
+ font-size: 14px;
+}
+
+.server-details strong {
+ color: var(--api-method-documentation-label-color, #333);
+}
+
+.heading4 {
+ font-size: 16px;
+ font-weight: 600;
+ margin: 8px 0;
+ color: var(--api-method-documentation-heading4-color, #333);
+}
+
+/* Enhanced gRPC Badge Styles */
+.grpc-badge {
+ background: linear-gradient(135deg, #4285f4 0%, #34a853 100%) !important;
+ color: white !important;
+ box-shadow: 0 2px 4px rgba(66, 133, 244, 0.3);
+}
+
+.stream-badge {
+ background: linear-gradient(135deg, #e8f0fe 0%, #d2e3fc 100%);
+ color: #1a73e8;
+ border: 1px solid #dadce0;
+ font-weight: 600;
+ text-transform: capitalize;
+}
+
+/* gRPC Message Field Enhancements */
+.grpc-field {
+ border-left: 3px solid var(--api-method-documentation-grpc-accent, #4285f4);
+}
+
+.grpc-field .field-type {
+ background-color: var(--api-method-documentation-type-bg, #f1f3f4);
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-weight: 500;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .operation-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .method-badges {
+ align-self: flex-start;
+ }
+
+ .parameter-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 4px;
+ }
+
+ .metadata-item {
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ .metadata-label {
+ min-width: auto;
+ }
+
+ .example-snippet clipboard-copy {
+ position: relative;
+ top: auto;
+ right: auto;
+ margin: 8px;
+ }
+}
`;