Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Davinci/Davinci.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
A51D4CF82C65978300FE09E0 /* DaVinciIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51D4CF72C65978300FE09E0 /* DaVinciIntegrationTests.swift */; };
A51D4CFA2C6BA75000FE09E0 /* CallbackFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51D4CF92C6BA75000FE09E0 /* CallbackFactoryTests.swift */; };
A53B6EDE2F9FA25600B279CE /* BooleanCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53B6EDD2F9FA25600B279CE /* BooleanCollector.swift */; };
A5245B012FABCD1200000001 /* RichContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5245B002FABCD1200000001 /* RichContent.swift */; };
A53B6EE02F9FA2D900B279CE /* BooleanCollectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53B6EDF2F9FA2D900B279CE /* BooleanCollectorTests.swift */; };
A5A61E032D4A92BF00767C2D /* ErrorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A61E022D4A92B000767C2D /* ErrorNode.swift */; };
A5A712482CAC527200B7DD58 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A5A712472CAC527200B7DD58 /* PrivacyInfo.xcprivacy */; };
Expand Down Expand Up @@ -136,6 +137,7 @@
A51D4CF92C6BA75000FE09E0 /* CallbackFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallbackFactoryTests.swift; sourceTree = "<group>"; };
A53A9B582F1984C8000E2F2C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
A53B6EDD2F9FA25600B279CE /* BooleanCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BooleanCollector.swift; sourceTree = "<group>"; };
A5245B002FABCD1200000001 /* RichContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichContent.swift; sourceTree = "<group>"; };
A53B6EDF2F9FA2D900B279CE /* BooleanCollectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BooleanCollectorTests.swift; sourceTree = "<group>"; };
A5A61E022D4A92B000767C2D /* ErrorNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNode.swift; sourceTree = "<group>"; };
A5A712472CAC527200B7DD58 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
Expand Down Expand Up @@ -236,6 +238,7 @@
A51D4CE12C62C53400FE09E0 /* module */,
3A292C812BF58462006977EF /* Agent.swift */,
3A203D7E2BDB136C0020C995 /* DaVinci.swift */,
A5245B002FABCD1200000001 /* RichContent.swift */,
3A57CB612C44D688001EC9A0 /* User.swift */,
);
path = Davinci;
Expand Down Expand Up @@ -483,6 +486,7 @@
A51D4CD92C585E5700FE09E0 /* TextCollector.swift in Sources */,
3A57CB622C44D68C001EC9A0 /* User.swift in Sources */,
A53B6EDE2F9FA25600B279CE /* BooleanCollector.swift in Sources */,
A5245B012FABCD1200000001 /* RichContent.swift in Sources */,
A5E9AEB42D2F2256000533ED /* LabelCollector.swift in Sources */,
A5E9AEBA2D2F2731000533ED /* ValidatedCollector.swift in Sources */,
3A203D7F2BDB136C0020C995 /* DaVinci.swift in Sources */,
Expand Down
29 changes: 29 additions & 0 deletions Davinci/Davinci/RichContent.swift
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RichContent.swift is placed at the module root (Davinci/Davinci/) while all other collector-supporting types live in Davinci/Davinci/collector/. Consider moving the file...

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// RichContent.swift
// PingDavinci
//
// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
//
// This software may be modified and distributed under the terms
// of the MIT license. See the LICENSE file for details.
//

/// A replacement entry within rich content.
/// - `value`: The display text for the replacement.
/// - `href`: The URL for link-type replacements.
/// - `type`: The type of replacement (e.g., "link").
/// - `target`: The link target (e.g., "_self", "_blank").
public struct RichContentReplacement: Sendable {
public let value: String
public let href: String?
public let type: String
public let target: String?
}

/// Rich content for a form field, enabling template-based text with embedded links.
/// - `content`: A template string with `{{placeholder}}` tokens.
/// - `replacements`: A dictionary mapping placeholder keys to their replacement details.
public struct RichContent: Sendable {
public let content: String
public let replacements: [String: RichContentReplacement]
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both RichContent and RichContentReplacement are pure value types with no stored closures or references, so Equatable conformance is trivial and free. Without it, callers can't use == or XCTAssertEqual directly on these structs — they have to compare individual fields, as the tests currently do. Add Equatable at the definition site:

public struct RichContentReplacement: Sendable, Equatable {  }
public struct RichContent: Sendable, Equatable {}

20 changes: 0 additions & 20 deletions Davinci/Davinci/collector/BooleanCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,6 @@ public class BooleanCollector: FieldCollector<Bool>, @unchecked Sendable {
}
}

/// A replacement entry within rich content.
/// - `value`: The display text for the replacement.
/// - `href`: The URL for link-type replacements.
/// - `type`: The type of replacement (e.g., "link").
/// - `target`: The link target (e.g., "_self", "_blank").
public struct RichContentReplacement: Sendable {
public let value: String
public let href: String?
public let type: String
public let target: String?
}

/// Rich content associated with a single checkbox field.
/// - `content`: A template string with `{{placeholder}}` tokens.
/// - `replacements`: A dictionary mapping placeholder keys to their replacement details.
public struct RichContent: Sendable {
public let content: String
public let replacements: [String: RichContentReplacement]
}

/// The appearance options for a single checkbox field.
/// - `checkbox`: A traditional checkbox appearance.
/// - `switch`: A switch/toggle appearance.
Expand Down
23 changes: 21 additions & 2 deletions Davinci/Davinci/collector/LabelCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// LabelCollector.swift
// PingDavinci
//
// Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
// Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved.
//
// This software may be modified and distributed under the terms
// of the MIT license. See the LICENSE file for details.
Expand All @@ -24,12 +24,31 @@ public class LabelCollector: Collector, @unchecked Sendable {
public private(set) var key: String = ""
/// The label content.
public private(set) var content: String = ""

/// Optional rich content with template text and link replacements.
public private(set) var richContent: RichContent?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update Davinci readme file to include .richContent too in the LabelCollector (LABEL) section


/// Initializes a new instance of `LabelCollector`.
/// - Parameter json: The json to initialize from.
public required init(with json: [String : Any]) {
content = json[Constants.content] as? String ?? ""
key = json[Constants.key] as? String ?? ""

if let richContentDict = json[Constants.richContent] as? [String: Any],
let richContentContent = richContentDict[Constants.content] as? String {
var replacements: [String: RichContentReplacement] = [:]
if let replacementsDict = richContentDict[Constants.replacements] as? [String: [String: Any]] {
for (replacementKey, replacementDict) in replacementsDict {
let replacement = RichContentReplacement(
value: replacementDict[Constants.value] as? String ?? "",
href: replacementDict[Constants.href] as? String,
type: replacementDict[Constants.type] as? String ?? "",
target: replacementDict[Constants.target] as? String
)
replacements[replacementKey] = replacement
}
}
self.richContent = RichContent(content: richContentContent, replacements: replacements)
}
}

/// Initializes the `LabelCollector` with the given value. The `LabelCollector` does not hold any value.
Expand Down
Loading
Loading