Spaces:
Paused
Paused
| import Foundation | |
| enum ConfigPathSegment: Hashable { | |
| case key(String) | |
| case index(Int) | |
| } | |
| typealias ConfigPath = [ConfigPathSegment] | |
| struct ConfigUiHint { | |
| let label: String? | |
| let help: String? | |
| let order: Double? | |
| let advanced: Bool? | |
| let sensitive: Bool? | |
| let placeholder: String? | |
| init(raw: [String: Any]) { | |
| self.label = raw["label"] as? String | |
| self.help = raw["help"] as? String | |
| if let order = raw["order"] as? Double { | |
| self.order = order | |
| } else if let orderInt = raw["order"] as? Int { | |
| self.order = Double(orderInt) | |
| } else { | |
| self.order = nil | |
| } | |
| self.advanced = raw["advanced"] as? Bool | |
| self.sensitive = raw["sensitive"] as? Bool | |
| self.placeholder = raw["placeholder"] as? String | |
| } | |
| } | |
| struct ConfigSchemaNode { | |
| let raw: [String: Any] | |
| init?(raw: Any) { | |
| guard let dict = raw as? [String: Any] else { return nil } | |
| self.raw = dict | |
| } | |
| var title: String? { self.raw["title"] as? String } | |
| var description: String? { self.raw["description"] as? String } | |
| var enumValues: [Any]? { self.raw["enum"] as? [Any] } | |
| var constValue: Any? { self.raw["const"] } | |
| var explicitDefault: Any? { self.raw["default"] } | |
| var requiredKeys: Set<String> { | |
| Set((self.raw["required"] as? [String]) ?? []) | |
| } | |
| var typeList: [String] { | |
| if let type = self.raw["type"] as? String { return [type] } | |
| if let types = self.raw["type"] as? [String] { return types } | |
| return [] | |
| } | |
| var schemaType: String? { | |
| let filtered = self.typeList.filter { $0 != "null" } | |
| if let first = filtered.first { return first } | |
| return self.typeList.first | |
| } | |
| var isNullSchema: Bool { | |
| let types = self.typeList | |
| return types.count == 1 && types.first == "null" | |
| } | |
| var properties: [String: ConfigSchemaNode] { | |
| guard let props = self.raw["properties"] as? [String: Any] else { return [:] } | |
| return props.compactMapValues { ConfigSchemaNode(raw: $0) } | |
| } | |
| var anyOf: [ConfigSchemaNode] { | |
| guard let raw = self.raw["anyOf"] as? [Any] else { return [] } | |
| return raw.compactMap { ConfigSchemaNode(raw: $0) } | |
| } | |
| var oneOf: [ConfigSchemaNode] { | |
| guard let raw = self.raw["oneOf"] as? [Any] else { return [] } | |
| return raw.compactMap { ConfigSchemaNode(raw: $0) } | |
| } | |
| var literalValue: Any? { | |
| if let constValue { return constValue } | |
| if let enumValues, enumValues.count == 1 { return enumValues[0] } | |
| return nil | |
| } | |
| var items: ConfigSchemaNode? { | |
| if let items = self.raw["items"] as? [Any], let first = items.first { | |
| return ConfigSchemaNode(raw: first) | |
| } | |
| if let items = self.raw["items"] { | |
| return ConfigSchemaNode(raw: items) | |
| } | |
| return nil | |
| } | |
| var additionalProperties: ConfigSchemaNode? { | |
| if let additional = self.raw["additionalProperties"] as? [String: Any] { | |
| return ConfigSchemaNode(raw: additional) | |
| } | |
| return nil | |
| } | |
| var allowsAdditionalProperties: Bool { | |
| if let allow = self.raw["additionalProperties"] as? Bool { return allow } | |
| return self.additionalProperties != nil | |
| } | |
| var defaultValue: Any { | |
| if let value = self.raw["default"] { return value } | |
| switch self.schemaType { | |
| case "object": | |
| return [String: Any]() | |
| case "array": | |
| return [Any]() | |
| case "boolean": | |
| return false | |
| case "integer": | |
| return 0 | |
| case "number": | |
| return 0.0 | |
| case "string": | |
| return "" | |
| default: | |
| return "" | |
| } | |
| } | |
| func node(at path: ConfigPath) -> ConfigSchemaNode? { | |
| var current: ConfigSchemaNode? = self | |
| for segment in path { | |
| guard let node = current else { return nil } | |
| switch segment { | |
| case let .key(key): | |
| if node.schemaType == "object" { | |
| if let next = node.properties[key] { | |
| current = next | |
| continue | |
| } | |
| if let additional = node.additionalProperties { | |
| current = additional | |
| continue | |
| } | |
| return nil | |
| } | |
| return nil | |
| case .index: | |
| guard node.schemaType == "array" else { return nil } | |
| current = node.items | |
| } | |
| } | |
| return current | |
| } | |
| } | |
| func decodeUiHints(_ raw: [String: Any]) -> [String: ConfigUiHint] { | |
| raw.reduce(into: [:]) { result, entry in | |
| if let hint = entry.value as? [String: Any] { | |
| result[entry.key] = ConfigUiHint(raw: hint) | |
| } | |
| } | |
| } | |
| func hintForPath(_ path: ConfigPath, hints: [String: ConfigUiHint]) -> ConfigUiHint? { | |
| let key = pathKey(path) | |
| if let direct = hints[key] { return direct } | |
| let segments = key.split(separator: ".").map(String.init) | |
| for (hintKey, hint) in hints { | |
| guard hintKey.contains("*") else { continue } | |
| let hintSegments = hintKey.split(separator: ".").map(String.init) | |
| guard hintSegments.count == segments.count else { continue } | |
| var match = true | |
| for (index, seg) in segments.enumerated() { | |
| let hintSegment = hintSegments[index] | |
| if hintSegment != "*", hintSegment != seg { | |
| match = false | |
| break | |
| } | |
| } | |
| if match { return hint } | |
| } | |
| return nil | |
| } | |
| func isSensitivePath(_ path: ConfigPath) -> Bool { | |
| let key = pathKey(path).lowercased() | |
| return key.contains("token") | |
| || key.contains("password") | |
| || key.contains("secret") | |
| || key.contains("apikey") | |
| || key.hasSuffix("key") | |
| } | |
| func pathKey(_ path: ConfigPath) -> String { | |
| path.compactMap { segment -> String? in | |
| switch segment { | |
| case let .key(key): return key | |
| case .index: return nil | |
| } | |
| } | |
| .joined(separator: ".") | |
| } | |