Skip to content

Commit f042132

Browse files
jimschubertwing328
authored andcommitted
Cli error message improvements (#172)
* Errors in Generate/Validate print to stderr/exit 1 Generate and Validate exposed exceptions rather than user-friendly messages when an error occurred. In generate, this could happen for numerous reasons, but the most likely is a user typing (or guessing) an invalid generator name. In Validate, an error was exposed if there were any validation errors in a spec. New behavior: * Generate now exposes a typed exception when a generator cannot be loaded by name. This allows consistent messaging for load failures. * Generate now presents guidance on failure (check the spelling and try again). This is purely a usability improvement. * Validate now writes validation errors to stderr and exits with code 1. * Improve err messages: config-help/required opts. config-help now presents same error for invalid generator names as the 'generate' command. Options which are required, and those which require a value, now present a user-friendly hint at the error and exit with code 1 (rather than an uncaught exception). * Log missing -g error to stderr rather than LOGGER
1 parent 5a87fe6 commit f042132

7 files changed

Lines changed: 126 additions & 44 deletions

File tree

modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/OpenAPIGenerator.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import io.airlift.airline.Cli;
2121
import io.airlift.airline.Help;
22+
import io.airlift.airline.ParseOptionMissingException;
23+
import io.airlift.airline.ParseOptionMissingValueException;
2224
import org.openapitools.codegen.cmd.*;
2325

2426
import java.util.Arrays;
@@ -53,8 +55,6 @@ public static void main(String[] args) {
5355
Version.class
5456
);
5557

56-
builder.build().parse(args).run();
57-
5858
// If CLI is run without a command, consider this an error.
5959
// We can check against empty args because unrecognized arguments/commands result in an exception.
6060
// This is useful to exit with status 1, for example, so that misconfigured scripts fail fast.
@@ -64,5 +64,12 @@ public static void main(String[] args) {
6464
if (args.length == 0) {
6565
System.exit(1);
6666
}
67+
68+
try {
69+
builder.build().parse(args).run();
70+
} catch (ParseOptionMissingException | ParseOptionMissingValueException e) {
71+
System.err.printf("[error] %s%n", e.getMessage());
72+
System.exit(1);
73+
}
6774
}
6875
}

modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.openapitools.codegen.CliOption;
2323
import org.openapitools.codegen.CodegenConfig;
2424
import org.openapitools.codegen.CodegenConfigLoader;
25+
import org.openapitools.codegen.GeneratorNotFoundException;
2526

2627
@Command(name = "config-help", description = "Config help for chosen lang")
2728
public class ConfigHelp implements Runnable {
@@ -32,14 +33,20 @@ public class ConfigHelp implements Runnable {
3233

3334
@Override
3435
public void run() {
35-
System.out.println();
36-
CodegenConfig config = CodegenConfigLoader.forName(lang);
37-
System.out.println("CONFIG OPTIONS");
38-
for (CliOption langCliOption : config.cliOptions()) {
39-
System.out.println("\t" + langCliOption.getOpt());
40-
System.out.println("\t "
41-
+ langCliOption.getOptionHelp().replaceAll("\n", "\n\t "));
36+
try {
37+
CodegenConfig config = CodegenConfigLoader.forName(lang);
4238
System.out.println();
39+
System.out.println("CONFIG OPTIONS");
40+
for (CliOption langCliOption : config.cliOptions()) {
41+
System.out.println("\t" + langCliOption.getOpt());
42+
System.out.println("\t "
43+
+ langCliOption.getOptionHelp().replaceAll("\n", System.lineSeparator() + "\t "));
44+
System.out.println();
45+
}
46+
} catch (GeneratorNotFoundException e) {
47+
System.err.println(e.getMessage());
48+
System.err.println("[error] Check the spelling of the generator's name and try again.");
49+
System.exit(1);
4350
}
4451
}
4552
}

modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
import org.openapitools.codegen.ClientOptInput;
2323
import org.openapitools.codegen.CodegenConstants;
2424
import org.openapitools.codegen.DefaultGenerator;
25+
import org.openapitools.codegen.GeneratorNotFoundException;
2526
import org.openapitools.codegen.config.CodegenConfigurator;
2627
import org.slf4j.Logger;
2728
import org.slf4j.LoggerFactory;
2829

29-
import static org.apache.commons.lang3.StringUtils.isEmpty;
3030
import static org.openapitools.codegen.config.CodegenConfiguratorUtils.*;
3131
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
3232

@@ -226,7 +226,7 @@ public void run() {
226226
LOGGER.warn("The '--lang' and '-l' are deprecated and may reference language names only in the next major release (4.0). Please use --generator-name /-g instead.");
227227
configurator.setGeneratorName(lang);
228228
} else {
229-
LOGGER.error("A generator name (--generator-name / -g) is required. ");
229+
System.err.println("[error] A generator name (--generator-name / -g) is required.");
230230
System.exit(1);
231231
}
232232

@@ -309,8 +309,14 @@ public void run() {
309309
applyAdditionalPropertiesKvpList(additionalProperties, configurator);
310310
applyLanguageSpecificPrimitivesCsvList(languageSpecificPrimitives, configurator);
311311
applyReservedWordsMappingsKvpList(reservedWordsMappings, configurator);
312-
final ClientOptInput clientOptInput = configurator.toClientOptInput();
313312

314-
new DefaultGenerator().opts(clientOptInput).generate();
313+
try {
314+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
315+
new DefaultGenerator().opts(clientOptInput).generate();
316+
} catch (GeneratorNotFoundException e) {
317+
System.err.println(e.getMessage());
318+
System.err.println("[error] Check the spelling of the generator's name and try again.");
319+
System.exit(1);
320+
}
315321
}
316322
}

modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Validate.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,24 @@ public class Validate implements Runnable {
3636

3737
@Override
3838
public void run() {
39-
System.out.println("Validating spec file (" + spec + ")");
39+
System.out.println("Validating spec (" + spec + ")");
4040

4141
SwaggerParseResult result = new OpenAPIParser().readLocation(spec, null, null);
4242
List<String> messageList = result.getMessages();
4343
Set<String> messages = new HashSet<String>(messageList);
4444

45-
for (String message : messages) {
46-
System.out.println(message);
47-
}
48-
4945
if (messages.size() > 0) {
50-
throw new ValidateException();
46+
StringBuilder sb = new StringBuilder();
47+
sb.append(System.lineSeparator());
48+
for (String message : messages) {
49+
sb.append(String.format("\t- %s%s", message, System.lineSeparator()));
50+
}
51+
sb.append(System.lineSeparator());
52+
sb.append("[error] Spec is invalid.");
53+
System.err.println(sb.toString());
54+
System.exit(1);
55+
} else {
56+
System.out.println("No validation errors detected.");
5157
}
5258
}
5359
}

modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ValidateException.java

Lines changed: 0 additions & 24 deletions
This file was deleted.

modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfigLoader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static CodegenConfig forName(String name) {
4747
try {
4848
return (CodegenConfig) Class.forName(name).newInstance();
4949
} catch (Exception e) {
50-
throw new RuntimeException("Can't load config class with name '".concat(name) + "'\nAvailable:\n" + availableConfigs.toString());
50+
throw new GeneratorNotFoundException("Can't load config class with name '".concat(name) + "'\nAvailable:\n" + availableConfigs.toString(), e);
5151
}
5252
}
5353

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.openapitools.codegen;
2+
3+
/**
4+
* Typed exception exposing issues with loading generators (e.g. by name).
5+
*/
6+
@SuppressWarnings("unused")
7+
public class GeneratorNotFoundException extends RuntimeException {
8+
/**
9+
* Constructs a new runtime exception with {@code null} as its
10+
* detail message. The cause is not initialized, and may subsequently be
11+
* initialized by a call to {@link #initCause}.
12+
*/
13+
public GeneratorNotFoundException() {
14+
}
15+
16+
/**
17+
* Constructs a new runtime exception with the specified detail message.
18+
* The cause is not initialized, and may subsequently be initialized by a
19+
* call to {@link #initCause}.
20+
*
21+
* @param message the detail message. The detail message is saved for
22+
* later retrieval by the {@link #getMessage()} method.
23+
*/
24+
public GeneratorNotFoundException(String message) {
25+
super(message);
26+
}
27+
28+
/**
29+
* Constructs a new runtime exception with the specified detail message and
30+
* cause. <p>Note that the detail message associated with
31+
* {@code cause} is <i>not</i> automatically incorporated in
32+
* this runtime exception's detail message.
33+
*
34+
* @param message the detail message (which is saved for later retrieval
35+
* by the {@link #getMessage()} method).
36+
* @param cause the cause (which is saved for later retrieval by the
37+
* {@link #getCause()} method). (A <tt>null</tt> value is
38+
* permitted, and indicates that the cause is nonexistent or
39+
* unknown.)
40+
* @since 1.4
41+
*/
42+
public GeneratorNotFoundException(String message, Throwable cause) {
43+
super(message, cause);
44+
}
45+
46+
/**
47+
* Constructs a new runtime exception with the specified cause and a
48+
* detail message of <tt>(cause==null ? null : cause.toString())</tt>
49+
* (which typically contains the class and detail message of
50+
* <tt>cause</tt>). This constructor is useful for runtime exceptions
51+
* that are little more than wrappers for other throwables.
52+
*
53+
* @param cause the cause (which is saved for later retrieval by the
54+
* {@link #getCause()} method). (A <tt>null</tt> value is
55+
* permitted, and indicates that the cause is nonexistent or
56+
* unknown.)
57+
* @since 1.4
58+
*/
59+
public GeneratorNotFoundException(Throwable cause) {
60+
super(cause);
61+
}
62+
63+
/**
64+
* Constructs a new runtime exception with the specified detail
65+
* message, cause, suppression enabled or disabled, and writable
66+
* stack trace enabled or disabled.
67+
*
68+
* @param message the detail message.
69+
* @param cause the cause. (A {@code null} value is permitted,
70+
* and indicates that the cause is nonexistent or unknown.)
71+
* @param enableSuppression whether or not suppression is enabled
72+
* or disabled
73+
* @param writableStackTrace whether or not the stack trace should
74+
* be writable
75+
* @since 1.7
76+
*/
77+
public GeneratorNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
78+
super(message, cause, enableSuppression, writableStackTrace);
79+
}
80+
}

0 commit comments

Comments
 (0)