7
7
tokenizer:: tokenize,
8
8
} ,
9
9
std:: {
10
+ borrow:: ToOwned ,
10
11
collections:: { BTreeMap , HashSet } ,
11
12
fs:: read_to_string,
13
+ io:: { self , ErrorKind } ,
12
14
path:: PathBuf ,
13
15
path:: { Component , Path } ,
14
16
} ,
@@ -46,13 +48,43 @@ pub fn load_schemas(
46
48
// Any errors will end up here.
47
49
let mut errors = vec ! [ ] ;
48
50
49
- // Canonicalize the path. This ensures the path doesn't contain `..` or `.`.
50
- let canonical_schema_path = match schema_path. canonicalize ( ) {
51
- Ok ( canonical_schema_path) => canonical_schema_path,
51
+ // The base directory for the schema's dependencies is the directory containing the schema.
52
+ let base_path = if let Some ( base_path) = schema_path. parent ( ) {
53
+ base_path
54
+ } else {
55
+ errors. push ( throw :: < Error > (
56
+ & format ! (
57
+ "{} is not a file." ,
58
+ schema_path. to_string_lossy( ) . code_str( ) ,
59
+ ) ,
60
+ None ,
61
+ None ,
62
+ None ,
63
+ ) ) ;
64
+
65
+ return Err ( errors) ;
66
+ } ;
67
+
68
+ // Canonicalize the path to the base directory. This will be used to calculate namespaces below.
69
+ // Note that even with this we still need `base_path` from above, because canonicalization on
70
+ // Windows adds a `\\?\` prefix to the path, which changes the meaning of `..` and thus prevents
71
+ // us from joining it with other paths containing `..`. Note also that we can't simply
72
+ // compute this by canonicalizing `base_path`, since `base_path` might have zero components,
73
+ // which is considered invalid for canonicalization. So, instead, we canonicalize `schma_path`
74
+ // and take the parent of the result.
75
+ let canonical_base_path = match schema_path
76
+ . canonicalize ( )
77
+ . and_then ( |canonical_schema_path| {
78
+ canonical_schema_path
79
+ . parent ( )
80
+ . map ( ToOwned :: to_owned)
81
+ . ok_or_else ( || io:: Error :: from ( ErrorKind :: Other ) )
82
+ } ) {
83
+ Ok ( canonical_base_path) => canonical_base_path,
52
84
Err ( error) => {
53
85
errors. push ( throw (
54
86
& format ! (
55
- "Unable to load {} ." ,
87
+ "{} is not a file ." ,
56
88
schema_path. to_string_lossy( ) . code_str( ) ,
57
89
) ,
58
90
None ,
@@ -64,11 +96,10 @@ pub fn load_schemas(
64
96
}
65
97
} ;
66
98
67
- // Compute the base directory for the schema's dependencies. Any canonical path which starts
68
- // with this base directory can be safely converted into a namespace
69
- // [tag:canonical_based_paths_are_namespaces].
70
- let base_path = if let Some ( base_path) = canonical_schema_path. parent ( ) {
71
- base_path
99
+ // Relative to the base directory, the path to the schema is the name of the schema file
100
+ // [tag:based_schema_path_is_file_name].
101
+ let based_schema_path = if let Some ( based_schema_path) = schema_path. file_name ( ) {
102
+ AsRef :: < Path > :: as_ref ( based_schema_path)
72
103
} else {
73
104
errors. push ( throw :: < Error > (
74
105
& format ! (
@@ -83,20 +114,19 @@ pub fn load_schemas(
83
114
return Err ( errors) ;
84
115
} ;
85
116
86
- // Strip the base path from the schema path, i.e., compute the schema file name. The `unwrap`
87
- // is safe because we know `base_path` is the parent of `canonical_schema_path` .
88
- let based_schema_path = canonical_schema_path . strip_prefix ( base_path ) . unwrap ( ) ;
117
+ // Compute the namespace of the schema. This is safe due to
118
+ // [ref:based_schema_path_is_file_name] .
119
+ let schema_namespace = path_to_namespace ( based_schema_path ) ;
89
120
90
121
// Initialize the "frontier" with the given path. Paths in the frontier are relative to
91
- // `base_path` [tag:frontier_paths_based]. The path-to-namespace conversion is safe due to
92
- // [ref:canonical_based_paths_are_namespaces].
122
+ // `base_path` [tag:frontier_paths_based].
93
123
let mut schemas_to_load = vec ! [ (
94
- path_to_namespace ( based_schema_path ) ,
124
+ schema_namespace . clone ( ) ,
95
125
based_schema_path. to_owned( ) ,
96
126
None as Option <( PathBuf , String ) >,
97
127
) ] ;
98
- let mut visited_paths = HashSet :: new ( ) ;
99
- visited_paths . insert ( based_schema_path . to_owned ( ) ) ;
128
+ let mut visited_namespaces = HashSet :: new ( ) ;
129
+ visited_namespaces . insert ( schema_namespace ) ;
100
130
101
131
// Perform a depth-first traversal of the transitive dependencies.
102
132
while let Some ( ( namespace, path, origin) ) = schemas_to_load. pop ( ) {
@@ -160,7 +190,7 @@ pub fn load_schemas(
160
190
errors. push ( throw (
161
191
& format ! (
162
192
"Unable to load {}." ,
163
- import . path . to_string_lossy( ) . code_str( ) ,
193
+ non_canonical_import_path . to_string_lossy( ) . code_str( ) ,
164
194
) ,
165
195
Some ( & path) ,
166
196
Some ( & origin_listing) ,
@@ -171,35 +201,37 @@ pub fn load_schemas(
171
201
}
172
202
} ;
173
203
174
- // Strip the base path from the schema path.
175
- let based_import_path =
176
- if let Ok ( based_import_path) = canonical_import_path. strip_prefix ( base_path) {
177
- based_import_path. to_owned ( )
178
- } else {
179
- errors. push ( throw :: < Error > (
180
- & format ! (
181
- "{} is not a descendant of {}, which is the base directory for this \
182
- run.",
183
- canonical_import_path. to_string_lossy( ) . code_str( ) ,
184
- base_path. to_string_lossy( ) . code_str( ) ,
185
- ) ,
186
- Some ( & path) ,
187
- Some ( & origin_listing) ,
188
- None ,
189
- ) ) ;
204
+ // Strip the base path from the schema path. Since this is computed from two canonical
205
+ // paths, its guaranteed to contain only normal components
206
+ // [tag:based_import_path_only_has_normal_components].
207
+ let based_import_path = if let Ok ( based_import_path) =
208
+ canonical_import_path. strip_prefix ( & canonical_base_path)
209
+ {
210
+ based_import_path. to_owned ( )
211
+ } else {
212
+ errors. push ( throw :: < Error > (
213
+ & format ! (
214
+ "{} is not a descendant of {}, which is the base directory for this run." ,
215
+ canonical_import_path. to_string_lossy( ) . code_str( ) ,
216
+ canonical_base_path. to_string_lossy( ) . code_str( ) ,
217
+ ) ,
218
+ Some ( & path) ,
219
+ Some ( & origin_listing) ,
220
+ None ,
221
+ ) ) ;
190
222
191
- continue ;
192
- } ;
223
+ continue ;
224
+ } ;
193
225
194
226
// Populate the namespace of the import [tag:namespace_populated]. The
195
227
// path-to-namespace conversion is safe due to
196
- // [ref:canonical_based_paths_are_namespaces ].
228
+ // [ref:based_import_path_only_has_normal_components ].
197
229
let import_namespace = path_to_namespace ( & based_import_path) ;
198
230
import. namespace = Some ( import_namespace. clone ( ) ) ;
199
231
200
232
// Visit this import if it hasn't been visited already.
201
- if !visited_paths . contains ( & based_import_path ) {
202
- visited_paths . insert ( based_import_path . clone ( ) ) ;
233
+ if !visited_namespaces . contains ( & import_namespace ) {
234
+ visited_namespaces . insert ( import_namespace . clone ( ) ) ;
203
235
schemas_to_load. push ( (
204
236
import_namespace,
205
237
based_import_path,
@@ -283,7 +315,6 @@ mod tests {
283
315
284
316
// This test doesn't work on Windows, for some reason.
285
317
#[ test]
286
- #[ cfg_attr( target_os = "windows" , ignore) ]
287
318
fn load_schemas_example ( ) {
288
319
load_schemas ( Path :: new ( "integration_tests/types/main.t" ) ) . unwrap ( ) ;
289
320
}
0 commit comments