-
Notifications
You must be signed in to change notification settings - Fork 76
/
Copy pathRefResolver.swift
140 lines (110 loc) · 3.07 KB
/
RefResolver.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import Foundation
func urlSplitFragment(url: String) -> (String, String) {
guard let hashIndex = url.index(of: "#") else {
return (url, "")
}
return (
String(url.prefix(upTo: hashIndex)),
String(url.suffix(from: url.index(after: hashIndex)))
)
}
func urlJoin(_ lhs: String, _ rhs: String) -> String {
if lhs.isEmpty {
return rhs
}
if rhs.isEmpty {
return lhs
}
return URL(string: rhs, relativeTo: URL(string: lhs)!)!.absoluteString
}
func urlNormalise(_ value: String) -> String {
if value.hasSuffix("#"), let index = value.lastIndex(of: "#") {
return String(value.prefix(upTo: index))
}
return value
}
func urlEqual(_ lhs: String, _ rhs: String) -> Bool {
return urlNormalise(lhs) == urlNormalise(rhs)
}
class RefResolver {
let referrer: [String: Any]
var store: [String: Any]
var stack: [String]
let idField: String
let defsField: String
init(schema: [String: Any], metaschemes: [String: Any], idField: String = "$id", defsField: String = "$defs") {
self.referrer = schema
self.store = metaschemes
self.idField = idField
self.defsField = defsField
if let id = schema[idField] as? String {
self.store[id] = schema
self.stack = [id]
} else {
self.store[""] = schema
self.stack = [""]
}
storeDefinitions(from: schema)
}
init(resolver: RefResolver) {
referrer = resolver.referrer
store = resolver.store
stack = resolver.stack
idField = resolver.idField
defsField = resolver.defsField
}
func storeDefinitions(from document: Any) {
guard
let document = document as? [String: Any],
let defs = document[defsField] as? [String: Any]
else {
return
}
for (_, defs) in defs {
guard let def = defs as? [String: Any] else { continue }
let id = def[idField] as? String
let anchor = def["$anchor"] as? String
let url: String
if let anchor = anchor {
url = urlJoin(stack.last!, "\(id ?? "")#\(anchor)")
} else if let id = id {
url = urlJoin(stack.last!, id)
} else { continue }
self.store[url] = def
// recurse
self.stack.append(url)
storeDefinitions(from: def)
self.stack.removeLast()
}
}
func resolve(reference: String) -> Any? {
let url = urlJoin(stack.last!, reference)
return resolve(url: url)
}
func resolve(url: String) -> Any? {
if let document = store[url] {
return document
}
let (url, fragment) = urlSplitFragment(url: url)
guard let document = store[url] else {
return nil
}
if let document = document as? [String: Any] {
return resolve(document: document, fragment: fragment)
}
if fragment == "" {
return document
}
return nil
}
func resolve(document: [String: Any], fragment: String) -> Any? {
guard !fragment.isEmpty else {
return document
}
guard let reference = (fragment as NSString).removingPercentEncoding else {
return nil
}
let pointer = JSONPointer(path: reference)
return pointer.resolve(document: document)
}
}