BrighterCommand / BrighterCommand/Brighter

Mapper assembly-scan registration does not filter by visibility, unlike handler registration

Open Beginner friendly
#4,286 0 comments 0 reactions 0 assignees View on GitHub
.NET 0 - Backlog Bug
Dominant language
C#
Stars
2.5k
Forks
296
Avg merge
1d 11h
Merged PRs (30d)
21

Description

## Summary

`ServiceCollectionBrighterBuilder`'s assembly-scan mapper registration does not filter by type visibility, unlike its handler registration. This means an `internal` (or nested-private) mapper class can trigger a false-positive "mapper already registered" conflict during `AddBrighter(...).AutoFromAssemblies()`, even though it was never intended to be publicly discoverable.

## Where

`src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionBrighterBuilder.cs`

- `RegisterHandlersFromAssembly` (line 244) filters candidates with:
```csharp
where ti is { IsClass: true, IsAbstract: false, IsInterface: false } && (ti.IsPublic || ti.IsNestedPublic)
```
- `RegisterMappersFromAssemblies` (line 273) and `RegisterAsyncMappersFromAssemblies` (line 288) use the same shape of query but **omit** the `(ti.IsPublic || ti.IsNestedPublic)` clause:
```csharp
where ti is { IsClass: true, IsAbstract: false, IsInterface: false }
```

Both call `assembly.GetTypes()` (via the shared `GetLoadableTypes` helper), which returns every type in the assembly regardless of accessibility — public, internal, and nested-private alike. The handler scan then narrows that down to public/nested-public types; the mapper scans do not.

## Repro

Add an `internal` class implementing `IAmAMessageMapper` for a request type that already has a mapper, in an assembly that also calls `AddBrighter(...).AutoFromAssemblies()` anywhere:

```csharp
internal sealed class InternalDuplicateMapper : IAmAMessageMapper
{
// ... maps SomeCommand, same as the "real" public mapper
}
```

Any test/host in that assembly that runs `AddBrighter(options => { ... }).AutoFromAssemblies()` (or equivalent `MapperRegistryFromAssemblies` call) throws:

```
System.ArgumentException : A mapper for message type has already been registered.
Mappers and are in conflict
```

even though `InternalDuplicateMapper` is `internal` and was never meant to be picked up.

## Impact

- Surprising failure mode: adding an `internal` type for local/test-only use can break unrelated tests or hosts elsewhere in the same assembly that use assembly-scan registration, with no compile-time warning.
- Inconsistent with the handler scan's behaviour, which is the behaviour a reader would reasonably expect from all three scans (handlers, mappers, transforms).
- Hit in practice while implementing spec 0036 (`spec/scoped-lifetime-per-pipeline`): a test-only mapper double caused 67 unrelated test failures via assembly-scan conflict; marking it `internal` was tried as a fix and does **not** avoid the conflict, confirmed by direct reproduction.

## Proposed fix

Add the same `(ti.IsPublic || ti.IsNestedPublic)` visibility filter to `RegisterMappersFromAssemblies` and `RegisterAsyncMappersFromAssemblies` that `RegisterHandlersFromAssembly` already uses, so assembly-scan mapper registration only considers publicly visible mapper types — consistent with handler registration.

(`TransformsFromAssemblies` registers transforms by type rather than checking uniqueness per request, so it never conflicts regardless of visibility — no change needed there, but worth confirming intent while this is being looked at.)

Contributor guide

Open the contributing guide

Research direction

Open src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionBrighterBuilder.cs and compare RegisterMappersFromAssemblies and RegisterAsyncMappersFromAssemblies with the visibility filter in RegisterHandlersFromAssembly. Reproduce the conflict with an internal duplicate mapper and assembly scanning, then verify that internal and nested-private mappers are ignored while public mappers retain existing registration behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.