diff --git a/LaunchDarkly/LaunchDarkly/Models/Context/LDContext.swift b/LaunchDarkly/LaunchDarkly/Models/Context/LDContext.swift index 2416b1f6b..1088e64a2 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Context/LDContext.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Context/LDContext.swift @@ -119,7 +119,7 @@ public struct LDContext: Encodable, Equatable { for key in optionalAttributeNames { if let value = context.getTopLevelAddressableAttributeSingleKind(key) { if redactAll { - redactedAttributes.append(key) + redactedAttributes.append(Reference(literal: key).raw()) continue } diff --git a/LaunchDarkly/LaunchDarkly/Models/Context/LDContextJSONWriter.swift b/LaunchDarkly/LaunchDarkly/Models/Context/LDContextJSONWriter.swift index befda51a6..808dc2a1d 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Context/LDContextJSONWriter.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Context/LDContextJSONWriter.swift @@ -60,7 +60,7 @@ extension LDContext { else { continue } if redactAll { - redaction.redactedAttributes.append(name) + redaction.redactedAttributes.append(Reference(literal: name).raw()) continue } diff --git a/LaunchDarkly/LaunchDarklyTests/EventJSONWriterTests.swift b/LaunchDarkly/LaunchDarklyTests/EventJSONWriterTests.swift index ad09da4a8..93704f1c3 100644 --- a/LaunchDarkly/LaunchDarklyTests/EventJSONWriterTests.swift +++ b/LaunchDarkly/LaunchDarklyTests/EventJSONWriterTests.swift @@ -432,11 +432,14 @@ final class EventJSONWriterTests: XCTestCase { } /// An attribute name is a literal name, not an attribute reference: `/foo` names an attribute called `/foo`, not the - /// path `foo`. Only private attributes are references, and they match the name as one component. + /// path `foo`. Private attributes and `redactedAttributes` are references, so a name starting with a slash is + /// listed as the one-component path that refers to it, `/~1foo`. func testAttributeNamesStartingWithASlashAreWrittenAndRedactedByName() throws { var builder = LDContextBuilder(key: "user-key") _ = builder.trySetValue("/foo", "bar") _ = builder.trySetValue("/secret", "hidden") + _ = builder.trySetValue("/a~b/c", true) + _ = builder.trySetValue("x/y", 2) _ = builder.trySetValue("plain", 1) builder.addPrivateAttribute(Reference("/~1secret")) builder.addPrivateAttribute(Reference("/foo")) @@ -465,7 +468,7 @@ final class EventJSONWriterTests: XCTestCase { if allAttributesPrivate { XCTAssertNil(written["/foo"], label) XCTAssertNil(written["plain"], label) - XCTAssertEqual(Set(redacted(written)), ["/foo", "/secret", "plain"], label) + XCTAssertEqual(Set(redacted(written)), ["/~1foo", "/~1secret", "/~1a~0b~1c", "x/y", "plain"], label) } else { // `Reference("/foo")` is the path `foo`, which names no attribute here. XCTAssertEqual(written["/foo"] as? String, "bar", label) @@ -473,6 +476,12 @@ final class EventJSONWriterTests: XCTestCase { XCTAssertEqual(redacted(written), ["/~1secret"], label) } XCTAssertNil(written["/secret"], label) + // Each entry, parsed back as a reference, refers to an attribute that was left out. + for entry in redacted(written) { + let reference = Reference(entry) + XCTAssertTrue(reference.isValid(), "\(label): \(entry)") + XCTAssertNotNil(context.getValue(reference), "\(label): \(entry)") + } } }