Skip to content

Commit ab10717

Browse files
Add editor types for JSON, arrays, and case iterable strings and ints (#14)
1 parent 4c309ca commit ab10717

28 files changed

+1681
-211
lines changed

App/Sources/App/ContentViewModel.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ final class ContentViewModel {
3838
let jsonVariable = ConfigVariable(
3939
key: "complexConfig",
4040
defaultValue: ComplexConfiguration(field1: "a", field2: 1),
41-
content: .json(representation: .data)
41+
content: .json(representation: .string())
4242
).metadata(\.displayName, "Complex Config")
4343

4444
let intBackedVariable = ConfigVariable(key: "favoriteCardSuit", defaultValue: CardSuit.spades, isSecret: true)
@@ -96,15 +96,15 @@ struct ComplexConfiguration: Codable, Hashable, Sendable {
9696
}
9797

9898

99-
enum Beatle: String, Codable, Hashable, Sendable {
99+
enum Beatle: String, CaseIterable, Codable, Hashable, Sendable {
100100
case john = "John"
101101
case paul = "Paul"
102102
case george = "George"
103103
case ringo = "Ringo"
104104
}
105105

106106

107-
enum CardSuit: Int, Codable, Hashable, Sendable {
107+
enum CardSuit: Int, CaseIterable, Codable, Hashable, Sendable {
108108
case spades
109109
case hearts
110110
case clubs

Sources/DevConfiguration/Core/CodableValueRepresentation.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ public struct CodableValueRepresentation: Sendable {
4747
CodableValueRepresentation(kind: .data)
4848
}
4949

50+
/// Whether this representation supports text-based editing in the editor UI.
51+
///
52+
/// String-backed representations can be edited as text, while data-backed representations cannot.
53+
var supportsTextEditing: Bool {
54+
switch kind {
55+
case .string: true
56+
case .data: false
57+
}
58+
}
59+
5060

5161
/// Reads raw data synchronously from the reader based on this representation.
5262
///
@@ -129,6 +139,30 @@ public struct CodableValueRepresentation: Sendable {
129139
}
130140

131141

142+
/// Extracts raw `Data` from a ``ConfigContent`` based on this representation.
143+
///
144+
/// This is the reverse of ``encodeToContent(_:)``. For string-backed representations, this extracts the string and
145+
/// converts it to `Data` using the representation's encoding. For data-backed representations, this extracts the
146+
/// byte array and wraps it in `Data`.
147+
///
148+
/// - Parameter content: The content to extract data from.
149+
/// - Returns: The raw data, or `nil` if the content doesn't match this representation's expected case.
150+
func data(from content: ConfigContent) -> Data? {
151+
switch kind {
152+
case .string(let encoding):
153+
guard case .string(let string) = content else {
154+
return nil
155+
}
156+
return string.data(using: encoding)
157+
case .data:
158+
guard case .bytes(let bytes) = content else {
159+
return nil
160+
}
161+
return Data(bytes)
162+
}
163+
}
164+
165+
132166
/// Watches for raw data changes from the reader based on this representation.
133167
///
134168
/// Each time the underlying configuration value changes, `onUpdate` is called with the new raw data (or `nil` if the

Sources/DevConfiguration/Core/ConfigVariable.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,28 @@ extension ConfigVariable {
280280
}
281281

282282

283+
extension ConfigVariable {
284+
/// Creates a `RawRepresentable<String> & CaseIterable` configuration variable.
285+
///
286+
/// Content is set to ``ConfigVariableContent/rawRepresentableCaseIterableString()`` automatically. Uses a picker
287+
/// control populated with all cases instead of a free-text field.
288+
///
289+
/// - Parameters:
290+
/// - key: The configuration key.
291+
/// - defaultValue: The default value to use when variable resolution fails.
292+
/// - isSecret: Whether this variable's value should be treated as secret. Defaults to `false`.
293+
public init(key: ConfigKey, defaultValue: Value, isSecret: Bool = false)
294+
where Value: RawRepresentable & CaseIterable & Sendable, Value.RawValue == String {
295+
self.init(
296+
key: key,
297+
defaultValue: defaultValue,
298+
content: .rawRepresentableCaseIterableString(),
299+
isSecret: isSecret
300+
)
301+
}
302+
}
303+
304+
283305
extension ConfigVariable {
284306
/// Creates a `[RawRepresentable<String>]` configuration variable.
285307
///
@@ -348,6 +370,23 @@ extension ConfigVariable {
348370
}
349371

350372

373+
extension ConfigVariable {
374+
/// Creates a `RawRepresentable<Int> & CaseIterable` configuration variable.
375+
///
376+
/// Content is set to ``ConfigVariableContent/rawRepresentableCaseIterableInt()`` automatically. Uses a picker
377+
/// control populated with all cases instead of a free-text number field.
378+
///
379+
/// - Parameters:
380+
/// - key: The configuration key.
381+
/// - defaultValue: The default value to use when variable resolution fails.
382+
/// - isSecret: Whether this variable's value should be treated as secret. Defaults to `false`.
383+
public init(key: ConfigKey, defaultValue: Value, isSecret: Bool = false)
384+
where Value: RawRepresentable & CaseIterable & Sendable, Value.RawValue == Int {
385+
self.init(key: key, defaultValue: defaultValue, content: .rawRepresentableCaseIterableInt(), isSecret: isSecret)
386+
}
387+
}
388+
389+
351390
extension ConfigVariable {
352391
/// Creates a `[RawRepresentable<Int>]` configuration variable.
353392
///

0 commit comments

Comments
 (0)