Skip to content

[AI Refactoring] DownloadErrorFactory.ThrowException default 분기 누락 → 미매핑 에러 코드 silent failure (호출부 45곳) + switch expression 전환 [Scope: src/Tizen.Content.Download] (2026-07-26) #7771

Description

@JoonghyunCho

[Type: Refactoring]
[Scope: src/Tizen.Content.Download]
[Priority: 🔴 Critical]
[Lens: Coding Guidelines, Modernization]

Observation

DownloadErrorFactory.ThrowException (src/Tizen.Content.Download/Tizen.Content.Download/DownloadErrorFactory.cs:58-100) is the single error-to-exception funnel for the whole module. Its switch enumerates 22 DownloadError cases — and has no default branch:

internal static void ThrowException(int errorCode, string errorMessage = null, string paramName = null)
{
    ...
    switch ((DownloadError)errorCode)
    {
        case DownloadError.InvalidParameter:
        ...
        case DownloadError.IoError: throw new InvalidOperationException(message);
        case DownloadError.NotSupported: throw new NotSupportedException(message);
        case DownloadError.PermissionDenied: throw new UnauthorizedAccessException(message);
    }
    // unmapped code -> falls through and RETURNS NORMALLY
}

All 45 call sites in the module (Request.cs 31, CacheManager.cs 8, Notification.cs 6) follow the pattern if (ret != (int)DownloadError.None) DownloadErrorFactory.ThrowException(ret, ...); and rely on the call never returning.

Problem

  1. Silent failure at 45 call sites (bug risk — Coding Guidelines). If the native url_download layer ever returns a code outside the 22 mapped values (a newly added platform error, or a generic Tizen error not aliased into DownloadError), ThrowException returns normally and the caller proceeds as if the interop call succeeded. Concrete consequences: CacheManager.MaxCacheSize returns an uninitialized out value; the Request constructors keep building state on a broken _downloadId; Start() reports success while nothing was started. This is the identical defect class already confirmed in ConnectionErrorFactory.ThrowConnectionException ([AI Refactoring] ConnectionErrorFactory.ThrowConnectionException default 분기 누락 → switch expression 으로 silent failure 차단 [Scope: src/Tizen.Network.Connection] (2026-04-26) #7591).
  2. 22-case switch statement with per-case throw (Modernization). The mapping is a pure value-to-exception translation — the canonical shape for a C# 8 switch expression with or patterns, which makes fall-through structurally impossible and cuts the method to a third of its size.

Proposed Improvement

Convert to a throw ... switch expression whose _ arm covers every unmapped code.

Before

switch ((DownloadError)errorCode)
{
    case DownloadError.InvalidParameter:
    case DownloadError.InvalidUrl:
    case DownloadError.InvalidDestination:
    case DownloadError.InvalidNetworkType: throw new ArgumentException(message, paramName);
    case DownloadError.OutOfMemory:
    case DownloadError.NetworkUnreachable:
    // ... 15 more cases ...
    case DownloadError.IoError: throw new InvalidOperationException(message);
    case DownloadError.NotSupported: throw new NotSupportedException(message);
    case DownloadError.PermissionDenied: throw new UnauthorizedAccessException(message);
}

After

internal static void ThrowException(int errorCode, string errorMessage = null, string paramName = null)
{
    DownloadError err = (DownloadError)errorCode;
    string message = String.IsNullOrEmpty(errorMessage) ? err.ToString() : errorMessage;

    throw err switch
    {
        DownloadError.InvalidParameter or DownloadError.InvalidUrl or
        DownloadError.InvalidDestination or DownloadError.InvalidNetworkType
            => new ArgumentException(message, paramName),
        DownloadError.NotSupported => new NotSupportedException(message),
        DownloadError.PermissionDenied => new UnauthorizedAccessException(message),
        _ => new InvalidOperationException(message),
    };
}

(The 19 codes previously mapped to InvalidOperationException collapse into the _ arm together with all future unmapped codes.)

Target Files

  • src/Tizen.Content.Download/Tizen.Content.Download/DownloadErrorFactory.cs

Expected Impact (Quantitative Metrics)

  • Call sites exposed to silent fall-through: 45 -> 0 (the method now provably always throws).
  • Method body: ~30 LOC -> ~12 LOC; case labels 22 -> 6 pattern arms.
  • Property getters that could return uninitialized out values after a swallowed error: eliminated (e.g. CacheManager.MaxCacheSize, CachePath, Request.DownloadedPath).

API Compatibility Check

  • Public API signature change: none (internal factory; public surface untouched).
  • Behavior change: previously-unmapped native error codes now throw InvalidOperationException instead of being silently ignored. This matches the <exception cref="InvalidOperationException"> contract already documented on every public member of the module. All 45 call sites are guarded by ret != None (or pass fixed non-None codes), so no success path ever reaches the throw.
  • Tizen API Level floor: maintained (switch expression is C# 8 syntax — no new runtime API).

Impact Scope

  • 1 internal method, single assembly. Measured via rg: 45 call sites, all inside src/Tizen.Content.Download (no cross-assembly callers of the internal factory).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions