WDP Part 9c: Implementation Guide
Practical implementation guidance for the WDP catalog system with code examples for generating catalogs, loading them on clients, and expanding compact diagnostics.
Abstract#
This document provides practical implementation guidance for the Waddling Diagnostic Protocol (WDP) catalog system. It includes code examples for generating catalogs, loading them on clients, and expanding compact diagnostics.
Key Points:
- Server-side: Generate catalogs from error registries
- Client-side: Load catalogs and expand compact diagnostics
- Testing: Validate catalogs and test expansion
- Multi-language: Rust, Python, TypeScript examples
Conformance
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
1. Introduction#
1.1 Implementation Checklist
Server-Side:
Client-Side:
1.2 Architecture Overview
ββββββββββββββββββββ
β Error Registry β (Source code, config)
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β Catalog Generatorβ (Build-time tool)
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β catalog.json β (Artifact)
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β CDN / Server β (Distribution)
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ ββββββββββββββββββββ
β Client Cache β ββββΊ β Server API β
ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ
β β
β {"xY9Kp":{"f":{...}}} β
β βββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ
β Expanded Message β
β "Token expiredβ¦" β
ββββββββββββββββββββ2. Server-Side Implementation#
2.1 Catalog Generation (Rust)
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
use xxhash_rust::xxh64::xxh64;
// Compute namespace hash using xxHash64 with seed "wdp-ns-v1"
fn compute_namespace_hash(namespace: &str) -> String {
let seed = xxh64(b"wdp-ns-v1", 0);
let hash = xxh64(namespace.as_bytes(), seed);
base62_encode(hash)
}
fn base62_encode(mut num: u64) -> String {
const CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let mut result = String::new();
while num > 0 || result.is_empty() {
result.insert(0, CHARS[(num % 62) as usize] as char);
num /= 62;
}
// Pad to 5 characters
while result.len() < 5 {
result.insert(0, '0');
}
result.chars().take(5).collect()
}
#[derive(Serialize, Deserialize)]
struct ErrorEntry {
code: String,
severity: String,
message: String,
description: Option<String>,
hints: Vec<String>,
tags: Vec<String>,
fields: Vec<String>,
}
struct CatalogGenerator {
version: String,
namespace: Option<String>,
namespace_hash: Option<String>,
diags: HashMap<String, ErrorEntry>,
}
impl CatalogGenerator {
fn new(version: &str) -> Self {
Self {
version: version.to_string(),
namespace: None,
namespace_hash: None,
diags: HashMap::new(),
}
}
fn with_namespace(mut self, namespace: &str) -> Self {
self.namespace_hash = Some(compute_namespace_hash(namespace));
self.namespace = Some(namespace.to_string());
self
}
fn add_error(&mut self, compact_id: &str, entry: ErrorEntry) {
self.diags.insert(compact_id.to_string(), entry);
}
fn generate_compact(&self) -> String {
let mut compact_diags = serde_json::Map::new();
for (hash, entry) in &self.diags {
compact_diags.insert(hash.clone(), json!({
"c": entry.code,
"s": entry.severity,
"m": entry.message,
"d": entry.description,
"h": entry.hints,
"t": entry.tags,
"f": entry.fields,
}));
}
let catalog = json!({
"v": self.version,
"wd": compact_diags
});
serde_json::to_string(&catalog).unwrap()
}
}
// Usage
fn main() {
let mut generator = CatalogGenerator::new("1.0.0");
generator.add_error("jGKFp", ErrorEntry {
code: "E.AUTH.TOKEN.001".to_string(),
severity: "E".to_string(),
message: "Token missing from Authorization header".to_string(),
description: Some("The JWT token was not provided.".to_string()),
hints: vec!["Include header: Authorization: Bearer <token>".to_string()],
tags: vec!["auth".to_string(), "security".to_string()],
fields: vec![],
});
let catalog_json = generator.generate_compact();
std::fs::write("catalog.json", catalog_json).unwrap();
}2.2 Catalog Generation (Python)
import json
from typing import Dict, List, Optional
from dataclasses import dataclass, asdict
import xxhash
def compute_namespace_hash(namespace: str) -> str:
"""Generate 5-character Base62 hash for namespace."""
seed_hash = xxhash.xxh64(b"wdp-ns-v1").intdigest()
ns_hash = xxhash.xxh64(namespace.encode('utf-8'), seed=seed_hash).intdigest()
return base62_encode(ns_hash)[:5]
def base62_encode(num: int) -> str:
"""Encode number as Base62 string."""
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
if num == 0:
return "00000"
result = ""
while num > 0:
result = chars[num % 62] + result
num //= 62
return result.zfill(5)[:5]
@dataclass
class ErrorEntry:
code: str
severity: str
message: str
description: Optional[str] = None
hints: List[str] = None
tags: List[str] = None
fields: List[str] = None
def __post_init__(self):
self.hints = self.hints or []
self.tags = self.tags or []
self.fields = self.fields or []
class CatalogGenerator:
def __init__(self, version: str, namespace: Optional[str] = None):
self.version = version
self.namespace = namespace
self.namespace_hash = compute_namespace_hash(namespace) if namespace else None
self.diags: Dict[str, ErrorEntry] = {}
def add_error(self, compact_id: str, entry: ErrorEntry):
self.diags[compact_id] = entry
def generate_compact(self) -> str:
compact_diags = {}
for cid, entry in self.diags.items():
compact_diags[cid] = {
"c": entry.code,
"s": entry.severity,
"m": entry.message,
"d": entry.description,
"h": entry.hints,
"t": entry.tags,
"f": entry.fields
}
catalog = {"v": self.version, "wd": compact_diags}
return json.dumps(catalog, separators=(',', ':'))
# Usage
generator = CatalogGenerator("1.0.0")
generator.add_error("jGKFp", ErrorEntry(
code="E.AUTH.TOKEN.001",
severity="E",
message="Token missing from Authorization header",
description="The JWT token was not provided.",
hints=["Include header: Authorization: Bearer <token>"],
tags=["auth", "security"],
fields=[]
))
catalog_json = generator.generate_compact()
with open("catalog.json", "w") as f:
f.write(catalog_json)2.3 Generating Diagnostic Responses (TypeScript)
interface Diagnostic {
compactId: string; // CompactID (5 chars) or CombinedID (11 chars)
fields?: Record<string, any>;
}
class DiagnosticResponse {
/**
* Generate WDP diagnostic response
* Use standalone format when diagnostics are the only content
*/
static standalone(diagnostics: Diagnostic[]): object {
const response: Record<string, any> = {};
for (const diag of diagnostics) {
if (diag.fields && Object.keys(diag.fields).length > 0) {
response[diag.compactId] = { f: diag.fields };
} else {
response[diag.compactId] = {};
}
}
return response;
}
/**
* Generate WDP diagnostic response mixed with application data
* Use "wd" wrapper to avoid key conflicts
*/
static withData(data: object, diagnostics: Diagnostic[]): object {
const wd: Record<string, any> = {};
for (const diag of diagnostics) {
if (diag.fields && Object.keys(diag.fields).length > 0) {
wd[diag.compactId] = { f: diag.fields };
} else {
wd[diag.compactId] = {};
}
}
return { ...data, wd };
}
}
// Usage - Express.js example
app.post('/api/login', async (req, res) => {
const errors: Diagnostic[] = [];
if (!req.body.email) {
errors.push({
compactId: 'aG8eT',
fields: { field: 'email' }
});
}
if (!req.body.password || req.body.password.length < 8) {
errors.push({
compactId: 'mN3Yr',
fields: { field: 'password', min_length: 8 }
});
}
if (errors.length > 0) {
return res.status(422).json(
DiagnosticResponse.standalone(errors)
);
}
res.json({ token: '...' });
});3. Client-Side Implementation#
3.1 Catalog Loading (TypeScript)
interface CatalogEntry {
code: string;
severity: string;
message: string;
description?: string;
hints?: string[];
tags?: string[];
fields?: string[];
}
interface Catalog {
version: string;
diags: Record<string, CatalogEntry>;
}
interface ExpandedDiagnostic {
compactId: string;
code: string;
severity: string;
message: string;
description?: string;
hints?: string[];
}
class WDPCatalog {
private catalog: Catalog | null = null;
async load(url: string): Promise<void> {
const response = await fetch(url);
this.catalog = await response.json();
}
/**
* Expand diagnostics from wire protocol format
* Handles both standalone and "wd" wrapped formats
*/
expand(response: any): ExpandedDiagnostic[] {
if (!this.catalog) {
throw new Error("Catalog not loaded");
}
// Extract diagnostics (handle both formats)
let diagnostics: Record<string, any>;
if (response.wd) {
diagnostics = response.wd;
} else {
diagnostics = {};
for (const [key, value] of Object.entries(response)) {
// Match CompactID (5 chars) or CombinedID (11 chars)
if (/^[0-9A-Za-z]{5}(-[0-9A-Za-z]{5})?$/.test(key)) {
diagnostics[key] = value;
}
}
}
const results: ExpandedDiagnostic[] = [];
for (const [wireKey, data] of Object.entries(diagnostics)) {
const entry = this.lookupDiag(wireKey);
if (!entry) {
results.push({
compactId: wireKey,
code: 'UNKNOWN',
severity: 'E',
message: `Unknown diagnostic: ${wireKey}`
});
continue;
}
let message = entry.message;
// Interpolate fields
if (data.f) {
for (const [field, value] of Object.entries(data.f)) {
message = message.replace(`{{${field}}}`, String(value));
}
}
results.push({
compactId: wireKey,
code: entry.code,
severity: entry.severity,
message,
description: entry.description,
hints: entry.hints
});
}
return results;
}
private lookupDiag(wireKey: string): CatalogEntry | null {
if (!this.catalog) return null;
// Direct lookup
if (this.catalog.diags[wireKey]) {
return this.catalog.diags[wireKey];
}
// Handle over-qualified (CombinedID β single-namespace)
if (wireKey.includes('-')) {
const [nsHash, compactId] = wireKey.split('-');
return this.catalog.diags[compactId] || null;
}
return null;
}
}
// Usage
const catalog = new WDPCatalog();
await catalog.load('https://cdn.example.com/catalog.json');
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email: '', password: '123' })
});
const diagnostics = catalog.expand(await response.json());
diagnostics.forEach(diag => {
const icon = {
'E': 'β', 'W': 'β οΈ', 'C': 'π΄', 'I': 'βΉοΈ', 'H': 'π‘'
}[diag.severity] || 'β’';
console.log(`${icon} ${diag.severity}: ${diag.message}`);
});3.2 Catalog Loading (Python)
import requests
import json
from typing import Dict, List, Optional
class WDPCatalog:
def __init__(self):
self.catalog: Optional[Dict] = None
def load(self, url: str):
"""Load catalog from URL"""
response = requests.get(url)
self.catalog = response.json()
def load_file(self, path: str):
"""Load catalog from local file"""
with open(path, 'r') as f:
self.catalog = json.load(f)
def expand(self, response: Dict) -> List[Dict]:
"""Expand diagnostics from wire protocol format"""
if not self.catalog:
raise ValueError("Catalog not loaded")
# Extract diagnostics
if 'wd' in response:
diagnostics = response['wd']
else:
diagnostics = {
k: v for k, v in response.items()
if len(k) == 5 and k.isalnum()
}
results = []
for compact_id, data in diagnostics.items():
entry = self.catalog['diags'].get(compact_id)
if not entry:
results.append({
'compact_id': compact_id,
'code': 'UNKNOWN',
'severity': 'E',
'message': f"Unknown diagnostic: {compact_id}"
})
continue
message = entry['message']
# Interpolate fields
if 'f' in data:
for field, value in data['f'].items():
message = message.replace(f"{{{{{field}}}}}", str(value))
results.append({
'compact_id': compact_id,
'code': entry['code'],
'severity': entry['severity'],
'message': message,
'description': entry.get('description'),
'hints': entry.get('hints', [])
})
return results
# Usage
catalog = WDPCatalog()
catalog.load('https://cdn.example.com/catalog.json')
response = requests.post('/api/login', json={
'email': '',
'password': '123'
})
diagnostics = catalog.expand(response.json())
severity_icons = {'E': 'β', 'W': 'β οΈ', 'C': 'π΄', 'I': 'βΉοΈ', 'H': 'π‘'}
for diag in diagnostics:
icon = severity_icons.get(diag['severity'], 'β’')
print(f"{icon} {diag['severity']}: {diag['message']}")3.3 Browser Implementation with Caching
class WDPCatalogClient {
constructor(catalogUrl, cacheKey = 'wdp-catalog') {
this.catalogUrl = catalogUrl;
this.cacheKey = cacheKey;
this.catalog = null;
}
async load() {
// Try to load from cache first
const cached = this.loadFromCache();
if (cached) {
this.catalog = cached;
// Update in background
this.updateCache().catch(console.error);
return;
}
await this.updateCache();
}
loadFromCache() {
try {
const cached = localStorage.getItem(this.cacheKey);
if (!cached) return null;
const data = JSON.parse(cached);
// Check if cache is still valid (< 7 days old)
const age = Date.now() - data.timestamp;
if (age > 7 * 24 * 60 * 60 * 1000) {
return null;
}
return data.catalog;
} catch (e) {
console.error('Failed to load cached catalog:', e);
return null;
}
}
async updateCache() {
const response = await fetch(this.catalogUrl);
this.catalog = await response.json();
localStorage.setItem(this.cacheKey, JSON.stringify({
catalog: this.catalog,
timestamp: Date.now()
}));
}
expand(response) {
if (!this.catalog) {
throw new Error('Catalog not loaded');
}
let diagnostics;
if (response.wd) {
diagnostics = response.wd;
} else {
diagnostics = {};
for (const [key, value] of Object.entries(response)) {
if (/^[0-9A-Za-z]{5}(-[0-9A-Za-z]{5})?$/.test(key)) {
diagnostics[key] = value;
}
}
}
const results = [];
for (const [wireKey, data] of Object.entries(diagnostics)) {
const entry = this.catalog.diags[wireKey];
if (!entry) {
results.push({
compactId: wireKey,
severity: 'E',
message: `Unknown diagnostic: ${wireKey}`
});
continue;
}
let message = entry.message;
if (data.f) {
for (const [field, value] of Object.entries(data.f)) {
message = message.replace(`{{${field}}}`, value);
}
}
results.push({
compactId: wireKey,
code: entry.code,
severity: entry.severity,
message,
hints: entry.hints
});
}
return results;
}
}4. Testing#
4.1 Catalog Validation Tests (Python)
import pytest
import json
import re
def test_catalog_structure():
"""Test catalog has required fields and valid structure"""
with open('catalog.json') as f:
catalog = json.load(f)
# Required top-level fields
assert 'version' in catalog
assert 'diags' in catalog
# Version format
assert re.match(r'^\d+\.\d+\.\d+$', catalog['version'])
def test_catalog_entries():
"""Test each catalog entry is valid"""
with open('catalog.json') as f:
catalog = json.load(f)
for compact_id, entry in catalog['diags'].items():
# Compact ID format
assert len(compact_id) == 5
assert re.match(r'^[0-9A-Za-z]{5}$', compact_id)
# Required fields
assert 'code' in entry
assert 'severity' in entry
assert 'message' in entry
# Severity matches code
assert entry['code'].startswith(entry['severity'])
def test_field_placeholders():
"""Test message placeholders match fields list"""
with open('catalog.json') as f:
catalog = json.load(f)
for compact_id, entry in catalog['diags'].items():
message = entry['message']
fields = entry.get('fields', [])
# Extract placeholders from message
placeholders = re.findall(r'\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}', message)
# All placeholders should be in fields list
for placeholder in placeholders:
assert placeholder in fields
def test_key_format_consistency():
"""Test catalog doesn't mix CompactID and CombinedID formats"""
with open('catalog.json') as f:
catalog = json.load(f)
has_hyphen = any('-' in key for key in catalog['diags'].keys())
no_hyphen = all('-' not in key for key in catalog['diags'].keys())
assert has_hyphen or no_hyphen, \
"Catalog cannot mix CompactID and CombinedID formats"4.2 Expansion Tests (TypeScript)
import { describe, it, expect } from 'vitest';
describe('WDPCatalog', () => {
it('should expand diagnostic with fields', () => {
const catalog = new WDPCatalog();
catalog.catalog = {
version: '1.0.0',
diags: {
'xY9Kp': {
code: 'E.AUTH.TOKEN.EXPIRED',
severity: 'E',
message: 'Token expired at {{timestamp}}'
}
}
};
const response = {
'xY9Kp': { f: { timestamp: '2024-01-15T10:30:00Z' } }
};
const results = catalog.expand(response);
expect(results).toHaveLength(1);
expect(results[0].message).toBe('Token expired at 2024-01-15T10:30:00Z');
});
it('should handle "wd" wrapper format', () => {
const catalog = new WDPCatalog();
catalog.catalog = {
version: '1.0.0',
diags: {
'wN4Qm': {
code: 'W.STORAGE.QUOTA.APPROACHING',
severity: 'W',
message: 'Storage quota at {{quota_used}}/{{quota_limit}} MB'
}
}
};
const response = {
data: { id: '12345' },
wd: {
'wN4Qm': { f: { quota_used: '850', quota_limit: '1000' } }
}
};
const results = catalog.expand(response);
expect(results).toHaveLength(1);
expect(results[0].message).toBe('Storage quota at 850/1000 MB');
});
it('should handle unknown compact ID', () => {
const catalog = new WDPCatalog();
catalog.catalog = { version: '1.0.0', diags: {} };
const response = { 'ZZZZZ': {} };
const results = catalog.expand(response);
expect(results).toHaveLength(1);
expect(results[0].message).toContain('Unknown diagnostic');
});
});5. Best Practices#
5.1 Catalog Generation
Recommended practices:
- Generate catalogs at build time (not runtime)
- Version catalogs with semantic versioning
- Use Compact format for production
- Host on CDN with long cache headers
- Generate separate catalogs per language
- Validate catalog structure before deploying
Practices to avoid:
- Generating catalogs on-the-fly for each request
- Embedding sensitive data in catalogs (they're public)
- Changing catalog content without bumping version
- Making catalogs too large (keep < 500KB)
5.2 Client-Side Loading
Recommended practices:
- Cache catalogs locally (localStorage, IndexedDB)
- Load catalog asynchronously at app startup
- Handle catalog loading failures gracefully
- Update catalogs in the background
- Validate catalog before using
Practices to avoid:
- Blocking app initialization on catalog load
- Loading catalog on every page load
- Assuming catalog is always available
- Caching catalogs indefinitely without updates
5.3 Diagnostic Generation
Recommended practices:
- Use standalone format for pure diagnostic responses
- Use "wd" wrapper when mixing with application data
- Include all necessary fields for interpolation
- Set appropriate HTTP status codes
5.4 Error Handling
Recommended practices:
- Handle unknown compact IDs gracefully
- Display fallback message if catalog unavailable
- Log catalog loading failures
- Provide clear error messages to users
- Allow manual catalog refresh
5.5 Namespace Usage
Choosing Catalog Type:
Namespace Naming Best Practices:
- Use descriptive, service-specific names:
auth_service,payment_lib - Use lowercase with underscores
- Keep names stable (hash doesn't change)
- Document namespace meanings
Privacy Considerations:
Include namespaces index when:
- β Internal tools (ops dashboards, monitoring)
- β Human-readable attribution needed
- β Service names not sensitive
Omit namespaces index when:
- β Public-facing applications
- β Service architecture is confidential
- β Privacy-preserving mode desired
5.6 Performance
Recommended practices:
- Compress catalogs (gzip/brotli)
- Use CDN for catalog distribution
- Cache catalog lookups in memory
- Lazy-load catalog if not immediately needed
- Minify JSON in production
Practices to avoid:
- Parsing catalog on every diagnostic
- Loading multiple catalogs simultaneously
- Including unnecessary metadata in production catalogs
- Re-downloading catalog unnecessarily
Appendix A: Complete Example Application#
// Complete example: React application with WDP
import React, { useEffect, useState } from 'react';
// Initialize catalog client
const catalogClient = new WDPCatalogClient(
'https://cdn.example.com/catalog.json'
);
function App() {
const [catalogLoaded, setCatalogLoaded] = useState(false);
useEffect(() => {
catalogClient.load()
.then(() => setCatalogLoaded(true))
.catch(console.error);
}, []);
return (
<div>
{catalogLoaded ? (
<LoginForm catalog={catalogClient} />
) : (
<div>Loading...</div>
)}
</div>
);
}
function LoginForm({ catalog }) {
const [diagnostics, setDiagnostics] = useState([]);
async function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: formData.get('email'),
password: formData.get('password')
})
});
const body = await response.json();
if (!response.ok) {
const expanded = catalog.expand(body);
setDiagnostics(expanded);
} else {
window.location.href = '/dashboard';
}
}
return (
<form onSubmit={handleSubmit}>
<DiagnosticList diagnostics={diagnostics} />
<input type="email" name="email" placeholder="Email" />
<input type="password" name="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
);
}
function Diagnostic({ diagnostic }) {
const config = {
'E': { icon: 'β', className: 'error' },
'W': { icon: 'β οΈ', className: 'warning' },
'C': { icon: 'π΄', className: 'critical' },
'I': { icon: 'βΉοΈ', className: 'info' },
'H': { icon: 'π‘', className: 'help' }
};
const { icon, className } = config[diagnostic.severity] || config['E'];
return (
<div className={`diagnostic ${className}`}>
<div className="diagnostic-header">
<span className="icon">{icon}</span>
<span className="message">{diagnostic.message}</span>
</div>
{diagnostic.hints?.length > 0 && (
<ul className="hints">
{diagnostic.hints.map((hint, i) => (
<li key={i}>π‘ {hint}</li>
))}
</ul>
)}
</div>
);
}References#
Related Specifications:
- Part 9a: Catalog Format - JSON catalog file format
- Part 9b: Wire Protocol - HTTP transmission format
- Part 5: Compact IDs - Hash generation algorithm
- Part 7: Namespaces - Namespace hashing