-
Notifications
You must be signed in to change notification settings - Fork 85
/
suggestion.rs
313 lines (305 loc) · 16.6 KB
/
suggestion.rs
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
use std::{
cmp::Ordering,
fmt::{self, Display, Write as _},
};
use camino::Utf8PathBuf;
use rover_client::shared::GraphRef;
use rover_std::Style;
use serde::Serialize;
use crate::utils::env::RoverEnvKey;
/// `Suggestion` contains possible suggestions for remedying specific errors.
#[derive(Clone, Serialize, Debug)]
pub enum RoverErrorSuggestion {
SubmitIssue,
SetConfigHome,
MigrateConfigHomeOrCreateConfig,
CreateConfig,
RecreateConfig(String),
ListProfiles,
UseFederatedGraph,
UseContractVariant,
RunComposition,
CheckGraphNameAndAuth,
ProvideValidSubgraph(Vec<String>),
ProvideValidVariant {
graph_ref: GraphRef,
valid_variants: Vec<String>,
frontend_url_root: String,
},
Adhoc(String),
CheckKey,
TryUnsetKey,
ValidComposeFile,
ValidComposeRoutingUrl,
ProperKey,
NewUserNoProfiles,
CheckServerConnection,
CheckResponseType,
ConvertGraphToSubgraph,
CheckGnuVersion,
FixSubgraphSchema {
graph_ref: GraphRef,
subgraph: String,
},
FixSupergraphConfigErrors,
FixCompositionErrors {
num_subgraphs: usize,
},
FixContractPublishErrors,
FixCheckFailures,
FixOperationsInSchema {
graph_ref: GraphRef,
},
FixDownstreamCheckFailure {
target_url: String,
},
FixOtherCheckTaskFailure {
target_url: String,
},
FixLintFailure,
IncreaseClientTimeout,
IncreaseChecksTimeout {
url: Option<String>,
},
FixChecksInput {
graph_ref: GraphRef,
},
UpgradePlan,
ProvideRoutingUrl {
subgraph_name: String,
graph_ref: GraphRef,
},
LinkPersistedQueryList {
graph_ref: GraphRef,
frontend_url_root: String,
},
CreateOrFindValidPersistedQueryList {
graph_id: String,
frontend_url_root: String,
},
AddRoutingUrlToSupergraphYaml,
InvalidSupergraphYamlSubgraphSchemaPath {
subgraph_name: String,
supergraph_yaml_path: Utf8PathBuf,
},
PublishSubgraphWithRoutingUrl {
subgraph_name: String,
graph_ref: String,
},
AllowInvalidRoutingUrlOrSpecifyValidUrl,
ContactApolloAccountManager,
TryAgainLater,
}
impl Display for RoverErrorSuggestion {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
use RoverErrorSuggestion::*;
let suggestion = match self {
SubmitIssue => {
format!("This error was unexpected! Please submit an issue with any relevant details about what you were trying to do: {}", Style::Link.paint("https://github.com/apollographql/rover/issues/new/choose"))
}
SetConfigHome => {
format!(
"You can override this path by setting the {} environment variable.",
Style::Command.paint(format!("${}", RoverEnvKey::ConfigHome))
)
}
MigrateConfigHomeOrCreateConfig => {
format!("If you've recently changed the {} environment variable, you may need to migrate your old configuration directory to the new path. Otherwise, try setting up a new configuration profile by running {}.",
Style::Command.paint(format!("${}", RoverEnvKey::ConfigHome)),
Style::Command.paint("`rover config auth`"))
}
CreateConfig => {
format!(
"Try setting up a configuration profile by running {}",
Style::Command.paint("`rover config auth`")
)
}
RecreateConfig(profile_name) => {
format!("Recreate this configuration profile by running {}.", Style::Command.paint(format!("`rover config auth{}`", match profile_name.as_str() {
"default" => "".to_string(),
profile_name => format!(" --profile {}", profile_name)
})))
}
ListProfiles => {
format!(
"Try running {} to see the possible values for the {} argument.",
Style::Command.paint("`rover config list`"),
Style::Command.paint("`--profile`")
)
}
RunComposition => {
format!("Try resolving the build errors in your subgraph(s), and publish them with the {} command.", Style::Command.paint("`rover subgraph publish`"))
}
UseFederatedGraph => {
"Try running the command on a valid federated graph, or use the appropriate `rover graph` command instead of `rover subgraph`.".to_string()
}
UseContractVariant => {
"Try running the command on a valid contract variant.".to_string()
}
CheckGraphNameAndAuth => {
format!(
"Make sure your graph name is typed correctly, and that your API key is valid.\n You can run {} to check if you are authenticated.\n If you are trying to create a new graph, you must do so online at {}, by clicking \"New Graph\".",
Style::Command.paint("`rover config whoami`"),
Style::Link.paint("https://studio.apollographql.com")
)
}
ProvideValidSubgraph(valid_subgraphs) => {
format!(
"Try running this command with one of the following valid subgraphs: [{}]",
valid_subgraphs.join(", ")
)
}
ProvideValidVariant { graph_ref, valid_variants, frontend_url_root} => {
if let Some(maybe_variant) = did_you_mean(&graph_ref.variant, valid_variants).pop() {
format!("Did you mean \"{}@{}\"?", graph_ref.name, maybe_variant)
} else {
let num_valid_variants = valid_variants.len();
let color_graph_name = Style::Link.paint(&graph_ref.name);
match num_valid_variants {
0 => format!("Graph {} exists, but has no variants. You can create a new monolithic variant by running {} for your graph schema, or a new federated variant by running {} for all of your subgraph schemas.", &color_graph_name, Style::Command.paint("`rover graph publish`"), Style::Command.paint("`rover subgraph publish`")),
1 => format!("The only existing variant for graph {} is {}.", &color_graph_name, Style::Link.paint(&valid_variants[0])),
2 => format!("The existing variants for graph {} are {} and {}.", &color_graph_name, Style::Link.paint(&valid_variants[0]), Style::Link.paint(&valid_variants[1])),
3 ..= 10 => {
let mut valid_variants_msg = "".to_string();
for (i, variant) in valid_variants.iter().enumerate() {
if i == num_valid_variants - 1 {
valid_variants_msg.push_str("and ");
}
let _ = write!(valid_variants_msg, "{}", Style::Link.paint(variant));
if i < num_valid_variants - 1 {
valid_variants_msg.push_str(", ");
}
}
format!("The existing variants for graph {} are {}.", &color_graph_name, &valid_variants_msg)
}
_ => {
let graph_url = format!("{}/graph/{}/settings", &frontend_url_root, &color_graph_name);
format!("You can view the variants for graph \"{}\" by visiting {}", &color_graph_name, Style::Link.paint(graph_url))
}
}
}
}
CheckKey => {
"Check your API key to make sure it's valid (are you using the right profile?).".to_string()
}
TryUnsetKey => {
format!(
"Try to unset your {} key if you want to use {}.",
Style::Command.paint(format!("`${}`", RoverEnvKey::Key)),
Style::Command.paint("`--profile default`")
)
}
ProperKey => {
format!("Try running {} for more details on Apollo's API keys.", Style::Command.paint("`rover docs open api-keys`"))
}
ValidComposeFile => {
"Make sure supergraph compose config YAML points to a valid schema file.".to_string()
}
ValidComposeRoutingUrl=> {
"When trying to compose with a local .graphql file, make sure you supply a `routing_url` in your config YAML.".to_string()
}
NewUserNoProfiles => {
format!("It looks like you may be new here. Welcome! To authenticate with Apollo Studio, run {}, or set {} to a valid Apollo Studio API key.",
Style::Command.paint("`rover config auth`"), Style::Command.paint(format!("`${}`", RoverEnvKey::Key))
)
}
Adhoc(msg) => msg.to_string(),
CheckServerConnection => "Make sure the endpoint is accepting connections and is spelled correctly".to_string(),
CheckResponseType => "Make sure the endpoint you specified is returning JSON data as its response".to_string(),
ConvertGraphToSubgraph => "If you are sure you want to convert a non-federated graph to a subgraph, you can re-run the same command with a `--convert` flag.".to_string(),
CheckGnuVersion => {
let mut suggestion = "It looks like you are running a Rover binary that does not have the ability to run composition, please try re-installing.";
if cfg!(target_env = "musl") {
suggestion = "Unfortunately, Deno does not currently support musl architectures, and as of yet, there is no native composition implementation in Rust. You can follow along with this issue for updates on musl support: https://github.com/denoland/deno/issues/3711, for now you will need to switch to a Linux distribution (like Ubuntu or CentOS) that can run Rover's prebuilt binaries.";
}
suggestion.to_string()
},
FixSubgraphSchema { graph_ref, subgraph } => format!("The changes in the schema you proposed for subgraph {} are incompatible with supergraph {}. See {} for more information on resolving build errors.", Style::Link.paint(subgraph), Style::Link.paint(graph_ref.to_string()), Style::Link.paint("https://www.apollographql.com/docs/federation/errors/")),
FixSupergraphConfigErrors => {
format!("See {} for information on the config format.", Style::Link.paint("https://www.apollographql.com/docs/rover/commands/supergraphs#yaml-configuration-file"))
}
FixCompositionErrors { num_subgraphs } => {
let prefix = match num_subgraphs {
1 => "The subgraph schema you provided is invalid.".to_string(),
_ => "The subgraph schemas you provided are incompatible with each other.".to_string()
};
format!("{} See {} for more information on resolving build errors.", prefix, Style::Link.paint("https://www.apollographql.com/docs/federation/errors/"))
},
FixContractPublishErrors => {
format!("Try resolving any configuration errors, and publish the configuration with the {} command.", Style::Command.paint("`rover contract publish`"))
},
FixCheckFailures => format!(
"See {} for more information on resolving check errors.",
Style::Link.paint("https://www.apollographql.com/docs/graphos/delivery/schema-checks")
),
FixOperationsInSchema { graph_ref } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving operation check errors.", Style::Link.paint(graph_ref.to_string()), Style::Link.paint("https://www.apollographql.com/docs/studio/schema-checks/")),
FixDownstreamCheckFailure { target_url } => format!("The changes in the schema you proposed cause checks to fail for blocking downstream variants. See {} to view the failure reasons for these downstream checks.", Style::Link.paint(target_url)),
FixOtherCheckTaskFailure { target_url } => format!("See {} to view the failure reason for the check.", Style::Link.paint(target_url)),
FixLintFailure => "The schema you submitted contains lint violations. Please address the violations and resubmit the schema.".to_string(),
IncreaseClientTimeout => "You can try increasing the timeout value by passing a higher value to the --client-timeout option.".to_string(),
IncreaseChecksTimeout {url} => format!("You can try increasing the timeout value by setting APOLLO_CHECKS_TIMEOUT_SECONDS to a higher value in your env. The default value is 300 seconds. You can also view the live check progress by visiting {}.", Style::Link.paint(url.clone().unwrap_or_else(|| "https://studio.apollographql.com".to_string()))),
FixChecksInput { graph_ref } => format!("Graph {} has no published schema or is not a composition variant. Please publish a schema or use a different variant.", Style::Link.paint(graph_ref.to_string())),
UpgradePlan => "Rover has likely reached rate limits while running graph or subgraph checks. Please try again later or contact your graph admin about upgrading your billing plan.".to_string(),
ProvideRoutingUrl { subgraph_name, graph_ref } => {
format!("The subgraph {} does not exist for {}. You cannot add a subgraph to a supergraph without a routing URL.
Try re-running this command with a `--routing-url` argument.", subgraph_name, Style::Link.paint(graph_ref.to_string()))
}
LinkPersistedQueryList { graph_ref, frontend_url_root } => {
format!("Link a persisted query list to {graph_ref} by heading to {frontend_url_root}/graph/{id}/persisted-queries", id = graph_ref.name)
}
CreateOrFindValidPersistedQueryList { graph_id, frontend_url_root } => {
format!("Find existing persisted query lists associated with '{graph_id}' or create a new one by heading to {frontend_url_root}/graph/{graph_id}/persisted-queries")
},
AddRoutingUrlToSupergraphYaml => {
String::from("Try specifying a routing URL in the supergraph YAML file. See https://www.apollographql.com/docs/rover/commands/supergraphs/#yaml-configuration-file for more details.")
},
PublishSubgraphWithRoutingUrl { graph_ref, subgraph_name } => {
format!("Try publishing the subgraph with a routing URL like so `rover subgraph publish {graph_ref} --name {subgraph_name} --routing-url <url>`")
},
AllowInvalidRoutingUrlOrSpecifyValidUrl => format!("Try publishing the subgraph with a valid routing URL. If you are sure you want to publish an invalid routing URL, re-run this command with the {} option.", Style::Command.paint("`--allow-invalid-routing-url`")),
ContactApolloAccountManager => {"Discuss your requirements with your Apollo point of contact.".to_string()},
TryAgainLater => {"Please try again later.".to_string()},
InvalidSupergraphYamlSubgraphSchemaPath {
subgraph_name, supergraph_yaml_path
} => format!("Make sure the specified path for subgraph '{}' is relative to the location of the supergraph schema file ({})", subgraph_name, Style::Path.paint(supergraph_yaml_path))
};
write!(formatter, "{}", &suggestion)
}
}
// source: https://github.com/clap-rs/clap/blob/a0269a41d4abaf4b0a9ec4f9a059fe62ea0ba3a7/src/parse/features/suggestions.rs
/// returns a value that the user may have intended to type
fn did_you_mean<T, I>(value: &str, possible_values: I) -> Vec<String>
where
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
let mut candidates: Vec<(f64, String)> = possible_values
.into_iter()
.map(|possible_value| {
(
strsim::jaro_winkler(value, possible_value.as_ref()),
possible_value.as_ref().to_owned(),
)
})
.filter(|(confidence, _)| *confidence > 0.8)
.collect();
candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
candidates.into_iter().map(|(_, pv)| pv).collect()
}
mod test {
#[test]
fn possible_values_match() {
let p_vals = ["test", "possible", "values"];
assert_eq!(
super::did_you_mean("tst", p_vals.iter()).pop(),
Some("test".to_string())
);
}
#[test]
fn possible_values_nomatch() {
let p_vals = ["test", "possible", "values"];
assert!(super::did_you_mean("hahaahahah", p_vals.iter())
.pop()
.is_none());
}
}