LLMApi: OpenAPI Dynamic Mock Generator: Charger n'importe quel spectreM SK2 Mock Any API
NOTE: Cet article est principalement AI généré dans le cadre de mon paquet nuget en tant que documentation de diffusion.
Introduction
Avez-vous déjà besoin de tester contre une API qui n’était pas encore prête' ? Ou avez-vous voulu développer en mode offline sans dépasser les limites de vitesse? Le Generator Mock Dynamic OpenAPI vous permet de charger toute spécification OpenAPI et crée instantanément un API mock fonctionnel intégralement structuré avec des données réalistes?
Aucun fichier de configuration. Aucune création manuelle d'un point final. Just pointer sur une spécification OpenAPI et commencer à faire des demandesM SK2
Vous pouvez trouver le GitHub ici pour le projet, tous les domaines publics, etc.

Le problème: Développement des blocs de dépendance API
Les applications modernes dépendent de dizaines d’API externes. Au cours du développementM SK1 vous êtes confrontés à plusieurs défis :
graph TD
A[Your App] --> B{External API}
B -->|Not Ready| C[Development Blocked]
B -->|Rate Limited| D[Can't Test Freely]
B -->|Requires Auth| E[Complex Setup]
B -->|Expensive| F[Cost Concerns]
B -->|Unreliable| G[Flaky Tests]
Solutions traditionnelles impliquent:
- Écrire manuellement les réponses de simulation (nuisible, devient outdatéM SK2
- Enregistrement/replaying HTTP traffic (brittle, difficile à entretenir
- Utilisation d'installations codées (non réalistes , ne couvre pas les cases de bordsM SK2
La solution: Mocking dynamique OpenAPI
Pointez le système à n’importe quel spec OpenAPI, et il apparaîtra automatiquement:
- Parse la spécification
- Discovers all endpoints
- génère des données de simulation réalistes à l'aide d'un LLM
- Serve l'API de modèle sur votre machine locale
sequenceDiagram
participant Dev as Developer
participant System as Mock System
participant Spec as OpenAPI Spec
participant LLM as Local LLM
Dev->>System: Load spec from URL/file
System->>Spec: Parse OpenAPI document
Spec-->>System: Endpoints, schemas, descriptions
System->>System: Register dynamic routes
Dev->>System: GET /petstore/pet/123
System->>LLM: Generate data for "Pet" schema
LLM-->>System: Realistic pet data
System-->>Dev: {"id": 123, "name": "Max", ...}
Comment cela fonctionne
Aperçu de l'architecture
Le système OpenAPI se compose de plusieurs composants coordonnés:
graph TB
A[HTTP Request] --> B{Route Matches?}
B -->|No| C[404 Not Found]
B -->|Yes| D[DynamicOpenApiManager]
D --> E[Find Matching Endpoint]
E --> F[OpenApiRequestHandler]
F --> G[Extract Schema from Spec]
G --> H[Build LLM Prompt]
H --> I[PromptBuilder]
I --> J[Include Context?]
J -->|Yes| K[OpenApiContextManager]
J -->|No| L[LLM Client]
K --> L
L --> M[Get Response]
M --> N[JsonExtractor]
N --> O[Return Mock Data]
Components clés:
- DynamicOpenApiManager - Gestion des spécifications chargées et du parallèle de la route
- OpenApiSpecLoader - Extraire et analyser les documents OpenAPI
- OpenApiRequestHandler - Génie des réponses pour les points de fin correspondants
- PromptBuilder - Crée des prompts LLM à partir de schémas OpenAPI
- OpenApiContextManager - Maintien de la cohérence entre les appels optionnel
Chargement des spécifications
Les spécifications peuvent être chargées de trois sources:
1. URL à distance
POST /api/openapi/specs
Content-Type: application/json
{
"name": "petstore",
"source": "https://petstore3.swagger.io/api/v3/openapi.json",
"basePath": "/petstore"
}
2. fichier local
POST /api/openapi/specs
Content-Type: application/json
{
"name": "my-api",
"source": "./specs/my-api.yaml",
"basePath": "/api/v1"
}
3. URL de données
POST /api/openapi/specs
Content-Type: application/json
{
"name": "inline-api",
"source": "data:application/json;base64,eyJvcGVuYXBpIjoiMy...",
"basePath": "/api"
}
Préciser le processus de chargement
Voici ce qui se passe lorsque vous chargez un spec:
public async Task<SpecLoadResult> LoadSpecAsync(
string name,
string source,
string? basePath = null,
string? contextName = null)
{
// 1. Use scoped service factory for OpenApiSpecLoader
using var scope = _scopeFactory.CreateScope();
var specLoader = scope.ServiceProvider
.GetRequiredService<OpenApiSpecLoader>();
// 2. Load the OpenAPI document
var document = await specLoader.LoadSpecAsync(source);
// 3. Determine base path from spec or parameter
var effectiveBasePath = basePath
?? document.Servers?.FirstOrDefault()?.Url
?? "/api";
// 4. Store spec with configuration
var config = new OpenApiSpecConfig
{
Name = name,
Source = source,
Document = document,
BasePath = effectiveBasePath,
ContextName = contextName,
LoadedAt = DateTimeOffset.UtcNow
};
_specs.AddOrUpdate(name, config, (_, __) => config);
// 5. Notify listeners via SignalR
await NotifySpecLoaded(name, effectiveBasePath);
return new SpecLoadResult
{
Name = name,
BasePath = effectiveBasePath,
EndpointCount = CountEndpoints(document),
Success = true
};
}
Correspondance dynamique des routes
Lorsque une demande arrive,, le système la compare à toutes les spécifications chargées:
public OpenApiEndpointMatch? FindMatchingEndpoint(string path, string method)
{
// Try each loaded spec
foreach (var spec in _specs.Values)
{
// Remove base path prefix
var relativePath = path;
if (path.StartsWith(spec.BasePath))
{
relativePath = path.Substring(spec.BasePath.Length);
}
// Find matching path in OpenAPI document
var (pathTemplate, operation) = FindOperation(
spec.Document,
relativePath,
method);
if (operation != null)
{
return new OpenApiEndpointMatch
{
Spec = spec,
PathTemplate = pathTemplate,
Operation = operation,
Method = ParseMethod(method)
};
}
}
return null;
}
Traitement des demandes
Une fois qu'un point final correspondant est trouvé,, le gestionnaire génère une réponse:
public async Task<string> HandleRequestAsync(
HttpContext context,
OpenApiDocument document,
string path,
OperationType method,
OpenApiOperation operation,
string? contextName = null,
CancellationToken cancellationToken = default)
{
// 1. Extract request body
var requestBody = await ReadRequestBodyAsync(context.Request);
// 2. Get success response schema
var shape = ExtractResponseSchema(operation);
// 3. Get context history if using contexts
var contextHistory = !string.IsNullOrWhiteSpace(contextName)
? _contextManager.GetContextForPrompt(contextName)
: null;
// 4. Build prompt from OpenAPI metadata
var description = operation.Summary ?? operation.Description;
var prompt = _promptBuilder.BuildPrompt(
method.ToString(),
path,
requestBody,
new ShapeInfo { Shape = shape },
streaming: false,
description: description,
contextHistory: contextHistory);
// 5. Get response from LLM
var rawResponse = await _llmClient.GetCompletionAsync(
prompt,
cancellationToken);
// 6. Extract clean JSON
var jsonResponse = JsonExtractor.ExtractJson(rawResponse);
// 7. Store in context if configured
if (!string.IsNullOrWhiteSpace(contextName))
{
_contextManager.AddToContext(
contextName,
method.ToString(),
path,
requestBody,
jsonResponse);
}
return jsonResponse;
}
Extraction du schéma
Le système extrait les schémas de réponse des définitions OpenAPI:
private string? ExtractResponseSchema(OpenApiOperation operation)
{
// Look for successful response (2xx)
var successResponse = operation.Responses
.FirstOrDefault(r => r.Key.StartsWith("2"))
.Value;
if (successResponse == null)
return null;
// Get JSON content
var jsonContent = successResponse.Content
.FirstOrDefault(c => c.Key.Contains("json"))
.Value;
if (jsonContent?.Schema == null)
return null;
// Convert OpenAPI schema to JSON Schema
return ConvertToJsonSchema(jsonContent.Schema);
}
private string ConvertToJsonSchema(OpenApiSchema schema)
{
// Recursively build JSON Schema representation
var builder = new StringBuilder();
builder.Append("{");
if (schema.Type != null)
{
builder.Append($"\"type\":\"{schema.Type}\"");
}
if (schema.Properties?.Count > 0)
{
builder.Append(",\"properties\":{");
var props = schema.Properties
.Select(p => $"\"{p.Key}\":{ConvertToJsonSchema(p.Value)}");
builder.Append(string.Join(",", props));
builder.Append("}");
}
if (schema.Items != null)
{
builder.Append(",\"items\":");
builder.Append(ConvertToJsonSchema(schema.Items));
}
builder.Append("}");
return builder.ToString();
}
Utilisation mondiale
Exemple: Petstore API
Laissez's marcher en ridiculisant l’API classique de Petstore:
Étape 1: Charger le spec
POST /api/openapi/specs
Content-Type: application/json
{
"name": "petstore",
"source": "https://petstore3.swagger.io/api/v3/openapi.json",
"basePath": "/petstore"
}
Réponse:
{
"name": "petstore",
"basePath": "/petstore",
"endpointCount": 19,
"endpoints": [
{"path": "/petstore/pet", "method": "POST"},
{"path": "/petstore/pet/{petId}", "method": "GET"},
{"path": "/petstore/pet/findByStatus", "method": "GET"},
...
],
"success": true
}
Étape 2: Utiliser les points de fin Mock
Maintenant, tous les points 19 sont disponibles
### Get a pet by ID
GET /petstore/pet/123
### Response (auto-generated):
{
"id": 123,
"name": "Max",
"category": {
"id": 1,
"name": "Dogs"
},
"photoUrls": [
"https://example.com/max1.jpg"
],
"tags": [
{"id": 1, "name": "friendly"},
{"id": 2, "name": "trained"}
],
"status": "available"
}
### Find pets by status
GET /petstore/pet/findByStatus?status=available
### Response (auto-generated array):
[
{
"id": 42,
"name": "Buddy",
"status": "available",
...
},
{
"id": 43,
"name": "Luna",
"status": "available",
...
}
]
Étape 3: Inspecter le spec
GET /api/openapi/specs/petstore
### Shows full details:
### - All endpoints
### - Load time
### - Context configuration
### - Base path
Étape 4: Recharger si la spécification change
POST /api/openapi/specs/petstore/reload
Étape 5: Retirer lorsque cela est fait
DELETE /api/openapi/specs/petstore
Plusieurs espèces simultanément
Vous pouvez charger plusieurs spécifications à la fois, chacune avec son propre chemin de base:
### Load Petstore at /petstore
POST /api/openapi/specs
{"name": "petstore", "source": "...", "basePath": "/petstore"}
### Load GitHub API at /github
POST /api/openapi/specs
{"name": "github", "source": "...", "basePath": "/github"}
### Load Stripe API at /stripe
POST /api/openapi/specs
{"name": "stripe", "source": "...", "basePath": "/stripe"}
### All three APIs now available simultaneously:
GET /petstore/pet/123
GET /github/users/octocat
GET /stripe/customers/cus_123
Specs avec contexte
Pour plus de réalisme, assigner un contexte à une spécification.
POST /api/openapi/specs
Content-Type: application/json
{
"name": "petstore",
"source": "https://petstore3.swagger.io/api/v3/openapi.json",
"basePath": "/petstore",
"contextName": "petstore-session"
}
Maintenant, tous les points finals de petstore partagent le même contexte:
### Create a pet
POST /petstore/pet
{"name": "Max", "status": "available"}
### Response: {"id": 42, "name": "Max", "status": "available"}
### Get the pet (will reference same ID and name)
GET /petstore/pet/42
### Response: {"id": 42, "name": "Max", "status": "available"}
### Notice: Consistent ID and name from context
Point final d'essai
Le point final de l'essai vous permet d'essayer un point final sans faire une demande réelle:
POST /api/openapi/test
Content-Type: application/json
{
"specName": "petstore",
"path": "/pet/123",
"method": "GET"
}
### Returns mock response without affecting routes
Ceci est utile pour:
- Prévision des réponses avant l'intégration
- Essai de points finals spécifiques en isolement
- Débugging des problèmes liés au schéma
L'interface de gestion
Pour la gestion visuelle, visite http://localhost:5116/OpenApi:
graph TD
A[OpenAPI Manager UI] --> B[Load Spec Section]
A --> C[Spec List]
A --> D[Context Viewer]
B --> E[URL Input]
B --> F[JSON Input]
B --> G[Context Configuration]
C --> H[Spec Card]
H --> I[Reload Button]
H --> J[Delete Button]
H --> K[View Endpoints]
D --> L[Active Contexts]
L --> M[Context Details]
L --> N[Clear Context]
Caractéristiques:
- Drag-andM SK1drop téléchargement de fichiers spéciaux
- La découverte d'un point final en direct - Voir tous les points de fin instantanément
- Un-essai de cliquer - Tester tout point final avec un bouton
- Notifications de temps réel- - SignalR fait des mises à jour lorsque les spécifications sont chargées
- Éclaircissement de la syntaxe - Gracieuse présentation de la réponse JSON
- Gestion du contexte - Voir et clarifier les contextes
Caractéristiques avancées
Paramètres du chemin
Les paramètres du chemin sont extraits automatiquement:
# OpenAPI Spec
/pet/{petId}:
get:
parameters:
- name: petId
in: path
schema:
type: integer
GET /petstore/pet/123
### LLM receives: "Generate data for Pet with petId=123"
### Response: {"id": 123, ...}
Paramètres de la demande
Les paramètres de la demande influent sur la réponse:
/pet/findByStatus:
get:
parameters:
- name: status
in: query
schema:
type: string
enum: [available, pending, sold]
GET /petstore/pet/findByStatus?status=available
### LLM receives: "Generate array of Pets with status=available"
### Response: [{"status": "available", ...}, ...]
Organismes de demande
POST/Les organes PUT sont inclus dans le prompt:
/pet:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
POST /petstore/pet
Content-Type: application/json
{"name": "Max", "status": "available"}
### LLM receives: "Generate response for creating Pet with name=Max, status=available"
### Response: {"id": 42, "name": "Max", "status": "available"}
Descriptions et sommaires
Les descriptions OpenAPI guident le LLM:
/pet/{petId}:
get:
summary: Find pet by ID
description: Returns a single pet based on the ID provided
Elles sont incluses dans le prompt, qui aide le LLM à comprendre l’objectif du point final'
Codes d'état de la réponse
Le système utilise la première réponse réussie :
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
'404':
description: Pet not found
Seule le schéma 200 est utilisé pour la génération de modèles.
SignalR Real-Mises à jour du temps
Lorsque les spécifications sont chargées, l’interface reçoit des notifications réelles de temps via SignalR:
public async Task NotifySpecLoaded(string name, string basePath)
{
await _hubContext.Clients.All.SendAsync("SpecLoaded", new
{
name,
basePath,
timestamp = DateTimeOffset.UtcNow
});
}
public async Task NotifySpecDeleted(string name)
{
await _hubContext.Clients.All.SendAsync("SpecDeleted", new
{
name,
timestamp = DateTimeOffset.UtcNow
});
}
Code de l'interface JavaScript:
const connection = new signalR.HubConnectionBuilder()
.withUrl('/hubs/openapi')
.build();
connection.on('SpecLoaded', (data) => {
showNotification(`Spec "${data.name}" loaded at ${data.basePath}`, 'success');
refreshSpecList();
});
connection.on('SpecDeleted', (data) => {
showNotification(`Spec "${data.name}" deleted`, 'info');
refreshSpecList();
});
Soutien au format
Le système supporte:
- OpenAPI 3.0.x
- OpenAPI 3.1.x
- Swagger 2.0
- Format JSON
- Format YAML
Les spécifications JSON et YAML sont automatiquement détectées et analysées.
Pratiques exemplaires
1. Utiliser des noms de spécifications descriptifs
Bad: {"name": "spec1", ...}
Good: {"name": "github-v3", ...}
2. Déterminer les voies de base appropriées
Éviter les conflits en utilisant des voies de base uniques:
### Good separation
/petstore/...
/github/...
/stripe/...
### Bad (conflicts!)
/api/... (multiple specs)
3. Recharger lorsque les spécifications changent
Si votre spécification OpenAPI est mise à jour, la chargez de nouveau:
POST /api/openapi/specs/my-api/reload
4. Utiliser des contextes pour les appels connexes
POST /api/openapi/specs
{
"name": "petstore",
"source": "...",
"contextName": "test-session"
}
### Now all petstore calls maintain consistency
5. Nettoyage après essai
Supprimer les spécifications que vous n’utilisez plus'
DELETE /api/openapi/specs/old-spec
Limitations
- Codes d'état de la réponse - Seules les réponses réussies 2xxM SK2 sont reprochées
- Authentification - Les en-têtes d'Auth sont acceptés mais non validés
- Validation - La demande de validation contre les schémas n'est pas appliquée
- État - Aucune base de données réelle
- Rendement - La génération de LLM ajoute la latence ~100-500ms par demande
Intégration avec les points finals de Mock régulier
Les spécifications OpenAPI fonctionnent en même temps que les ordinaires /api/mock Points finals:
### OpenAPI-based (from spec)
GET /petstore/pet/123
### Uses Pet schema from OpenAPI spec
### Regular mock (shape-based)
GET /api/mock/custom?shape={"id":0,"name":"string"}
### Uses explicit shape parameter
Les deux utilisent le même LLM sous-jacent mais diffèrent dans la façon dont le schéma est fourni.
Optimisation du rendement
Cachement
Les spécifications chargées sont cachées dans la mémoire:
private readonly ConcurrentDictionary<string, OpenApiSpecConfig> _specs = new();
Durée de vie du service
DynamicOpenApiManager est un tonne unique, de sorte que les spécifications demeurent chargées pour la durée de vie de l'applicationM SK1
Chargement de spécifications parallèles
Plusieurs spécifications peuvent être chargées en parallèle:
### Send these simultaneously
POST /api/openapi/specs {"name": "spec1", ...}
POST /api/openapi/specs {"name": "spec2", ...}
POST /api/openapi/specs {"name": "spec3", ...}
Tous les trois seront chargés en parallèle, non séquentiellementM SK1
Troubleshooting
Spec Won't Charge
Problème: Défaut de chargement des spécifications
Solutions:
- Vérifier si l'URL est accessible
- Vérifier si le fichier existe (pour les chemins locaux)
- Assurez-vous que JSON/YAML est valide
- Trouver des problèmes de CORS (pour les URLs éloignés)
Point final n'est pas trouvé
Problème: 404 sur le point final prévu
Solutions:
- Vérifier le chemin de base:
/petstore+/pet/123=/petstore/pet/123 - Vérifiez que la spécification définit effectivement ce chemin
- Assurez-vous que la méthode HTTP correspond (GET vs POST)
Réponse ne correspond pas à l’échéancier
Problème: Les données générées ne correspondent pas au schéma escompté
Solutions:
- Vérifier si le schéma dans la spécification est correct
- Vérifiez si vous regardez la réponse appropriée (200 vs 201)
- Rappelez-vous que la génération de LLM est probabiliste: non déterministe
Conclusion
L'OpenAPI Dynamic Mock Generator transforme toute spécification OpenAPI en une API à mock fonctionnel intégralement-dans quelques secondes
Il suffit de charger une spécification et de commencer à faire des demandes. Le LLM génère des données réalistes, variées qui correspondent aux schémas de votre entrepriseM SK2 ce qui la rend parfaite pour les besoins de l’entreprise :
- Développement de frontend alors que le backend est en cours de construction
- Essais d'intégration sans toucher des API réels
- Développement hors ligne lorsque la connectivité est limitée
- Essais de chargement sans préoccupations en matière de quota
- Prototypage de nouvelles fonctionnalités rapidement
En combinaison avec les contextes d’API, vous obtiendrez des API-moquettes de qualité qui assurent la cohérence entre les appels, offrant une simulation encore plus réaliste des systèmes de productionM SK2
L’avenir des API-moquettes est dynamique, intelligent, et appuyé par les mêmes LLM qui alimentent les outils de développement modernes . Bienvenue à la nouvelle ère du moquettement d’APIM SK3