LLMApi: OpenAPI Dynamic Mock Generator: Laden van elke Spec, Mock elke API (Nederlands (Dutch))

LLMApi: OpenAPI Dynamic Mock Generator: Laden van elke Spec, Mock elke API

Wednesday, 05 November 2025

//

14 minute read

OPMERKING: Dit artikel is voornamelijk AI gegenereerd als onderdeel van mijn nuget pakket als release documentatie.

Het is best interessant dus ik heb het hier neergezet maar als dat een probleem voor je is, negeer het dan alsjeblieft.

Inleiding

Heb je ooit een API moeten testen die nog niet klaar was?

Of wilde offline te ontwikkelen zonder het halen van tarieflimieten? Met de OpenAPI Dynamic Mock Generator kunt u elke OpenAPI specificatie laden en maakt u onmiddellijk een volledig functionele spot API met realistische, LLM-gegenereerde gegevens.

Geen configuratiebestanden.Geen handmatige eindpuntcreatie.Richt het gewoon op een OpenAPI spec en begin met het maken van verzoeken.

NuGet

NuGet

U kunt de

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 hier

  • voor het project, alle publieke domein, enz...
  • OpenApi-beheer
  • Het probleem: API afhankelijkheden Blokontwikkeling

Moderne toepassingen zijn afhankelijk van tientallen externe API's.

Tijdens de ontwikkeling wordt u geconfronteerd met verschillende uitdagingen:

  1. Traditionele oplossingen omvatten:
  2. Handmatig schrijven van spotreacties (vervelend, wordt verouderd)
  3. Recording/replaying HTTP verkeer (brittle, moeilijk te onderhouden)
  4. Met behulp van harde gecodeerde armaturen (onrealistisch, heeft geen betrekking op rand gevallen)
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", ...}

De oplossing: Dynamische OpenAPI Mocking

Richt het systeem op elke OpenAPI spec, en het automatisch:

Ontleedt de specificatie

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]

Ontdekt alle eindpunten

  1. Genereert realistische spotgegevens met behulp van een LLMServeert de spot API op uw lokale machine
  2. Hoe het werktOverzicht architectuur
  3. **Het OpenAPI-systeem bestaat uit verschillende gecoördineerde componenten:**Sleutelcomponenten:
  4. DynamicOpenApiManager- Beheert geladen specs en route matching
  5. OpenApiSpecLoader- Fetches en parses OpenAPI documenten

OpenApiRequestHandler

  • Genereert responsen voor overeenkomende eindpunten

PromptBuilder

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

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

- Maakt LLM-prompts aan van OpenAPI schema's

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

- Onderhoudt consistentie tussen gesprekken (optioneel)

Specificaties laden

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

Specificaties kunnen uit drie bronnen worden geladen:

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-adres op afstand

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

Lokaal bestand

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

Data URL (Base64 gecodeerd)

spec Loadproces

Dit is wat er gebeurt als je een spec laadt:

Dynamic Route Matching

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

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

Wanneer een verzoek arriveert, komt het systeem overeen met alle geladen specificaties:

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

Behandeling van verzoeken

Zodra een passend eindpunt is gevonden, genereert de begeleider een respons:

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

Schema extractie

GET /api/openapi/specs/petstore

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

Het systeem haalt responsschema's uit OpenAPI definities:

POST /api/openapi/specs/petstore/reload

Gebruik in de reële wereld

DELETE /api/openapi/specs/petstore

Voorbeeld: Petstore API

Laten we de klassieke Petstore API bespotten:

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

Stap 1: Laad de spec

Respons:

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

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

Stap 2: Gebruik de eindpunten van 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

Nu zijn alle 19 eindpunten beschikbaar:

Stap 3: Inspecteer de spec

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

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

### Returns mock response without affecting routes

Stap 4: Herladen als spec verandert

  • Stap 5: Verwijderen wanneer voltooid
  • Meerdere Specificaties Tegelijkertijd
  • U kunt meerdere specificaties tegelijk laden, elk met zijn eigen basispad:

Specificaties met contexten

Voor nog meer realisme, geef een context aan een spec: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]

Nu hebben alle petstore eindpunten dezelfde context:

  • Eindpunt testenMet het testeindpunt kunt u een eindpunt proberen zonder een echt verzoek te doen:
  • **Dit is nuttig voor:**Evaluatie van de antwoorden vóór integratie
  • Testen van specifieke eindpunten in isolatieSchema-problemen debuggen
  • Beheers-UIVoor visueel beheer, bezoek
  • **Functies:**Sleep-and-drop
  • spec bestand uploadenLive endpoint discovery

- Bekijk alle eindpunten direct

Testen met één klik

  • Test elk eindpunt met een knop
# 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, ...}

Realtime-kennisgevingen

  • SignalR-updates bij het laden van specificaties
/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", ...}, ...]

Syntaxismarkering

  • Prachtige JSON response display
/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"}

Contextbeheer

  • Zicht en duidelijke contexten
/pet/{petId}:
  get:
    summary: Find pet by ID
    description: Returns a single pet based on the ID provided

Geavanceerde functies

Padparameters

Padparameters worden automatisch uitgepakt:

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

Parameters opvragen

Query parameters beïnvloeden de respons:

Verzoekorganen

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

POST/PUT-lichamen zijn opgenomen in de prompt:

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

Beschrijvingen en samenvattingen

OpenAPI beschrijvingen begeleiden de LLM:

  • Deze zijn opgenomen in de prompt en helpen de LLM het doel van het eindpunt te begrijpen.
  • Responsstatuscodes
  • Het systeem maakt gebruik van de eerste succesvolle (2xx) respons:
  • Alleen de 200 schema wordt gebruikt voor de mock-generatie (404s zijn momenteel niet gesimuleerd).
  • SignalR real-time updates

Wanneer specs worden geladen/verwijderd, ontvangt de UI real-time meldingen via SignalR:

JavaScript UI code:

Ondersteuning voor formatteren

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

Het systeem ondersteunt:

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

JSON-formaat

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

### Now all petstore calls maintain consistency

YAML-formaat

Zowel JSON als YAML specs worden automatisch gedetecteerd en ontleed.

DELETE /api/openapi/specs/old-spec

Beste praktijken

  1. **1.**Gebruik Descriptive Spec Names
  2. **2.**Passende basispaden instellen
  3. **Conflicten vermijden door gebruik te maken van unieke basispaden:**3.
  4. Herladen wanneer Specificaties wijzigenAls uw OpenAPI-spec bijgewerkt is, herlaad deze dan:
  5. **4.**Contexts gebruiken voor gerelateerde oproepen

5.

Opruimen na het testen/api/mockVerwijder specificaties die u niet meer gebruikt:

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

Beperkingen

Responsstatuscodes

- Alleen succesvolle (2xx) reacties worden bespot

Authenticatie

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

- Auth headers worden geaccepteerd maar niet gevalideerd

DynamicOpenApiManagerValidatie

- Verzoek validatie tegen schema's is niet afgedwongen

Staat

### Send these simultaneously
POST /api/openapi/specs {"name": "spec1", ...}
POST /api/openapi/specs {"name": "spec2", ...}
POST /api/openapi/specs {"name": "spec3", ...}
  • Geen werkelijke database; gegevens worden telkens opnieuw gegenereerd (tenzij met behulp van contexten)

Prestaties

- LLM generatie voegt latency (~100-500ms per aanvraag)

Integratie met reguliere Mock-eindpuntenOpenAPI-specs werken naast reguliere

eindpunten:

  • Beiden gebruiken dezelfde onderliggende LLM maar verschillen in hoe het schema wordt verstrekt.
  • Optimalisatie van de prestaties
  • Caching
  • Geladen specs worden gecached in het geheugen:

Levensduur van de dienst

**is een singleton, dus specs blijven geladen voor de levensduur van de toepassing.**Parallelle spec laden

Meerdere specificaties kunnen parallel worden geladen:

  • Alle drie zullen ze parallel laden, niet achtereenvolgens./petstore + /pet/123 = /petstore/pet/123
  • Problemen oplossen
  • Spec zal niet laden

Probleem:

Spec laden misluktOplossingen:

Controleer of de URL toegankelijk is

  • Controleren of het bestand bestaat (voor lokale paden)
  • Zorg ervoor dat de JSON/YAML geldig is
  • Zoek naar CORS-problemen (voor externe URL's)

Eindpunt niet gevonden

Probleem:

404 bij verwacht eindpunt

  • **Oplossingen:**Verifiëren van het basispad:
  • Controleer de spec definieert eigenlijk dat padZorg ervoor dat de HTTP methode overeenkomt (GET vs POST)
  • Antwoord komt niet overeen met schemaProbleem:
  • Gegenereerde gegevens komen niet overeen met het verwachte schemaOplossingen:
  • Controleer of het schema in de spec juist isControleer of je kijkt naar de juiste reactie (200 vs 201)

Onthoud: LLM-generatie is probabilistisch, niet deterministisch

Conclusie

Finding related posts...
logo

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