LLMApi: OpenAPI Dynamic Mock Generator: Charger n'importe quelle spécification, Mock n'importe quelle API (Français (French))

LLMApi: OpenAPI Dynamic Mock Generator: Charger n'importe quelle spécification, Mock n'importe quelle API

Wednesday, 05 November 2025

//

16 minute read

REMARQUE: Cet article est principalement généré par l'IA dans le cadre de mon paquet nuget comme documentation de publication.

C'est assez intéressant donc je l'ai mis ici mais si c'est un problème pour vous s'il vous plaît ignorez-le.

Présentation

Avez-vous déjà eu besoin de tester contre une API qui n'était pas encore prête ?

Ou a voulu se développer hors ligne sans frapper les limites de taux? L'OpenAPI Dynamic Mock Generator vous permet de charger n'importe quelle spécification OpenAPI et crée instantanément une API simulée entièrement fonctionnelle avec des données LLM réalistes.

Pas de fichiers de configuration.Pas de création manuelle d'extrémités.Il suffit de le pointer à une spécification OpenAPI et commencer à faire des requêtes.

NuGet

NuGet

Vous pouvez trouver le

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]

GitHub ici

  • pour le projet, tout domaine public etc...
  • Gestionnaire OpenApi
  • Le problème : les dépendances de l'API bloquent le développement

Les applications modernes dépendent de dizaines d'API externes.

Au cours du développement, vous êtes confrontés à plusieurs défis :

  1. Les solutions traditionnelles comprennent:
  2. Écrire manuellement des réponses fictives (pédagogiques, deviennent obsolètes)
  3. Enregistrement/rejouage du trafic HTTP (faible, difficile à maintenir)
  4. Utilisation d'appareils codés en dur (inréaliste, ne couvre pas les boîtiers de bord)
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", ...}

La solution : le mocking dynamique OpenAPI

Pointez le système sur n'importe quelle spécification OpenAPI, et il automatiquement:

Analyse la spécification

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]

Découvre tous les paramètres

  1. Génére des données de simulation réalistes à l'aide d'un LLMServez l'API simulée sur votre machine locale
  2. Comment ça marcheVue d'ensemble de l'architecture
  3. **Le système OpenAPI se compose de plusieurs composantes coordonnées:**Composantes clés :
  4. DynamicOpenApiManager- Gère les spécifications chargées et les correspondances d'itinéraire
  5. OpenApiSpecLoader- Fiches et analyses des documents OpenAPI

OpenApiRequestHandler

  • Génére des réponses pour les paramètres appariés

PromptBuilder

POST /api/openapi/specs
Content-Type: application/json

{
  "name": "petstore",
  "source": "https://petstore3.swagger.io/api/v3/openapi.json",
  "basePath": "/petstore"
}

- Création d'invites LLM à partir de schémas OpenAPI

POST /api/openapi/specs
Content-Type: application/json

{
  "name": "my-api",
  "source": "./specs/my-api.yaml",
  "basePath": "/api/v1"
}

OpenApiContextManager

POST /api/openapi/specs
Content-Type: application/json

{
  "name": "inline-api",
  "source": "data:application/json;base64,eyJvcGVuYXBpIjoiMy...",
  "basePath": "/api"
}

- Maintient la cohérence entre les appels (facultatif)

Chargement des spécifications

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
    };
}

Les spécifications peuvent être chargées à partir de trois sources :

  1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.
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;
}

URL distante

  1. Le Président. — L'ordre du jour appelle le rapport (doc.
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;
}

Fichier local

  1. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.
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();
}

URL de données (encodée Base64)

Processus de chargement des spécifications

Voici ce qui se passe lorsque vous chargez une spécification:

Correspondance dynamique de la route

POST /api/openapi/specs
Content-Type: application/json

{
  "name": "petstore",
  "source": "https://petstore3.swagger.io/api/v3/openapi.json",
  "basePath": "/petstore"
}

Lorsqu'une requête arrive, le système la compare à toutes les spécifications chargées:

{
  "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
}

Traitement des demandes

Une fois qu'un paramètre correspondant est trouvé, le gestionnaire génère une réponse:

### 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",
    ...
  }
]

Extraction de schéma

GET /api/openapi/specs/petstore

### Shows full details:
### - All endpoints
### - Load time
### - Context configuration
### - Base path

Le système extrait les schémas de réponse des définitions OpenAPI :

POST /api/openapi/specs/petstore/reload

Utilisation réelle dans le monde

DELETE /api/openapi/specs/petstore

Exemple : API Petstore

Passons à pied en nous moquant de l'API classique de Petstore :

### 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

Étape 1: Chargez la spécification

Réponse :

POST /api/openapi/specs
Content-Type: application/json

{
  "name": "petstore",
  "source": "https://petstore3.swagger.io/api/v3/openapi.json",
  "basePath": "/petstore",
  "contextName": "petstore-session"
}

Étape 2: Utilisez les points d'extrémité Mock

### 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

Maintenant, les 19 paramètres sont disponibles:

Étape 3 : Inspecter la spécification

POST /api/openapi/test
Content-Type: application/json

{
  "specName": "petstore",
  "path": "/pet/123",
  "method": "GET"
}

### Returns mock response without affecting routes

Étape 4 : Recharger si la spécification change

  • Étape 5: Supprimer une fois fait
  • Caractéristiques multiples Simultanément
  • Vous pouvez charger plusieurs spécifications à la fois, chacune avec son propre chemin de base:

Spécifications avec contextes

Pour encore plus de réalisme, assignez un contexte à une spécification :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]

Maintenant, tous les paramètres Petstore partagent le même contexte:

  • Mise à l'essai du point d'extrémitéLe paramètre test vous permet d'essayer un paramètre sans faire une vraie demande:
  • **Ceci est utile pour:**Prévisualiser les réponses avant l'intégration
  • Essai de paramètres spécifiques isolésProblèmes de schéma de débogage
  • Régime d'assurance-chômage pour la gestionPour la gestion visuelle, visitez
  • **Caractéristiques:**Faire glisser-déposer
  • Téléchargement du fichier specDécouverte d'un endpoint en direct

- Voir tous les paramètres instantanément

Essais en un clic

  • Tester n'importe quel point d'arrêt avec un bouton
# 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, ...}

Notifications en temps réel

  • Mises à jour SignalR lorsque les spécifications sont chargées
/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", ...}, ...]

Mise en évidence de la syntaxe

  • Magnifique écran de réponse JSON
/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"}

Gestion du contexte

  • Vue et contextes clairs
/pet/{petId}:
  get:
    summary: Find pet by ID
    description: Returns a single pet based on the ID provided

Caractéristiques avancées

Paramètres de trajectoire

Les paramètres de chemin sont automatiquement extraits :

responses:
  '200':
    description: Successful operation
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Pet'
  '404':
    description: Pet not found

Paramètres de requête

Les paramètres de requête influencent la réponse:

Organismes demandeurs

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
    });
}

Les corps POST/PUT sont inclus dans l'invite:

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();
});

Descriptions et résumés

Les descriptions OpenAPI guident le LLM :

  • Ceux-ci sont inclus dans l'invite, aidant le LLM à comprendre le but du paramètre.
  • Codes d'état de la réponse
  • Le système utilise la première réponse réussie (2xx) :
  • Seul le schéma 200 est utilisé pour la génération simulée (404s ne sont pas actuellement simulés).
  • Mises à jour en temps réel du signalR

Lorsque les spécifications sont chargées/supprimées, l'interface utilisateur reçoit des notifications en temps réel via SignalR :

Code d'interface utilisateur JavaScript & #160;:

Support de format

 Bad:  {"name": "spec1", ...}
 Good: {"name": "github-v3", ...}

Le système prend en charge:

OpenAPI 3.0.x

### Good separation
/petstore/...
/github/...
/stripe/...

### Bad (conflicts!)
/api/... (multiple specs)

OpenAPI 3.1.x

Swagger 2.0

POST /api/openapi/specs/my-api/reload

Format JSON

POST /api/openapi/specs
{
  "name": "petstore",
  "source": "...",
  "contextName": "test-session"
}

### Now all petstore calls maintain consistency

Format YAML

Les spécifications JSON et YAML sont automatiquement détectées et analysées.

DELETE /api/openapi/specs/old-spec

Meilleures pratiques

  1. **1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.**Utiliser les noms de spécifications descriptifs
  2. **2. Le Président. — L'ordre du jour appelle le rapport (doc.**Définir les voies de base appropriées
  3. **Éviter les conflits en utilisant des chemins de base uniques :**3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.
  4. Recharger lorsque les spécifications changentSi votre spécification OpenAPI est mise à jour, rechargez-la :
  5. **4. Le Président. — L'ordre du jour appelle le rapport (doc.**Utiliser les contextes pour les appels connexes

5.

Nettoyer après l'essai/api/mockEnlever les spécifications que vous n'utilisez plus :

### 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

Limitations

Codes d'état de la réponse

- Seules les réponses réussies (2xx) sont truquées

Authentification

private readonly ConcurrentDictionary<string, OpenApiSpecConfig> _specs = new();

- Les en-têtes d'auth sont acceptés mais non validés

DynamicOpenApiManagerValidation

- La validation de la demande par rapport aux schémas n'est pas appliquée

État

### Send these simultaneously
POST /api/openapi/specs {"name": "spec1", ...}
POST /api/openapi/specs {"name": "spec2", ...}
POST /api/openapi/specs {"name": "spec3", ...}
  • Pas de base de données réelle; les données sont générées à chaque fois (à moins d'utiliser des contextes)

Résultats

- La génération LLM ajoute de la latence (~100-500ms par demande)

Intégration avec les points d'extrémité Mock réguliersLes spécifications OpenAPI fonctionnent en parallèle avec les spécifications régulières

critères d' évaluation:

  • Les deux utilisent le même LLM sous-jacent, mais diffèrent dans la façon dont le schéma est fourni.
  • Optimisation des performances
  • Cache
  • Les spécifications chargées sont mises en cache en mémoire :

Durée de service

**est un simpleton, donc les spécifications restent chargées pour la durée de vie de l'application.**Chargement parallèle des spécifications

Plusieurs spécifications peuvent être chargées en parallèle:

  • Tous les trois vont se charger en parallèle, pas séquentiellement./petstore + /pet/123 = /petstore/pet/123
  • Dépannage
  • Spec ne sera pas chargé

Problème :

Défauts de chargement des spécificationsSolutions :

Vérifiez que l'URL est accessible

  • Vérifier l'existence du fichier (pour les chemins locaux)
  • S'assurer que le JSON/YAML est valide
  • Recherchez les problèmes CORS (pour les URL distantes)

Point d'arrivée non trouvé

Problème :

404 sur le critère d'évaluation prévu

  • **Solutions :**Vérifier le chemin de base :
  • Vérifiez que la spécification définit réellement ce cheminS'assurer que la méthode HTTP correspond (GET vs POST)
  • La réponse ne correspond pas au schémaProblème :
  • Les données générées ne correspondent pas au schéma attenduSolutions :
  • Vérifiez si le schéma de la spécification est correctVérifiez que vous regardez la bonne réponse (200 vs 201)

Rappelez-vous : la génération LLM est probabiliste, pas déterministe

Le présent règlement entre en vigueur le vingtième jour suivant celui de sa publication au Journal officiel de l'Union européenne.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.