1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 | const Hoek = require('hoek');
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | const internals = {};
|
11 |
|
12 |
|
13 | exports = module.exports = internals.Segment = function () {
|
14 |
|
15 | this._edge = null;
|
16 | this._fulls = null;
|
17 | this._literals = null;
|
18 | this._param = null;
|
19 | this._mixed = null;
|
20 | this._wildcard = null;
|
21 | };
|
22 |
|
23 |
|
24 | internals.Segment.prototype.add = function (segments, record) {
|
25 |
|
26 | |
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | const current = segments[0];
|
34 | const remaining = segments.slice(1);
|
35 | const isEdge = !remaining.length;
|
36 |
|
37 | const literals = [];
|
38 | let isLiteral = true;
|
39 | for (let i = 0; i < segments.length && isLiteral; ++i) {
|
40 | isLiteral = segments[i].literal !== undefined;
|
41 | literals.push(segments[i].literal);
|
42 | }
|
43 |
|
44 | if (isLiteral) {
|
45 | this._fulls = this._fulls || {};
|
46 | let literal = '/' + literals.join('/');
|
47 | if (!record.settings.isCaseSensitive) {
|
48 | literal = literal.toLowerCase();
|
49 | }
|
50 |
|
51 | Hoek.assert(!this._fulls[literal], 'New route', record.path, 'conflicts with existing', this._fulls[literal] && this._fulls[literal].record.path);
|
52 | this._fulls[literal] = { segment: current, record };
|
53 | }
|
54 | else if (current.literal !== undefined) {
|
55 |
|
56 |
|
57 |
|
58 | this._literals = this._literals || {};
|
59 | const currentLiteral = (record.settings.isCaseSensitive ? current.literal : current.literal.toLowerCase());
|
60 | this._literals[currentLiteral] = this._literals[currentLiteral] || new internals.Segment();
|
61 | this._literals[currentLiteral].add(remaining, record);
|
62 | }
|
63 | else if (current.wildcard) {
|
64 |
|
65 |
|
66 |
|
67 | Hoek.assert(!this._wildcard, 'New route', record.path, 'conflicts with existing', this._wildcard && this._wildcard.record.path);
|
68 | Hoek.assert(!this._param || !this._param._wildcard, 'New route', record.path, 'conflicts with existing', this._param && this._param._wildcard && this._param._wildcard.record.path);
|
69 | this._wildcard = { segment: current, record };
|
70 | }
|
71 | else if (current.mixed) {
|
72 |
|
73 |
|
74 |
|
75 | this._mixed = this._mixed || [];
|
76 |
|
77 | let mixed = this._mixedLookup(current);
|
78 | if (!mixed) {
|
79 | mixed = { segment: current, node: new internals.Segment() };
|
80 | this._mixed.push(mixed);
|
81 | this._mixed.sort(internals.mixed);
|
82 | }
|
83 |
|
84 | if (isEdge) {
|
85 | Hoek.assert(!mixed.node._edge, 'New route', record.path, 'conflicts with existing', mixed.node._edge && mixed.node._edge.record.path);
|
86 | mixed.node._edge = { segment: current, record };
|
87 | }
|
88 | else {
|
89 | mixed.node.add(remaining, record);
|
90 | }
|
91 | }
|
92 | else {
|
93 |
|
94 |
|
95 |
|
96 | this._param = this._param || new internals.Segment();
|
97 |
|
98 | if (isEdge) {
|
99 | Hoek.assert(!this._param._edge, 'New route', record.path, 'conflicts with existing', this._param._edge && this._param._edge.record.path);
|
100 | this._param._edge = { segment: current, record };
|
101 | }
|
102 | else {
|
103 | Hoek.assert(!this._wildcard || !remaining[0].wildcard, 'New route', record.path, 'conflicts with existing', this._wildcard && this._wildcard.record.path);
|
104 | this._param.add(remaining, record);
|
105 | }
|
106 | }
|
107 | };
|
108 |
|
109 |
|
110 | internals.Segment.prototype._mixedLookup = function (segment) {
|
111 |
|
112 | for (let i = 0; i < this._mixed.length; ++i) {
|
113 | if (internals.mixed({ segment }, this._mixed[i]) === 0) {
|
114 | return this._mixed[i];
|
115 | }
|
116 | }
|
117 |
|
118 | return null;
|
119 | };
|
120 |
|
121 |
|
122 | internals.mixed = function (a, b) {
|
123 |
|
124 | const aFirst = -1;
|
125 | const bFirst = 1;
|
126 |
|
127 | const as = a.segment;
|
128 | const bs = b.segment;
|
129 |
|
130 | if (as.length !== bs.length) {
|
131 | return (as.length > bs.length ? aFirst : bFirst);
|
132 | }
|
133 |
|
134 | if (as.first !== bs.first) {
|
135 | return (as.first ? bFirst : aFirst);
|
136 | }
|
137 |
|
138 | for (let i = 0; i < as.segments.length; ++i) {
|
139 | const am = as.segments[i];
|
140 | const bm = bs.segments[i];
|
141 |
|
142 | if (am === bm) {
|
143 | continue;
|
144 | }
|
145 |
|
146 | if (am.length === bm.length) {
|
147 | return (am > bm ? bFirst : aFirst);
|
148 | }
|
149 |
|
150 | return (am.length < bm.length ? bFirst : aFirst);
|
151 | }
|
152 |
|
153 | return 0;
|
154 | };
|
155 |
|
156 |
|
157 | internals.Segment.prototype.lookup = function (path, segments, options) {
|
158 |
|
159 | let match = null;
|
160 |
|
161 |
|
162 |
|
163 | if (this._fulls) {
|
164 | match = this._fulls[options.isCaseSensitive ? path : path.toLowerCase()];
|
165 | if (match) {
|
166 | return { record: match.record, array: [] };
|
167 | }
|
168 | }
|
169 |
|
170 |
|
171 |
|
172 | const current = segments[0];
|
173 | const nextPath = path.slice(current.length + 1);
|
174 | const remainder = (segments.length > 1 ? segments.slice(1) : null);
|
175 |
|
176 | if (this._literals) {
|
177 | const literal = options.isCaseSensitive ? current : current.toLowerCase();
|
178 | match = this._literals.hasOwnProperty(literal) && this._literals[literal];
|
179 | if (match) {
|
180 | const record = internals.deeper(match, nextPath, remainder, [], options);
|
181 | if (record) {
|
182 | return record;
|
183 | }
|
184 | }
|
185 | }
|
186 |
|
187 |
|
188 |
|
189 | if (this._mixed) {
|
190 | for (let i = 0; i < this._mixed.length; ++i) {
|
191 | match = this._mixed[i];
|
192 | const params = current.match(match.segment.mixed);
|
193 | if (params) {
|
194 | const array = [];
|
195 | for (let j = 1; j < params.length; ++j) {
|
196 | array.push(params[j]);
|
197 | }
|
198 |
|
199 | const record = internals.deeper(match.node, nextPath, remainder, array, options);
|
200 | if (record) {
|
201 | return record;
|
202 | }
|
203 | }
|
204 | }
|
205 | }
|
206 |
|
207 |
|
208 |
|
209 | if (this._param) {
|
210 | if (current ||
|
211 | (this._param._edge && this._param._edge.segment.empty)) {
|
212 |
|
213 | const record = internals.deeper(this._param, nextPath, remainder, [current], options);
|
214 | if (record) {
|
215 | return record;
|
216 | }
|
217 | }
|
218 | }
|
219 |
|
220 |
|
221 |
|
222 | if (this._wildcard) {
|
223 | return { record: this._wildcard.record, array: [path.slice(1)] };
|
224 | }
|
225 |
|
226 | return null;
|
227 | };
|
228 |
|
229 |
|
230 | internals.deeper = function (match, path, segments, array, options) {
|
231 |
|
232 | if (!segments) {
|
233 | if (match._edge) {
|
234 | return { record: match._edge.record, array };
|
235 | }
|
236 |
|
237 | if (match._wildcard) {
|
238 | return { record: match._wildcard.record, array };
|
239 | }
|
240 | }
|
241 | else {
|
242 | const result = match.lookup(path, segments, options);
|
243 | if (result) {
|
244 | return { record: result.record, array: array.concat(result.array) };
|
245 | }
|
246 | }
|
247 |
|
248 | return null;
|
249 | };
|