graphql_schema_diff/path/
parse.rs1use super::*;
2
3impl<'a> Path<'a> {
4 pub fn parse(s: &'a str) -> ParseResult<Path<'a>> {
5 let mut segments = s.split('.');
6
7 let first_segment = segments.next().filter(|s| !s.is_empty()).ok_or(ParseError)?;
8
9 if let Some(suffix) = first_segment.strip_prefix(":schema") {
11 if segments.next().is_some() {
12 return Err(ParseError);
13 }
14
15 if suffix.is_empty() {
16 return Ok(Path::SchemaDefinition);
17 }
18
19 let index = parse_index(suffix)?;
20 return Ok(Path::SchemaExtension(index));
21 }
22
23 if let Some(name) = first_segment.strip_prefix('@') {
25 if !is_valid_graphql_name(name) {
26 return Err(ParseError);
27 }
28
29 if segments.next().is_some() {
30 return Err(ParseError);
31 }
32
33 return Ok(Path::DirectiveDefinition(name));
34 }
35
36 let (name, idx) = parse_name_and_optional_index(first_segment)?;
38
39 if let Some(idx) = idx {
40 Ok(Path::TypeExtension(name, idx, Self::parse_path_in_type(segments)?))
41 } else {
42 Ok(Path::TypeDefinition(name, Self::parse_path_in_type(segments)?))
43 }
44 }
45
46 fn parse_path_in_type(mut segments: impl Iterator<Item = &'a str>) -> ParseResult<Option<PathInType<'a>>> {
47 let segment = match segments.next() {
48 Some("") => return Err(ParseError),
49 Some(segment) => segment,
50 None => return Ok(None),
51 };
52
53 if let Some(suffix) = segment.strip_prefix('@') {
55 let (name, idx) = parse_name_and_optional_index(suffix)?;
56
57 if segments.next().is_some() {
58 return Err(ParseError);
59 }
60
61 let Some(idx) = idx else {
62 return Err(ParseError);
63 };
64
65 return Ok(Some(PathInType::InDirective(name, idx)));
66 }
67
68 if let Some(suffix) = segment.strip_prefix('&') {
70 if !is_valid_graphql_name(suffix) {
71 return Err(ParseError);
72 }
73
74 if segments.next().is_some() {
75 return Err(ParseError);
76 }
77
78 return Ok(Some(PathInType::InterfaceImplementation(suffix)));
79 }
80
81 if !is_valid_graphql_name(segment) {
83 return Err(ParseError);
84 }
85
86 Ok(Some(PathInType::InField(segment, Self::parse_path_in_field(segments)?)))
87 }
88
89 fn parse_path_in_field(mut segments: impl Iterator<Item = &'a str>) -> ParseResult<Option<PathInField<'a>>> {
90 let segment = match segments.next() {
91 Some("") => return Err(ParseError),
92 Some(segment) => segment,
93 None => return Ok(None),
94 };
95
96 if let Some(suffix) = segment.strip_prefix('@') {
98 let (name, Some(idx)) = parse_name_and_optional_index(suffix)? else {
99 return Err(ParseError);
100 };
101
102 if segments.next().is_some() {
103 return Err(ParseError);
104 }
105
106 return Ok(Some(PathInField::InDirective(name, idx)));
107 }
108
109 if !is_valid_graphql_name(segment) {
111 return Err(ParseError);
112 }
113
114 Ok(Some(PathInField::InArgument(
115 segment,
116 Self::parse_path_in_argument(segments)?,
117 )))
118 }
119
120 fn parse_path_in_argument(mut segments: impl Iterator<Item = &'a str>) -> ParseResult<Option<PathInArgument<'a>>> {
121 let segment = match segments.next() {
122 Some("") => return Err(ParseError),
123 Some(segment) => segment,
124 None => return Ok(None),
125 };
126
127 if segments.next().is_some() {
128 return Err(ParseError);
129 }
130
131 let suffix = segment.strip_prefix('@').ok_or(ParseError)?;
132 let (name, Some(idx)) = parse_name_and_optional_index(suffix)? else {
133 return Err(ParseError);
134 };
135
136 Ok(Some(PathInArgument::InDirective(name, idx)))
137 }
138}
139
140fn parse_name_and_optional_index(s: &str) -> ParseResult<(&str, Option<usize>)> {
141 let bracket_idx = s.chars().position(|c| c == '[');
142
143 let name = bracket_idx.map(|idx| &s[..idx]).unwrap_or(s);
144
145 if !is_valid_graphql_name(name) {
146 return Err(ParseError);
147 }
148
149 let idx = if let Some(bracket_idx) = bracket_idx {
150 Some(parse_index(&s[bracket_idx..])?)
151 } else {
152 None
153 };
154
155 Ok((name, idx))
156}
157
158fn parse_index(s: &str) -> ParseResult<usize> {
159 if !s.starts_with('[') || !s.ends_with(']') {
160 return Err(ParseError);
161 }
162
163 let idx = &s[1..s.len() - 1];
164
165 if !idx.chars().any(|char| char.is_ascii_digit()) {
166 return Err(ParseError);
167 }
168
169 idx.parse::<usize>().map_err(|_| ParseError)
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn parse_index_tests() {
178 assert_eq!(parse_index("[0]").unwrap(), 0);
179 assert_eq!(parse_index("[10]").unwrap(), 10);
180 assert_eq!(parse_index("[1243247]").unwrap(), 1243247);
181 assert_eq!(parse_index("[03]").unwrap(), 3);
182
183 assert!(parse_index("3").is_err());
184 assert!(parse_index("[3").is_err());
185 assert!(parse_index("3]").is_err());
186 assert!(parse_index("[[3]]").is_err());
187 assert!(parse_index("[0x3]").is_err());
188 assert!(parse_index("[-3]").is_err());
189 }
190}