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.
139 lines
4.9 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|