mirror of
https://github.com/clawdbot/clawdbot.git
synced 2026-02-01 03:47:45 +01:00
Mac: finish Moltbot rename (paths)
This commit is contained in:
204
apps/macos/Sources/Moltbot/ConfigSchemaSupport.swift
Normal file
204
apps/macos/Sources/Moltbot/ConfigSchemaSupport.swift
Normal file
@@ -0,0 +1,204 @@
|
||||
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: ".")
|
||||
}
|
||||
Reference in New Issue
Block a user