# LLMAPI: OpenAPI Dynamic Mock Generator: Ladda alla specifikationer, Mock API

<!--category-- AI, LLM, ASP.NET Core, API, Nuget, mockllmapi, LLMApi, SignalR, AI-Article-->
<datetime class="hidden">2025-11-05T15:15</datetime>

> OBS!Denna artikel är främst AI genererad som en del av mitt nugetpaket som utgivningsdokumentation.

# Det är ganska intressant så jag la det här men om det är ett problem för dig, var snäll och ignorera det.

Inledning

Har du någonsin behövt testa mot ett API som inte var redo än?

[![Eller ville utvecklas offline utan att slå hastighetsbegränsningar?](https://img.shields.io/nuget/v/mostlylucid.mockllmapi.svg)](https://www.nuget.org/packages/mostlylucid.mockllmapi)
[![Med OpenAPI Dynamic Mock Generator kan du ladda alla OpenAPI-specifikationer och omedelbart skapa ett fullt fungerande prototyp API med realistiska, LLM-genererade data.](https://img.shields.io/nuget/dt/mostlylucid.mockllmapi.svg)](https://www.nuget.org/packages/mostlylucid.mockllmapi)

Inga inställningsfiler.[Inget manuellt målskapande.](https://github.com/scottgal/LLMApi)Rikta den bara mot en OpenAPI spec och börja göra förfrågningar.

![Hämta](openapi.png)

[TOC]

## Hämta

Du kan hitta

```mermaid
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 här

- för projektet, alla offentliga domäner etc...
- OpenApi- hantering
- Problemet: API-beroenden Blockutveckling

## Moderna applikationer är beroende av dussintals externa API:er.

Under utvecklingen står du inför flera utmaningar:

1. Traditionella lösningar omfattar följande:
2. Manuellt skriva hånfulla svar (tröttligt, blir föråldrad)
3. Inspelning/respelning av HTTP-trafik (svår, svår att underhålla)
4. Använda hårdkodade fixturer (orealistiska, täcker inte kant fall)

```mermaid
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", ...}
```

## Lösningen: Dynamisk OpenAPI-mockning

### Rikta systemet mot någon OpenAPI spec, och det automatiskt:

Tolkning av specifikationen

```mermaid
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]
```

**Upptäcker alla endpoints**

1. **Genererar realistiska prototypdata med hjälp av en LLM**Serverar prototyp API på din lokala maskin
2. **Hur den fungerar**Översikt över arkitekturen
3. **OpenAPI-systemet består av flera samordnade komponenter:**Nyckelkomponenter:
4. **DynamiskOpenApiManager**- Hanterar laddade specifikationer och ruttmatchning
5. **OpenApiSpecLoader**- Fetcher och tolkar OpenAPI dokument

### OpenApiRequestHandler

- Genererar svar för matchade endpoints

**Snabbt bygge**

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

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

**- Skapar LLM- samtal från OpenAPI scheman**

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

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

**OpenApiContextManager**

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

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

### - Bibehåller överensstämmelse mellan samtal (frivilligt)

Lastspecifikationer

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

### Specifikationer kan laddas från tre källor:

1. Vad är det som händer?

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

### Fjärrwebbadress

Två.

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

### Lokal fil

3. Vad är det som händer?

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

## Datawebbadress (Base64 kodad)

### Specific Laddar process

Så här går det när man laddar en spec:

**Dynamisk ruttmatchning**

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

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

**När en begäran kommer, matchar systemet den mot alla laddade specifikationer:**

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

**Hantering av begäran**

När ett matchande resultatmått har hittats genererar hanteraren ett svar:

```http
### 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"
}
```

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

**Utdragning av schema**

```http
GET /api/openapi/specs/petstore

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

**Systemet extraherar responsscheman från OpenAPI-definitioner:**

```http
POST /api/openapi/specs/petstore/reload
```

**Real-World användning**

```http
DELETE /api/openapi/specs/petstore
```

### Exempel: Petstore API

Låt oss gå igenom håna den klassiska Petstore API:

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

### Steg 1: Ladda specifikationen

Svar:

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

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

Steg 2: Använd Mock Endpoints

```http
### 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 finns alla 19 endpoints tillgängliga:

Steg 3: Inspektera specifikationerna

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

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

### Returns mock response without affecting routes
```

Steg 4: Ladda om om specifika ändringar

- Steg 5: Ta bort när det är klart
- Flera specifikationer samtidigt
- Du kan ladda flera specifikationer på en gång, var och en med sin egen basväg:

## Specifikationer med sammanhang

För ännu mer realism, tilldela ett sammanhang till en spec:`http://localhost:5116/OpenApi`:

```mermaid
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 delar alla slutpunkter i djurbutiken samma sammanhang:**

- **Provningsslutpunkt**Test endpoint kan du prova en endpoint utan att göra en verklig begäran:
- **Detta är användbart för:**Förhandsgranskning av svar före integration
- **Testning av specifika endpoints isolerat**Felsökning av schemafrågor
- **Ledningsgränssnitt**För visuell hantering, besök
- **Kännetecken:**Dra och släpp
- **Spec- filuppladdning**Upptäckt av levande endpoint

## - Se alla endpoints omedelbart

### Enklickstestning

- Testa alla endpoints med en knapp

```yaml
# OpenAPI Spec
/pet/{petId}:
  get:
    parameters:
      - name: petId
        in: path
        schema:
          type: integer
```

```http
GET /petstore/pet/123
### LLM receives: "Generate data for Pet with petId=123"
### Response: {"id": 123, ...}
```

### Anmälan i realtid

- SignalR-uppdateringar när specsen laddas

```yaml
/pet/findByStatus:
  get:
    parameters:
      - name: status
        in: query
        schema:
          type: string
          enum: [available, pending, sold]
```

```http
GET /petstore/pet/findByStatus?status=available
### LLM receives: "Generate array of Pets with status=available"
### Response: [{"status": "available", ...}, ...]
```

### Syntaxfärgläggning

- Vacker JSON svarsdisplay

```yaml
/pet:
  post:
    requestBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Pet'
```

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

### Sammanhangshantering

- Visa och tydliga sammanhang

```yaml
/pet/{petId}:
  get:
    summary: Find pet by ID
    description: Returns a single pet based on the ID provided
```

Avancerade funktioner

### Parametrar för sökväg

Sökvägsparametrar extraheras automatiskt:

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

Köningsparametrar

## Frågaparametrar påverkar svaret:

Begärande organ

```csharp
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 organ ingår i prompten:

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

## Beskrivningar och sammanfattningar

OpenAPI-beskrivningar vägleder LLM:

- Dessa ingår i prompten, vilket hjälper LLM att förstå endpointens syfte.
- Koder för svarsstatus
- Systemet använder det första lyckade (2xx) svaret:
- Endast 200 schema används för mock generation (404s för närvarande inte simuleras).
- SignalR realtidsuppdateringar

När specifikationer laddas/utgår tar UI emot realtidsmeddelanden via SignalR:

## JavaScript UI- kod:

### Formatstöd

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

### Systemet stöder:

OpenAPI 3.0.x

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

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

### OpenAPI 3.1.x

Swagger 2.0 Ordförande

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

### JSON- format

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

### Now all petstore calls maintain consistency
```

### YAML- format

Både JSON och YAML specs detekteras och tolkas automatiskt.

```http
DELETE /api/openapi/specs/old-spec
```

## Bästa praxis

1. **1. Vad är det som händer?**Använd beskrivande specifikationer
2. **Två.**Ställ in lämpliga basvägar
3. **Undvik konflikter genom att använda unika basvägar:**3. Vad är det som händer?
4. **Ladda om när specifikationer ändras**Om din OpenAPI-specifikation uppdateras, ladda om den:
5. **Fyra.**Använd sammanhang för relaterade samtal

## För att uppnå detta mål har kommissionen beslutat att inleda ett förfarande enligt artikel 108.2 i fördraget om Europeiska unionens funktionssätt (nedan kallat EUF-fördraget).

Städa upp efter testning`/api/mock`Ta bort specifikationer du inte längre använder:

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

Begränsningar

## Koder för svarsstatus

### - Endast framgångsrika (2xx) svar hånas

Behörighetskontroll

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

### - Auth-huvuden accepteras men är inte validerade

`DynamicOpenApiManager`Validering

### - Begära validering mot scheman inte verkställs

Tillstånd

```http
### Send these simultaneously
POST /api/openapi/specs {"name": "spec1", ...}
POST /api/openapi/specs {"name": "spec2", ...}
POST /api/openapi/specs {"name": "spec3", ...}
```

- Ingen verklig databas; data genereras färska varje gång (om inte med hjälp av sammanhang)

## Prestanda

### - LLM generation lägger latency (~100-500ms per begäran)

**Integrering med regelbundna Mock Endpoints**OpenAPI specs fungerar tillsammans med vanliga

**ändpunkter:**

- Båda använder samma underliggande LLM men skiljer sig åt i hur schemat tillhandahålls.
- Prestandaoptimering
- Caching
- Laddade specifikationer lagras i minnet:

### Tjänstlivstid

**är ett singleton, så specifikationerna förblir laddade under applikationens livstid.**Parallella specifikationer som laddar

**Flera specifikationer kan laddas parallellt:**

- Alla tre kommer att ladda parallellt, inte sekventiellt.`/petstore` + `/pet/123` = `/petstore/pet/123`
- Felsökning
- Spec kommer inte att ladda

### Problem:

**Specificering misslyckas**Lösningar:

**Kontrollera att webbadressen är tillgänglig**

- Verifiera filen finns (för lokala sökvägar)
- Se till att JSON/YAML är giltig
- Leta efter CORS-frågor (för fjärrwebbadresser)

## Slutpunkt hittades inte

Problem:

404 för förväntat effektmått

- **Lösningar:**Verifiera bassökvägen:
- **Markera spec definierar faktiskt den sökvägen**Se till att HTTP-metoden matchar (GET vs POST)
- **Svar matchar inte schemat**Problem:
- **Genererad data matchar inte förväntat schema**Lösningar:
- **Kontrollera om schemat i specifikationen är korrekt**Kontrollera att du tittar på rätt svar (200 vs 201)

Kom ihåg: LLM-generationen är probabilistisk, inte deterministisk

Slutsatser