WrapGod Architecture
WrapGod is a source-generation toolkit that creates strongly-typed wrapper interfaces, facades, and mapper classes around third-party .NET libraries. Its purpose is to decouple application code from vendor APIs so that library upgrades, version-specific breaking changes, and cross-version compatibility can be managed declaratively rather than through manual refactoring.
The system is organized as a multi-stage pipeline. Each stage consumes well-defined inputs and produces well-defined outputs, keeping individual components testable in isolation.
Pipeline Overview
+-------------------+
| Assembly DLL(s) |
+--------+----------+
|
1. EXTRACT
|
v
+-------------+-------------+
| ApiManifest (JSON) |
| + VersionDiff (if multi) |
+-------------+-------------+
|
+----------------+ 2. PLAN | +------------------+
| JSON config +--->---+--------+-<---+ [WrapType] attrs |
| (.wrapgod.json)| | | (Abstractions) |
+----------------+ v +------------------+
+------+------+
| Merged | +------------------+
| WrapGodConfig+--<-----+ Fluent DSL |
+------+------+ | (WrapGodConfig) |
| +------------------+
v
+----------+----------+
| TypeMappingPlan |
| GenerationPlan |
+----------+----------+
|
3. GENERATE
|
v
+---------------+---------------+
| IWrapped* interfaces (.g.cs) |
| *Facade proxy classes (.g.cs) |
| *Mapper static classes (.g.cs)|
+---------------+---------------+
|
4. ANALYZE + FIX
|
v
+---------------+---------------+
| WG2001 / WG2002 diagnostics |
| Automated code-fix rewrites |
+-------------------------------+
Component Dependency Graph
WrapGod.Abstractions (netstandard2.0 -- attributes + config models)
^ ^
| |
| WrapGod.Manifest (netstandard2.0 -- manifest models + config loaders)
| ^
| |
| WrapGod.Extractor (net10.0 -- reflection-based extraction)
|
+-----WrapGod.TypeMap (netstandard2.0 -- mapping plan + mapper emitter)
|
+-----WrapGod.Fluent (netstandard2.0 -- fluent DSL builder)
|
+-----WrapGod.Generator (netstandard2.0 -- Roslyn incremental generator)
|
+-----WrapGod.Analyzers (netstandard2.0 -- Roslyn analyzer + code fix)
|
+-----WrapGod.Runtime (net10.0 -- runtime helpers, e.g. version guards)
All generator and analyzer assemblies target netstandard2.0 to be loadable
by any Roslyn host (VS, VS Code, dotnet build). The extractor, which uses
MetadataLoadContext, targets net10.0.
Data Flow
The canonical data flow from raw assembly to generated source is:
- Assembly DLL -- a compiled third-party library on disk.
ApiManifest(JSON) -- the complete public API surface serialized as a deterministic, schema-versioned JSON document.WrapGodConfig(merged) -- the union of JSON overrides,[WrapType]/[WrapMember]attributes, and fluent DSL directives, resolved throughConfigMergeEnginewith configurable precedence.TypeMappingPlan+GenerationPlan-- the normalized, ready-to-emit representation consumed by the generator.- Source output --
IWrapped*interfaces,*Facadeproxy classes, and*Mapperstatic mapping classes injected into the compilation.
Component Reference
WrapGod.Abstractions
Purpose. Shared contracts consumed by every other project. Contains the attribute definitions and configuration model types.
| Key type | Role |
|---|---|
WrapTypeAttribute |
Marks a type for wrapper generation; sets SourceType, Include, TargetName. |
WrapMemberAttribute |
Marks a member for wrapper generation; sets SourceMember, Include, TargetName. |
WrapGodConfig |
Root config model: list of TypeConfig entries. |
TypeConfig |
Per-type configuration: SourceType, Include, TargetName, list of MemberConfig. |
MemberConfig |
Per-member configuration: SourceMember, Include, TargetName. |
ConfigMergeResult |
Output of merge: merged WrapGodConfig + list of ConfigDiagnostic. |
ConfigMergeOptions |
Controls which source (JSON or Attributes) takes precedence on conflict. |
Inputs. None (leaf dependency). Outputs. Attribute types (consumed at compile time) and config model types (consumed at plan time).
WrapGod.Extractor
Purpose. Reads the public API surface of one or more .NET assemblies and
produces an ApiManifest. Uses System.Reflection.MetadataLoadContext for
safe, reflection-only loading -- no assembly is ever executed.
| Key type | Role |
|---|---|
AssemblyExtractor |
Extracts a single assembly into an ApiManifest. Computes a SHA-256 SourceHash for drift detection. |
MultiVersionExtractor |
Accepts an ordered list of (VersionLabel, AssemblyPath) pairs, extracts each, then merges into a single manifest with VersionPresence metadata and a VersionDiff compatibility report. |
VersionDiff |
Machine-readable report: added/removed types, added/removed/changed members, classified BreakingChange entries. |
Inputs. One or more assembly DLLs on disk.
Outputs. ApiManifest (single-version) or MultiVersionResult (merged manifest + diff).
WrapGod.Manifest
Purpose. Defines the ApiManifest model hierarchy and provides serialization,
config loading, and config merging.
| Key type | Role |
|---|---|
ApiManifest |
Root model: SchemaVersion, GeneratedAt, Assembly identity, SourceHash, list of ApiTypeNode. |
ApiTypeNode |
Public type: StableId, FullName, Kind (class/struct/interface/enum/delegate), modifiers, members, VersionPresence. |
ApiMemberNode |
Public member: StableId, Kind (method/property/field/event/constructor/indexer/operator), signature details, VersionPresence. |
VersionPresence |
Records IntroducedIn, RemovedIn, ChangedIn version labels. |
ManifestSerializer |
Deterministic JSON serialization (camelCase, enum-as-string, null-suppressed). |
JsonConfigLoader |
Loads a WrapGodConfig from a JSON file or string. |
AttributeConfigReader |
Scans an assembly for [WrapType]/[WrapMember] attributes and produces a WrapGodConfig. |
ConfigMergeEngine |
Merges JSON-sourced and attribute-sourced configs. On conflict, applies ConfigMergeOptions.HigherPrecedence and emits ConfigDiagnostic entries. |
Inputs. Assembly DLLs (via extractor), JSON config files, attribute-decorated assemblies.
Outputs. ApiManifest model, merged WrapGodConfig.
WrapGod.Fluent
Purpose. Provides a programmatic, chainable API for defining wrapper configuration in C# code rather than JSON or attributes.
| Key type | Role |
|---|---|
WrapGodConfiguration |
Fluent entry point. Create() / ForAssembly() / WrapType() / MapType() / ExcludeType() / Build(). |
TypeDirectiveBuilder |
Nested builder returned by WrapType(). Supports .As(), .WrapMethod(), .WrapProperty(), .ExcludeMember(), .WrapAllPublicMembers(). |
MemberDirectiveBuilder |
Nested builder returned by WrapMethod(). Supports .As() for rename. |
GenerationPlan (Fluent) |
The normalized output of the builder: TypeDirectives, TypeMappings, ExclusionPatterns, CompatibilityMode. |
TypeDirective |
Per-type directive: source type, target name, member directives, excluded members. |
MemberDirective |
Per-member directive: source name, target name, kind (Method/Property). |
Inputs. Developer code calling the fluent API.
Outputs. GenerationPlan (consumed by generation pipeline).
WrapGod.TypeMap
Purpose. Builds a type-mapping plan from merged config, then emits static mapper classes that convert between source and destination types.
| Key type | Role |
|---|---|
TypeMapping |
Maps a source type to a destination type with a TypeMappingKind and per-member mappings. |
TypeMappingKind |
Enum: ObjectMapping, Enum, Collection, Nullable, Custom. |
MemberMapping |
Source member to destination member, with optional ConverterRef. |
ConverterRef |
Reference to a user-defined converter (type name + optional method name). |
TypeMappingPlan |
Collection of TypeMapping entries with lookup helpers. |
TypeMappingPlanner |
Builds a TypeMappingPlan from WrapGodConfig + optional TypeMappingOverride list. |
TypeMappingOverride |
Explicit override: force mapping kind or converter for a source type. |
TypeMapperEmitter |
Generates C# static mapper classes from a TypeMappingPlan. |
MapperSourceBuilder |
Indentation-aware StringBuilder wrapper for well-formatted C# output. |
Inputs. Merged WrapGodConfig, optional TypeMappingOverride list.
Outputs. TypeMappingPlan model; generated *Mapper source text.
WrapGod.Generator
Purpose. Roslyn incremental source generator that reads *.wrapgod.json
manifest files from AdditionalFiles, parses them into generation plans, and
emits wrapper interfaces and facade proxy classes.
| Key type | Role |
|---|---|
WrapGodIncrementalGenerator |
The [Generator] entry point. Implements IIncrementalGenerator. Filters AdditionalTexts to *.wrapgod.json, parses each into a GenerationPlan, then calls SourceEmitter to produce output. |
GenerationPlan (Generator) |
Lightweight, IEquatable<T> model parsed from manifest JSON. Drives incremental caching. |
TypePlan |
Per-type plan: FullName, Name, Namespace, Members, version metadata (IntroducedIn/RemovedIn), optional TargetName. |
MemberPlan |
Per-member plan: Name, Kind, ReturnType, Parameters, GenericParameters, version metadata. |
ParameterPlan |
Parameter descriptor: Name, Type, Modifier. |
SourceEmitter |
String-based emitter that produces IWrapped* interface source and *Facade proxy class source from TypePlan models. Supports an AdaptiveMode flag for version-guarded member access. |
CompatibilityMode |
Enum: Lcd (lowest common denominator), Targeted (single version), Adaptive (all members with runtime guards). |
CompatibilityFilter |
Filters a GenerationPlan based on CompatibilityMode, stripping members that do not belong in the selected mode. |
Inputs. *.wrapgod.json additional files (manifest JSON).
Outputs. IWrapped*.g.cs interface files, *Facade.g.cs proxy class files.
WrapGod.Analyzers
Purpose. Roslyn analyzer and code-fix provider that detects direct usage of third-party types that have generated wrappers and offers automated migration.
| Key type | Role |
|---|---|
DirectUsageAnalyzer |
[DiagnosticAnalyzer]. Reads *.wrapgod-types.txt additional files to build a mapping from original types to their wrapper interface and facade. Reports WG2001 (direct type usage) and WG2002 (direct method call). |
UseWrapperCodeFixProvider |
[ExportCodeFixProvider]. Fixes WG2001 by replacing type references with the wrapper interface; fixes WG2002 by replacing method receivers with the facade type. Supports FixAll via BatchFixer. |
DiagnosticDescriptors |
Static descriptor definitions for WG2001 and WG2002. |
Inputs. *.wrapgod-types.txt additional files (one mapping per line: Original -> IWrapper, Facade), user source code.
Outputs. Diagnostics (warnings), automated code-fix rewrites.
WrapGod.Runtime
Purpose. Provides runtime helpers referenced by generated code. Notably,
the WrapGodVersionHelper.IsMemberAvailable() method used by Adaptive-mode
facades to gate member access based on the detected library version at runtime.
Status. Scaffold only -- implementation pending.
Inputs. Called by generated facade code at runtime. Outputs. Boolean availability checks.
Compatibility Modes
WrapGod supports three strategies for handling API members that exist in only a subset of the targeted library versions:
| Mode | Behavior | Use case |
|---|---|---|
| LCD (Lowest Common Denominator) | Emit only members present in every targeted version. | Maximum portability; no runtime version checks needed. |
| Targeted | Emit members present in a single specified version. | Pinned deployment against a known library version. |
| Adaptive | Emit all members; version-specific ones are wrapped with WrapGodVersionHelper.IsMemberAvailable() guards that throw PlatformNotSupportedException if the member is unavailable at runtime. |
Libraries that must support multiple versions simultaneously. |
Extension Points
Custom Converters
ConverterRef allows users to plug in custom conversion logic at both the
type level and the member level. The generator delegates to the referenced
static method instead of emitting default property-copy code.
// Example: type-level converter
new TypeMappingOverride
{
SourceType = "Vendor.Lib.LegacyDate",
Kind = TypeMappingKind.Custom,
Converter = new ConverterRef
{
TypeName = "MyApp.Converters.LegacyDateConverter",
MethodName = "Convert"
}
};
Config Sources
Three config sources feed into ConfigMergeEngine:
- JSON --
*.wrapgod.jsonfiles loaded byJsonConfigLoader. - Attributes --
[WrapType]/[WrapMember]scanned byAttributeConfigReader. - Fluent DSL --
WrapGodConfigurationbuilder producing aGenerationPlan.
Precedence on conflict is controlled by ConfigMergeOptions.HigherPrecedence
(default: Attributes win). Conflicts emit ConfigDiagnostic entries with codes
WG6001--WG6004.
Analyzer Rules
The analyzer reads mapping data from *.wrapgod-types.txt additional files.
Adding new entries to this file (or generating it as a build artifact from the
manifest) automatically extends analyzer coverage without code changes.
Diagnostics Report Formats (RFC-0054)
WrapGod.Abstractions.Diagnostics.WgDiagnosticEmitter is the canonical formatter
entry point for structured diagnostics:
EmitJson(...)emits thewg.diagnostic.v1contract records.EmitSarif(...)emits SARIF 2.1.0 for CI/security tooling integration.
SARIF output projects a stable WG rule catalog into
runs[0].tool.driver.rules[] (one entry per WG####) and maps diagnostic
severity, locations, related locations, fingerprints, and suppression metadata
from the same canonical model used by JSON output.
Determinism and Reproducibility
- Stable IDs. Every type and member receives a deterministic
StableIdderived from its namespace, name, and parameter signature. These IDs are used for cross-version presence tracking and diffing. - Schema versioning.
ApiManifest.SchemaVersionenables forward compatibility; consumers can reject manifests with an unsupported schema. - Source hashing.
AssemblyExtractorcomputes a SHA-256 hash of the input DLL and embeds it inApiManifest.SourceHash. Downstream stages can detect drift when a manifest is stale relative to its source artifact. - Deterministic sorting. Types and members within a manifest are sorted by
StableIdusing ordinal comparison, ensuring identical output regardless of reflection enumeration order.
Performance Notes
Incremental Generator Caching
WrapGodIncrementalGenerator leverages Roslyn's incremental pipeline
(IncrementalGeneratorInitializationContext) to avoid redundant work:
GenerationPlan,TypePlan,MemberPlan, andParameterPlanall implementIEquatable<T>with value-based equality.- When a
*.wrapgod.jsonfile changes, only its correspondingGenerationPlanis re-parsed. If the parsed plan is structurally equal to the previous one, no source is re-emitted. - Plans are collected via
.Collect()into anImmutableArray, enabling a singleRegisterSourceOutputcallback that emits all files.
Partitioned Output
Generated source files are partitioned by type (IWrapped*.g.cs and
*Facade.g.cs per type). This limits the blast radius of a single type change
-- only that type's files are regenerated.
No Reflection in Generator Path
All reflection-based metadata loading is confined to WrapGod.Extractor.
The generator and analyzer paths operate purely on serialized manifest data and
Roslyn symbol models, avoiding the cost and complexity of runtime reflection.