diff --git a/melos_openapi_generator_dart.iml b/melos_openapi_generator_dart.iml index a84ffe6..ef116cd 100644 --- a/melos_openapi_generator_dart.iml +++ b/melos_openapi_generator_dart.iml @@ -10,9 +10,12 @@ + + + - + \ No newline at end of file diff --git a/openapi-generator-cli/bin/main.dart b/openapi-generator-cli/bin/main.dart index e6deb32..4162f15 100644 --- a/openapi-generator-cli/bin/main.dart +++ b/openapi-generator-cli/bin/main.dart @@ -3,31 +3,12 @@ import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; +import 'package:openapi_generator_cli/src/models.dart'; import 'package:path/path.dart' as p; const baseDownloadUrl = 'https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli'; -/// Default configuration values -class ConfigDefaults { - static const openapiGeneratorVersion = '7.9.0'; - static const additionalCommands = ''; - static const downloadUrlOverride = null; - static const jarCacheDir = '.dart_tool/openapi_generator_cache'; - static const customGeneratorUrls = [ - 'https://repo1.maven.org/maven2/com/bluetrainsoftware/maven/openapi-dart-generator/7.2/openapi-dart-generator-7.2.jar' - ]; -} - -/// Configuration keys as static constants -class ConfigKeys { - static const openapiGeneratorVersion = 'openapiGeneratorVersion'; - static const additionalCommands = 'additionalCommands'; - static const downloadUrlOverride = 'downloadUrlOverride'; - static const jarCachePath = 'jarCacheDir'; - static const customGeneratorUrls = 'customGeneratorUrls'; -} - /// Resolves a given path to an absolute path, handling both relative and absolute inputs String resolvePath(String path) { return p.isAbsolute(path) ? path : p.absolute(Directory.current.path, path); diff --git a/openapi-generator-cli/lib/src/generate_command.dart b/openapi-generator-cli/lib/src/generate_command.dart new file mode 100644 index 0000000..1206a50 --- /dev/null +++ b/openapi-generator-cli/lib/src/generate_command.dart @@ -0,0 +1,41 @@ +import 'package:args/command_runner.dart'; +import 'package:openapi_generator_cli/src/models.dart'; + +class GenerateCommand extends Command { + // The [name] and [description] properties must be defined by every + // subclass. + final name = "generate"; + final description = "Record changes to the repository."; + + CommitCommand() { + // Add options based on ConfigDefaults and ConfigKeys + argParser.addOption(ConfigKeys.openapiGeneratorVersion, + help: 'The version of the OpenAPI generator to use.', + defaultsTo: ConfigDefaults.openapiGeneratorVersion); + + argParser.addOption(ConfigKeys.additionalCommands, + help: + 'Additional commands to pass to the generator. This command will be appended at the end of the commands pas', + defaultsTo: ConfigDefaults.additionalCommands); + + argParser.addOption(ConfigKeys.downloadUrlOverride, + help: 'A custom URL to override the default download location.', + defaultsTo: ConfigDefaults.downloadUrlOverride); + + argParser.addOption(ConfigKeys.jarCachePath, + help: 'The directory where the JAR cache will be stored.', + defaultsTo: ConfigDefaults.jarCacheDir); + + argParser.addMultiOption(ConfigKeys.customGeneratorUrls, + help: + 'Urls for the jars of additional OpenAPI generators to combine with the official one.', + defaultsTo: ConfigDefaults.customGeneratorUrls); + } + + // [run] may also return a Future. + void run() { + // [argResults] is set before [run()] is called and contains the flags/options + // passed to this command. + print(argResults?.option('all')); + } +} diff --git a/openapi-generator-cli/lib/src/models.dart b/openapi-generator-cli/lib/src/models.dart new file mode 100644 index 0000000..daca4f8 --- /dev/null +++ b/openapi-generator-cli/lib/src/models.dart @@ -0,0 +1,19 @@ +/// Default configuration values +class ConfigDefaults { + static const openapiGeneratorVersion = '7.9.0'; + static const additionalCommands = ''; + static const downloadUrlOverride = null; + static const jarCacheDir = '.dart_tool/openapi_generator_cache'; + static const customGeneratorUrls = [ + 'https://repo1.maven.org/maven2/com/bluetrainsoftware/maven/openapi-dart-generator/7.2/openapi-dart-generator-7.2.jar' + ]; +} + +/// Configuration keys as static constants +class ConfigKeys { + static const openapiGeneratorVersion = 'openapiGeneratorVersion'; + static const additionalCommands = 'additionalCommands'; + static const downloadUrlOverride = 'downloadUrlOverride'; + static const jarCachePath = 'jarCacheDir'; + static const customGeneratorUrls = 'customGeneratorUrls'; +} diff --git a/openapi-generator-cli/pubspec.yaml b/openapi-generator-cli/pubspec.yaml index 96600b0..a2898df 100644 --- a/openapi-generator-cli/pubspec.yaml +++ b/openapi-generator-cli/pubspec.yaml @@ -9,6 +9,8 @@ environment: dependencies: http: '>=1.2.2 <=2.0.0' path: '>=1.9.0 <=2.0.0' + args: '>=2.6.0 <3.0.0' + cli_launcher: ^0.3.1 dev_dependencies: pedantic: diff --git a/openapi-generator-cli/test/openapi_generator_cli_test.dart b/openapi-generator-cli/test/openapi_generator_cli_test.dart index f71f878..a3c7142 100644 --- a/openapi-generator-cli/test/openapi_generator_cli_test.dart +++ b/openapi-generator-cli/test/openapi_generator_cli_test.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; +import 'package:openapi_generator_cli/src/models.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; diff --git a/openapi-generator/lib/src/openapi_generator_runner.dart b/openapi-generator/lib/src/openapi_generator_runner.dart index e8480e7..a310fbb 100755 --- a/openapi-generator/lib/src/openapi_generator_runner.dart +++ b/openapi-generator/lib/src/openapi_generator_runner.dart @@ -12,6 +12,7 @@ import 'package:openapi_generator/src/process_runner.dart'; import 'package:openapi_generator/src/utils.dart'; import 'package:openapi_generator_annotations/openapi_generator_annotations.dart' as annots; +import 'package:path/path.dart' as path; import 'package:source_gen/source_gen.dart'; import 'models/command.dart'; @@ -131,7 +132,21 @@ class OpenapiGenerator extends GeneratorForAnnotation { workingDirectory: Directory.current.path, runInShell: Platform.isWindows, ); - + var outputDir = path.isRelative(arguments.outputDirectory!) + ? path.normalize( + path.join(Directory.current.path, arguments.outputDirectory!)) + : path.normalize(arguments.outputDirectory!); + var outputFolderExists = Directory(outputDir).existsSync(); + if (!outputFolderExists) { + logOutputMessage( + log: log, + communication: OutputMessage( + message: [ + '\n::::::::::::::::::::::::\n:: Seems the code may not have been generated. If you do not find more info in the logs, set debugLogging to true on your annotation and try again.\n::::::::::::::::::::::::', + ].join('\n'), + ), + ); + } if (result.exitCode != 0) { return Future.error( OutputMessage( diff --git a/openapi-generator/melos_openapi_generator.iml b/openapi-generator/melos_openapi_generator.iml index b855938..3b42791 100644 --- a/openapi-generator/melos_openapi_generator.iml +++ b/openapi-generator/melos_openapi_generator.iml @@ -23,6 +23,9 @@ + + + diff --git a/openapi-generator/test/github_issues_test.dart b/openapi-generator/test/github_issues_test.dart index 4944bde..6e6e1cb 100644 --- a/openapi-generator/test/github_issues_test.dart +++ b/openapi-generator/test/github_issues_test.dart @@ -10,9 +10,11 @@ import 'utils.dart'; /// We test the build runner by mocking the specs and then checking the output /// content for the expected generate command. +/// +/// we do not use mock process runner for github issues because we want to test +/// that generated code compiles. +/// If you do not want to generate the actual code, then you can initialise [MockProcessRunner] in the test void main() { - // we do not use mock process runner for github issues because we want to test - // that generated code compiles var processRunner = ProcessRunner(); group('Github Issues', () { // setUpAll(() { @@ -58,11 +60,8 @@ void main() { process: processRunner, ); - expect(generatedOutput, - contains('Skipping source gen because generator does not need it.'), - reason: generatedOutput); - expect(generatedOutput, contains('Successfully formatted code.'), - reason: generatedOutput); + expectSourceGenSkipped(generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); var analyzeResult = await Process.run( 'dart', ['analyze'], @@ -101,11 +100,9 @@ void main() { annotatedFileContent.replaceAll('{{issueNumber}}', issueNumber), ); - expect(generatedOutput, - contains('Skipping source gen because generator does not need it.'), - reason: generatedOutput); - expect(generatedOutput, contains('Successfully formatted code.'), - reason: generatedOutput); + expectSourceGenSkipped(generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); + var analyzeResult = await Process.run( 'dart', ['analyze', '--no-fatal-warnings'], @@ -133,13 +130,8 @@ void main() { annotatedFileContent.replaceAll('{{issueNumber}}', issueNumber), ); - expect( - generatedOutput, - contains( - 'pub run build_runner build --delete-conflicting-outputs'), - reason: generatedOutput); - expect(generatedOutput, contains('Successfully formatted code.'), - reason: generatedOutput); + expectSourceGenRun(generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); var workingDirectory = path.join(parentFolder, 'output'); var analyzeResult = await Process.run( 'dart', @@ -180,11 +172,8 @@ void main() { annotatedFileContent.replaceAll('{{issueNumber}}', issueNumber), ); - expect(generatedOutput, - contains('Skipping source gen because generator does not need it.'), - reason: generatedOutput); - expect(generatedOutput, contains('Successfully formatted code.'), - reason: generatedOutput); + expectSourceGenSkipped(generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); var analyzeResult = await Process.run( 'dart', ['analyze', '--fatal-warnings'], @@ -212,13 +201,8 @@ void main() { annotatedFileContent.replaceAll('{{issueNumber}}', issueNumber), ); - expect( - generatedOutput, - contains( - 'pub run build_runner build --delete-conflicting-outputs'), - reason: generatedOutput); - expect(generatedOutput, contains('Successfully formatted code.'), - reason: generatedOutput); + expectSourceGenRun(generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); var workingDirectory = path.join(parentFolder, 'output'); await Process.run( 'dart', @@ -257,11 +241,8 @@ void main() { var generatedOutput = await generateFromPath(annotatedFile.path, process: processRunner, openapiSpecFilePath: inputSpecFile.path); - expect(generatedOutput, - contains('Skipping source gen because generator does not need it.'), - reason: generatedOutput); - expect(generatedOutput, contains('Successfully formatted code.'), - reason: generatedOutput); + expectSourceGenSkipped(generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); var analyzeResult = await Process.run( 'dart', ['analyze', '--no-fatal-warnings'], @@ -282,8 +263,7 @@ void main() { var generatedOutput = await generateFromPath(annotatedFile.path, process: processRunner, openapiSpecFilePath: inputSpecFile.path); - expect(generatedOutput, contains('Successfully formatted code.'), - reason: generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); var workingDirectory = path.join(parentFolder, 'output'); var analyzeResult = await Process.run( 'dart', @@ -333,5 +313,90 @@ void main() { cleanup(workingDirectory); }, skip: true); }); + + group('#164', () { + var issueNumber = '164'; + var parentFolder = path.join(testSpecPath, 'issue', issueNumber); + var workingDirectory = path.join(parentFolder, 'output'); + setUpAll( + () { + var workingDirectory = path.join(parentFolder, 'output'); + cleanup(workingDirectory); + }, + ); + test('[dio] Test that generation does not fail', () async { + var generatedOutput = await generateFromAnnotation( + Openapi( + additionalProperties: DioProperties( + pubName: 'petstore_api', pubAuthor: 'Johnny_dep'), + inputSpec: RemoteSpec( + path: 'https://petstore3.swagger.io/api/v3/openapi.json'), + typeMappings: {'Pet': 'ExamplePet'}, + generatorName: Generator.dio, + runSourceGenOnOutput: true, + skipIfSpecIsUnchanged: false, + cleanSubOutputDirectory: [ + './test/specs/issue/$issueNumber/output' + ], + outputDirectory: './test/specs/issue/$issueNumber/output'), + process: processRunner, + ); + + expectSourceGenRun(generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); + var analyzeResult = await Process.run( + 'dart', + ['analyze', '--no-fatal-warnings'], + workingDirectory: workingDirectory, + ); + expect(analyzeResult.exitCode, 0, + reason: '${analyzeResult.stdout}\n\n${analyzeResult.stderr}'); + cleanup(workingDirectory); + }); + }); + + group('#167', () { + var issueNumber = '167'; + var parentFolder = path.join(testSpecPath, 'issue', issueNumber); + var workingDirectory = path.join(parentFolder, 'output'); + setUpAll( + () { + var workingDirectory = path.join(parentFolder, 'output'); + cleanup(workingDirectory); + }, + ); + test('[dio] Test that generation does not fail', () async { + var generatedOutput = await generateFromAnnotation( + Openapi( + additionalProperties: DioAltProperties( + pubName: 'issue_api', + ), + inputSpec: InputSpec( + path: + './test/specs/issue/$issueNumber/github_issue_#167.yaml'), + generatorName: Generator.dio, + runSourceGenOnOutput: true, + typeMappings: {'Pet': 'ExamplePet', 'Test': 'ExampleTest'}, + skipIfSpecIsUnchanged: false, + cleanSubOutputDirectory: [ + './test/specs/issue/$issueNumber/output' + ], + outputDirectory: './test/specs/issue/$issueNumber/output'), + process: processRunner, + ); + + expectSourceGenRun(generatedOutput); + expectCodeFormattedSuccessfully(generatedOutput); + var analyzeResult = await Process.run( + 'dart', + ['analyze', '--no-fatal-warnings'], + workingDirectory: workingDirectory, + ); + expect(analyzeResult.exitCode, 0, + reason: '${analyzeResult.stdout}\n\n${analyzeResult.stderr}'); + + cleanup(workingDirectory); + }); + }); }); } diff --git a/openapi-generator/test/specs/issue/167/a.yml b/openapi-generator/test/specs/issue/167/a.yml new file mode 100644 index 0000000..552843f --- /dev/null +++ b/openapi-generator/test/specs/issue/167/a.yml @@ -0,0 +1,4 @@ +type: object +properties: + optionA1: + type: string diff --git a/openapi-generator/test/specs/issue/167/b.yml b/openapi-generator/test/specs/issue/167/b.yml new file mode 100644 index 0000000..9c2e983 --- /dev/null +++ b/openapi-generator/test/specs/issue/167/b.yml @@ -0,0 +1,4 @@ +type: object +properties: + optionB1: + type: string diff --git a/openapi-generator/test/specs/issue/167/github_issue_#167.yaml b/openapi-generator/test/specs/issue/167/github_issue_#167.yaml new file mode 100644 index 0000000..b02d975 --- /dev/null +++ b/openapi-generator/test/specs/issue/167/github_issue_#167.yaml @@ -0,0 +1,15 @@ +openapi: 3.0.3 +info: + version: '1.0' + title: Test +paths: + /test: + get: + operationId: test + responses: + '200': + description: response + content: + application/json: + schema: + $ref: "./test.yml" diff --git a/openapi-generator/test/specs/issue/167/test.yml b/openapi-generator/test/specs/issue/167/test.yml new file mode 100644 index 0000000..cd51db1 --- /dev/null +++ b/openapi-generator/test/specs/issue/167/test.yml @@ -0,0 +1,11 @@ +type: object +oneOf: + - $ref: './a.yml' + - $ref: './b.yml' +discriminator: + propertyName: type +required: + - type +properties: + type: + $ref: './testType.yml' diff --git a/openapi-generator/test/specs/issue/167/testType.yml b/openapi-generator/test/specs/issue/167/testType.yml new file mode 100644 index 0000000..c752ecc --- /dev/null +++ b/openapi-generator/test/specs/issue/167/testType.yml @@ -0,0 +1,4 @@ +type: string +enum: + - a + - b diff --git a/openapi-generator/test/utils.dart b/openapi-generator/test/utils.dart index 17ab961..f971841 100644 --- a/openapi-generator/test/utils.dart +++ b/openapi-generator/test/utils.dart @@ -197,3 +197,21 @@ void cleanup(String path) async { print('Folder does not exist.'); } } + +// Test Expectations +void expectSourceGenSkipped(String generatedOutput) { + return expect(generatedOutput, + contains('Skipping source gen because generator does not need it.'), + reason: generatedOutput); +} + +void expectCodeFormattedSuccessfully(String generatedOutput) { + expect(generatedOutput, contains('Successfully formatted code.'), + reason: generatedOutput); +} + +void expectSourceGenRun(String generatedOutput) { + return expect(generatedOutput, + contains('pub run build_runner build --delete-conflicting-outputs'), + reason: generatedOutput); +}