Skip to content

ArgumentOutOfRangeException when topic key starts overlaps with Root (e.g., RO) #154

Description

@JeremyCaney

When importing or otherwise assigning a Parent to a newly constructed Topic, SetParent() can incorrectly throw ArgumentOutOfRangeException ("A descendant cannot be its own parent") for topics whose Key is a case-insensitive prefix of the target parent's UniqueKey, even when the topic isn't actually an ancestor or descendant of that parent. This happens because a new topic's GetUniqueKey() returns only its own Key until Parent is assigned, so the guard in SetParent() ends up comparing that key against the full parent path using StartsWith(). Any topic keyed R, Ro, Roo, or Root (case-insensitive) anywhere in a site whose root topic is named Root will therefore fail to be created.

Background

The TopicFactory.Create() factory method invokes the Topic constructor, which sets Key before assigning Parent. At the moment Parent is set, SetParent() runs:

if (parent.GetUniqueKey().StartsWith(GetUniqueKey(), StringComparison.OrdinalIgnoreCase)) {
  throw new ArgumentOutOfRangeException(nameof(parent), "A descendant cannot be its own parent.");
}

Since the new topic isn't yet assigned to a parent, GetUniqueKey() returns e.g., "RO". The check becomes "Root:Configuration:Metadata:Country:LookupList".StartsWith("RO", OrdinalIgnoreCase), which evaluates true—not because RO is an actual ancestor, but because Root case-insensitively starts with Ro.

Implementation Notes

The guard should only treat parent as a descendant of the current topic when the prefix matches a full path segment boundary, not any substring. The character following the matched prefix must thus be either the end of the string or the : delimiter.

Affected Files

  • OnTopic/Topic.cs (SetParent())

Proposed Fix

var uniqueKey = GetUniqueKey();
var parentKey = parent.GetUniqueKey();
if (
  parentKey.StartsWith(uniqueKey, StringComparison.OrdinalIgnoreCase) &&
  parentKey.Length > uniqueKey.Length &&
  parentKey[uniqueKey.Length] == ':'
) {
  throw new ArgumentOutOfRangeException(nameof(parent), "A descendant cannot be its own parent.");
}

Tasks

  • Update the ancestor-guard check in SetParent() to require a : delimiter (or end of string) after the matched prefix
  • Add a regression test covering a topic keyed Ro (or similar) being created anywhere under a topic named Root
  • Add a regression test covering the general case, where a short topic key is a case-insensitive prefix of an unrelated ancestor path segment

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: EntityRelates to the core data data structure for modeling topic entities.Priority: 1Status 2: ScheduledPlanned for an upcoming release.Type: BugBehavior that is inconsistent with documented or expected behavior.

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions