Namespace Kuestenlogik.Bowire

Classes

BowireDiscoveryProbe

The registry fan-out behind every discovery surface Bowire has (#534): the /api/services endpoint, bowire discover on the CLI, and the bowire.discover MCP tool. Each of those used to own a private copy of "loop the plugins, call DiscoverAsync, swallow what throws" — and each swallowed a different amount of the diagnosis, so the three surfaces disagreed about why a URL produced nothing.

One pass produces two things: the merged service list, and one BowireDiscoveryAttempt per probed plugin — including the plugins that ran cleanly and found nothing, which is precisely the case the old error-only reporting hid.

Static because it is pure: no cache, no ring buffer, no registry of its own. Every input arrives as a parameter. Should it ever need to remember something between calls, it has to become an injected service instead. IBowireDiscoveryDiagnostics (#544) does not change that: the probe reads a diagnostic off the return value of the same await it was already making, into a local that dies with the task. It stores nothing, and it never goes back to a plugin to ask what happened.

BowireDiscoveryProbeResult

What one RunAsync(BowireProtocolRegistry, string, string?, bool, TimeSpan, ILogger?, CancellationToken) pass produced: the merged services from every plugin that found something, plus one BowireDiscoveryAttempt per probed plugin regardless of outcome. Attempts is populated even when Services is not — that is the whole point.

BowireDiscoveryReport

One DiscoverWithDiagnosticsAsync(string, bool, CancellationToken) pass: what the plugin found, and — optionally — what went wrong while it was finding it.

BowireEndpointRouteBuilderExtensions

ASP.NET routing extensions for mounting the Bowire multi-protocol API workbench onto an IEndpointRouteBuilder.

BowireHelpServiceCollectionExtensions

Opt-in registration for the in-app help provider. Embedded hosts call builder.Services.AddBowireHelp() alongside app.MapBowire(); standalone tool + Docker image take this package as a transitive reference so the CLI surface gets help without extra wiring.

BowireOptions

Runtime configuration for a Bowire instance.

BowirePluginSetting

Describes a single setting that a protocol plugin contributes to the Bowire Settings dialog. The UI renders a control (toggle, text, number, select) based on the Type and persists the value in localStorage under bowire_plugin_{pluginId}_{Key}.

BowirePluginSettingOption

Option entry for a "select" type plugin setting.

BowireProtocolRegistry

Registry of discovered protocol plugins. Scans loaded assemblies for IBowireProtocol implementations.

BowireServerUrl

Parses Bowire's optional hint@url form for server URLs. A plugin hint tells the discovery + invoke endpoints which protocol plugin to consult — without one Bowire still probes every plugin in turn, which is fine in practice but slow when one of them needs a long network round-trip to discover that the URL isn't theirs (e.g. the gRPC plugin opens an HTTP/2 channel against an HTTP/1.1 GraphQL server and waits for the handshake to time out).

Examples:

  • grpc@https://api.example.com:443 → hint=grpc, url=https://api.example.com:443
  • signalr@https://api.example.com/hubs/chat → hint=signalr, url=https://api.example.com/hubs/chat
  • https://alice:pwd@host.com → no hint, url stays intact (the @ is URI userinfo)
  • https://api.example.com → no hint, url stays intact
  • udp://239.0.13.37:8137 → no hint, the URL scheme itself already routes the plugin
BowireServiceCollectionExtensions

DI-container extensions that wire Bowire and its installed protocol plugins into an ASP.NET application.

InvokeResult

Result of a protocol invocation (unary or client-streaming).

OpenApiUploadStore

In-memory store for OpenAPI / Swagger documents uploaded via the UI. The REST plugin reads from this store during discovery and merges the parsed services with whatever it finds via embedded discovery or URL fetching.

The store keeps raw document text only — parsing happens in the REST plugin because Kuestenlogik.Bowire core can't take a dependency on the OpenAPI reader package without dragging it into every host that uses Bowire.

ProtoSource

Source for proto file definitions. Used to provide service schemas when gRPC Server Reflection is not available.

SafePath

Path-traversal-safe wrapper around Combine(string, string). The BCL Combine(string, string) silently drops earlier arguments when a later one is an absolute path (e.g. Path.Combine("/var/bowire", "/etc/passwd") == "/etc/passwd"). When the right-hand side comes from caller input that's a classic path-traversal footgun — flagged by CodeQL cs/path-combine.

StreamFrame

One frame yielded by a server-streaming call that exposes wire bytes. Json is the display / recording form; Binary is what the mock server replays on the wire.

UploadedDoc

A single uploaded OpenAPI/Swagger document with its raw content.

Interfaces

IBowireChannel

Interactive bidirectional channel for duplex/client-streaming protocols.

IBowireDiscoveryDiagnostics

Optional capability surface for protocol plugins whose probe can half-succeed (#544): "here are the services I found, AND here is what broke while I found them".

DiscoverAsync(string, bool, CancellationToken) has no channel for that — a plugin either returns a list or throws, so a partially-faulted probe had to hide either the fault or the results. The MCP plugin is the canonical case: an MCP server with a single malformed tool used to suppress its perfectly good resources and prompts as well, because the throw was the only way to make the fault visible.

Discovered exactly the way IInlineHttpInvoker is — core does protocol is IBowireDiscoveryDiagnostics on the instance it was going to call anyway. A plugin that does not implement this behaves exactly as before, and third-party plugins keep compiling: nothing on IBowireProtocol changed.

IBowireProtocol

Protocol plugin for Bowire. Implement this to add support for a new protocol. Discovered automatically via assembly scanning.

IBowireProtocolServices

Optional extension of IBowireProtocol that allows a protocol plugin to register required services and map discovery endpoints automatically.

When a user calls builder.Services.AddBowire(), Bowire scans loaded assemblies for IBowireProtocol implementations that also implement this interface and calls ConfigureServices(IServiceCollection) on each one. Similarly, app.MapBowire() calls MapDiscoveryEndpoints(IEndpointRouteBuilder).

This eliminates the need for protocol-specific boilerplate. For example, the gRPC plugin uses this to call AddGrpcReflection() and MapGrpcReflectionService() automatically — but only when the gRPC protocol package is actually referenced.

IBowireStreamingWithWireBytes

Optional extension for IBowireProtocol implementations that can expose the raw wire bytes of each server-streamed frame alongside the JSON rendering. Implemented by protocol plugins whose wire format is binary and distinct from their JSON representation (gRPC today); the JSON-only InvokeStreamAsync(string, string, string, List<string>, bool, Dictionary<string, string>?, CancellationToken) stays the default path for everyone else.

IInlineHttpInvoker

Optional capability surface for protocol plugins that can dispatch a generic BowireMethodInfo as an HTTP request — i.e. plugins that know how to bucket fields by their Source (path / query / header / body), substitute path placeholders, build a request body, and parse the response.

The REST plugin implements this so that the gRPC plugin's HTTP-transcoding discovery (which produces BowireMethodInfo instances with HttpMethod + HttpPath set) can be invoked over HTTP without core taking on any HTTP-specific dependencies. If a Bowire build doesn't include the REST plugin, FindHttpInvoker() returns null and the /api/invoke endpoint refuses HTTP transcoding requests with a clear error.

IInlineSseSubscriber

Optional capability surface for protocol plugins that can subscribe to a remote Server-Sent Events stream and yield each event as it arrives.

The SSE plugin implements this so that other plugins (MCP for server-side notifications, GraphQL for the graphql-sse subscription transport) can reuse the SSE event-stream parser without taking a hard dependency on Kuestenlogik.Bowire.Protocol.Sse at compile time. If the SSE plugin isn't loaded, FindSseSubscriber() returns null and the consuming plugin should fall back to a clear "install the SSE plugin" error.

IInlineWebSocketChannel

Optional capability surface for protocol plugins that can open a raw WebSocket channel — used by plugins that need to ride on top of a WebSocket transport (e.g. the GraphQL plugin's graphql-transport-ws subscription support) without taking a compile-time dependency on Kuestenlogik.Bowire.Protocol.WebSocket.

Implementers should honour the subProtocols parameter by passing it to ClientWebSocket.Options.AddSubProtocol(...) before the handshake — that's how WebSocket sub-protocols like graphql-transport-ws are negotiated. Headers go on the upgrade request via SetRequestHeader so the existing auth-helper pipeline keeps working unchanged.

Enums

BowireMode

Bowire UI operating mode — chooses between in-process discovery (embedded) and user-driven URL entry (standalone).

BowireTheme

Initial colour theme for the Bowire UI. Users can always override this from the theme toggle in the header; the selected theme is persisted per browser in localStorage.