Files
luma/Sources/LumaCore/Models/FeatureValue.swift
Ole André Vadla Ravnås 7e51b8c2f2 core: Stabilize fields and choices with ids
ObjectField now carries an `id` separate from its display
name. Combo choices become [ComboChoice] (id + name pairs).
Generated .d.ts uses ids for keys and emits the human
name as a JSDoc comment.
2026-05-06 18:36:52 +02:00

139 lines
4.9 KiB
Swift

import Foundation
public enum FeatureValue: Sendable, Equatable {
case boolean(Bool)
case int(Int64)
case uint(UInt64)
case double(Double)
case string(String)
case regex(String)
case object([String: FeatureValue])
case array([FeatureValue])
public func matches(schema: FeatureSchema) -> Bool {
switch (self, schema) {
case (.boolean, .boolean): return true
case (.int, .int): return true
case (.uint, .uint): return true
case (.double, .double): return true
case (.string, .string), (.string, .combo): return true
case (.regex, .regex): return true
case (.object(let valueFields), .object(let schemaFields)):
return FeatureValue.objectFieldsMatch(valueFields: valueFields, schemaFields: schemaFields)
case (.array(let items), .array(let item, _)):
return items.allSatisfy { $0.matches(schema: item) }
default:
return false
}
}
public func matches(schema: ArrayItemSchema) -> Bool {
switch (self, schema) {
case (.boolean, .boolean): return true
case (.int, .int): return true
case (.uint, .uint): return true
case (.double, .double): return true
case (.string, .string), (.string, .combo): return true
case (.regex, .regex): return true
case (.object(let valueFields), .object(let schemaFields)):
return FeatureValue.objectFieldsMatch(valueFields: valueFields, schemaFields: schemaFields)
default:
return false
}
}
private static func objectFieldsMatch(
valueFields: [String: FeatureValue],
schemaFields: [ObjectField]
) -> Bool {
let knownIDs = Set(schemaFields.map(\.id))
for id in valueFields.keys where !knownIDs.contains(id) {
return false
}
for field in schemaFields {
if let v = valueFields[field.id] {
if !v.matches(schema: field.schema) { return false }
} else if !field.optional {
return false
}
}
return true
}
public func toJSONNative() -> Any {
switch self {
case .boolean(let v): return v
case .int(let v): return NSNumber(value: v)
case .uint(let v): return NSNumber(value: v)
case .double(let v): return NSNumber(value: v)
case .string(let v): return v
case .regex(let v): return v
case .object(let fields): return fields.mapValues { $0.toJSONNative() }
case .array(let items): return items.map { $0.toJSONNative() }
}
}
}
extension FeatureValue: Codable {
private enum CodingKeys: String, CodingKey {
case kind
case value
}
private enum Kind: String, Codable {
case boolean
case int
case uint
case double
case string
case regex
case object
case array
}
public init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let kind = try c.decode(Kind.self, forKey: .kind)
switch kind {
case .boolean: self = .boolean(try c.decode(Bool.self, forKey: .value))
case .int: self = .int(try c.decode(Int64.self, forKey: .value))
case .uint: self = .uint(try c.decode(UInt64.self, forKey: .value))
case .double: self = .double(try c.decode(Double.self, forKey: .value))
case .string: self = .string(try c.decode(String.self, forKey: .value))
case .regex: self = .regex(try c.decode(String.self, forKey: .value))
case .object: self = .object(try c.decode([String: FeatureValue].self, forKey: .value))
case .array: self = .array(try c.decode([FeatureValue].self, forKey: .value))
}
}
public func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .boolean(let v):
try c.encode(Kind.boolean, forKey: .kind)
try c.encode(v, forKey: .value)
case .int(let v):
try c.encode(Kind.int, forKey: .kind)
try c.encode(v, forKey: .value)
case .uint(let v):
try c.encode(Kind.uint, forKey: .kind)
try c.encode(v, forKey: .value)
case .double(let v):
try c.encode(Kind.double, forKey: .kind)
try c.encode(v, forKey: .value)
case .string(let v):
try c.encode(Kind.string, forKey: .kind)
try c.encode(v, forKey: .value)
case .regex(let v):
try c.encode(Kind.regex, forKey: .kind)
try c.encode(v, forKey: .value)
case .object(let fields):
try c.encode(Kind.object, forKey: .kind)
try c.encode(fields, forKey: .value)
case .array(let items):
try c.encode(Kind.array, forKey: .kind)
try c.encode(items, forKey: .value)
}
}
}