Skip to content

Commit f7d5737

Browse files
committed
Add example builder annotation tests
1 parent 763f115 commit f7d5737

File tree

10 files changed

+279
-29
lines changed

10 files changed

+279
-29
lines changed

openapi-generator-annotations/lib/src/openapi_generator_annotations_base.dart

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,6 @@ class Openapi {
128128
this.useNextGen = false,
129129
this.projectPubspecPath,
130130
});
131-
// TODO: Enable assertion error
132-
// : assert(cachePath != null && !useNextGen,
133-
// 'useNextGen should be set when providing cachePath');
134131
}
135132

136133
class AdditionalProperties {
@@ -320,7 +317,8 @@ class DioProperties extends AdditionalProperties {
320317
bool sortModelPropertiesByRequiredFlag = true,
321318
bool sortParamsByRequiredFlag = true,
322319
bool useEnumExtension = true,
323-
String? sourceFolder})
320+
String? sourceFolder,
321+
Wrapper wrapper = Wrapper.none})
324322
: super(
325323
allowUnicodeIdentifiers: allowUnicodeIdentifiers,
326324
ensureUniqueParams: ensureUniqueParams,
@@ -335,7 +333,8 @@ class DioProperties extends AdditionalProperties {
335333
sortModelPropertiesByRequiredFlag,
336334
sortParamsByRequiredFlag: sortParamsByRequiredFlag,
337335
sourceFolder: sourceFolder,
338-
useEnumExtension: useEnumExtension);
336+
useEnumExtension: useEnumExtension,
337+
wrapper: wrapper);
339338

340339
DioProperties.fromMap(Map<String, dynamic> map)
341340
: dateLibrary = EnumTransformer.dioDateLibrary(map['dateLibrary']),
@@ -393,7 +392,8 @@ class DioAltProperties extends AdditionalProperties {
393392
bool sortModelPropertiesByRequiredFlag = true,
394393
bool sortParamsByRequiredFlag = true,
395394
bool useEnumExtension = true,
396-
String? sourceFolder})
395+
String? sourceFolder,
396+
Wrapper wrapper = Wrapper.none})
397397
: super(
398398
allowUnicodeIdentifiers: allowUnicodeIdentifiers,
399399
ensureUniqueParams: ensureUniqueParams,
@@ -408,7 +408,8 @@ class DioAltProperties extends AdditionalProperties {
408408
sortModelPropertiesByRequiredFlag,
409409
sortParamsByRequiredFlag: sortParamsByRequiredFlag,
410410
sourceFolder: sourceFolder,
411-
useEnumExtension: useEnumExtension);
411+
useEnumExtension: useEnumExtension,
412+
wrapper: wrapper);
412413

413414
DioAltProperties.fromMap(Map<String, dynamic> map)
414415
: nullSafe = map['nullSafe'] as bool?,

openapi-generator/lib/src/models/generator_arguments.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,17 @@ class GeneratorArguments {
238238
if (inlineSchemaOptions != null)
239239
'--inline-schema-options=${inlineSchemaOptions!.toMap().entries.fold('', foldStringMap(keyModifier: convertToPropertyKey))}',
240240
if (additionalProperties != null)
241-
'--additional-properties=${additionalProperties!.toMap().entries.fold('', foldStringMap(keyModifier: convertToPropertyKey))}'
241+
'--additional-properties=${convertAdditionalProperties(additionalProperties!).fold('', foldStringMap(keyModifier: convertToPropertyKey))}'
242242
];
243+
244+
Iterable<MapEntry<String, dynamic>> convertAdditionalProperties(
245+
AdditionalProperties props) {
246+
if (props is DioProperties) {
247+
return props.toMap().entries;
248+
} else if (props is DioAltProperties) {
249+
return props.toMap().entries;
250+
} else {
251+
return props.toMap().entries;
252+
}
253+
}
243254
}

openapi-generator/lib/src/models/output_message.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,9 @@ class OutputMessage {
1616
this.additionalContext,
1717
this.stackTrace,
1818
});
19+
20+
@override
21+
String toString() {
22+
return '$message ${additionalContext ?? ''} ${stackTrace ?? ''}';
23+
}
1924
}

openapi-generator/lib/src/openapi_generator_runner.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ class OpenapiGenerator extends GeneratorForAnnotation<annots.Openapi> {
4343
);
4444
}
4545

46+
if (!(annotations.read('useNextGen').literalValue as bool)) {
47+
if (annotations.read('cachePath').literalValue != null) {
48+
throw InvalidGenerationSourceError(
49+
'useNextGen must be set when using cachePath',
50+
todo:
51+
'Either set useNextGen: true on the annotation or remove the custom cachePath',
52+
);
53+
}
54+
}
55+
4656
// Transform the annotations.
4757
final args = GeneratorArguments(annotations: annotations);
4858

openapi-generator/lib/src/utils.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ String convertToPropertyKey(String key) {
5454

5555
String Function(String, MapEntry<String, dynamic>) foldStringMap({
5656
String Function(String)? keyModifier,
57+
String Function(dynamic)? valueModifier,
5758
}) =>
5859
(String prev, MapEntry<String, dynamic> curr) =>
59-
'${prev.trim().isEmpty ? '' : '$prev,'}${keyModifier != null ? keyModifier(curr.key) : curr.key}=${curr.value}';
60+
'${prev.trim().isEmpty ? '' : '$prev,'}${keyModifier != null ? keyModifier(curr.key) : curr.key}=${valueModifier != null ? valueModifier(curr.value) : curr.value}';

openapi-generator/test/builder_test.dart

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ void main() {
2929
outputDirectory: 'api/petstore_api')
3030
'''),
3131
contains(
32-
'generate -o api/petstore_api -i ../openapi-spec.yaml -g dart-dio --type-mappings=Pet=ExamplePet --additional-properties=allowUnicodeIdentifiers=false,ensureUniqueParams=true,useEnumExtension=true,prependFormOrBodyParameters=false,pubAuthor=Johnny dep...,pubName=petstore_api,legacyDiscriminatorBehavior=true,sortModelPropertiesByRequiredFlag=true,sortParamsByRequiredFlag=true,wrapper=none,dateLibrary=core,serializationLibrary=built_value'));
32+
'generate -o api/petstore_api -i ../openapi-spec.yaml -g dart-dio --type-mappings=Pet=ExamplePet --additional-properties=allowUnicodeIdentifiers=false,ensureUniqueParams=true,useEnumExtension=true,prependFormOrBodyParameters=false,pubAuthor=Johnny dep...,pubName=petstore_api,legacyDiscriminatorBehavior=true,sortModelPropertiesByRequiredFlag=true,sortParamsByRequiredFlag=true,wrapper=none'));
3333
});
3434

3535
test('to generate command with import and type mappings', () async {
@@ -87,7 +87,7 @@ void main() {
8787
outputDirectory: 'api/petstore_api')
8888
'''),
8989
contains('''
90-
generate -o api/petstore_api -i ../openapi-spec.yaml -g dart-dio --type-mappings=Pet=ExamplePet --additional-properties=allowUnicodeIdentifiers=false,ensureUniqueParams=true,useEnumExtension=true,prependFormOrBodyParameters=false,pubAuthor=Johnny dep...,pubName=petstore_api,legacyDiscriminatorBehavior=true,sortModelPropertiesByRequiredFlag=true,sortParamsByRequiredFlag=true,wrapper=none,dateLibrary=core,serializationLibrary=built_value
90+
generate -o api/petstore_api -i ../openapi-spec.yaml -g dart-dio --type-mappings=Pet=ExamplePet --additional-properties=allowUnicodeIdentifiers=false,ensureUniqueParams=true,useEnumExtension=true,prependFormOrBodyParameters=false,pubAuthor=Johnny dep...,pubName=petstore_api,legacyDiscriminatorBehavior=true,sortModelPropertiesByRequiredFlag=true,sortParamsByRequiredFlag=true,wrapper=none
9191
'''
9292
.trim()));
9393
});
@@ -112,23 +112,7 @@ void main() {
112112
final specPath =
113113
'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml';
114114
final f = File('${testSpecPath}managed-cache.json');
115-
setUpAll(() async {
116-
generatedOutput = await generate('''
117-
@Openapi(
118-
inputSpecFile: '$specPath',
119-
typeMappings: {'int-or-string':'IntOrString'},
120-
importMappings: {'IntOrString':'./int_or_string.dart'},
121-
generatorName: Generator.dioAlt,
122-
useNextGen: true,
123-
)
124-
''');
125-
});
126-
test('Logs warning when using remote spec', () async {
127-
expect(
128-
generatedOutput,
129-
contains(
130-
':: Using a remote specification, a cache will still be create but may be outdated. ::'));
131-
});
115+
132116
group('runs', () {
133117
setUpAll(() {
134118
f.writeAsStringSync('{}');
@@ -138,6 +122,35 @@ void main() {
138122
f.deleteSync();
139123
}
140124
});
125+
test('fails with invalid configuration', () async {
126+
generatedOutput = await generate('''
127+
@Openapi(
128+
inputSpecFile: '$specPath',
129+
typeMappings: {'int-or-string':'IntOrString'},
130+
importMappings: {'IntOrString':'./int_or_string.dart'},
131+
generatorName: Generator.dioAlt,
132+
useNextGen: false,
133+
cachePath: './'
134+
)
135+
''');
136+
expect(generatedOutput,
137+
contains('useNextGen must be set when using cachePath'));
138+
});
139+
test('Logs warning when using remote spec', () async {
140+
generatedOutput = await generate('''
141+
@Openapi(
142+
inputSpecFile: '$specPath',
143+
typeMappings: {'int-or-string':'IntOrString'},
144+
importMappings: {'IntOrString':'./int_or_string.dart'},
145+
generatorName: Generator.dioAlt,
146+
useNextGen: true,
147+
)
148+
''');
149+
expect(
150+
generatedOutput,
151+
contains(
152+
':: Using a remote specification, a cache will still be create but may be outdated. ::'));
153+
});
141154
test('when the spec is dirty', () async {
142155
final src = '''
143156
@Openapi(
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
library test_annotations;
2+
3+
import 'package:openapi_generator_annotations/openapi_generator_annotations.dart';
4+
import 'package:source_gen_test/annotations.dart';
5+
6+
@ShouldGenerate(r'''
7+
const alwaysRun = false;
8+
9+
const fetchDependencies = true;
10+
11+
const generatorName = 'dio';
12+
13+
const inputSpecFile = '';
14+
15+
const runSourceGenOnOutput = true;
16+
17+
const skipSpecValidation = false;
18+
19+
const useNextGen = false;
20+
''')
21+
@Openapi(inputSpecFile: '', generatorName: Generator.dio)
22+
class TestClassDefault extends OpenapiGeneratorConfig {}
23+
24+
@ShouldThrow('useNextGen must be set when using cachePath', element: false)
25+
@Openapi(inputSpecFile: '', generatorName: Generator.dio, cachePath: './')
26+
class TestClassInvalidCachePathUsage extends OpenapiGeneratorConfig {}
27+
28+
@ShouldGenerate(r'''
29+
const additionalProperties = wrapper = 'flutterw';
30+
31+
const alwaysRun = false;
32+
33+
const fetchDependencies = true;
34+
35+
const generatorName = 'dart';
36+
37+
const inputSpecFile = '';
38+
39+
const runSourceGenOnOutput = true;
40+
41+
const skipSpecValidation = false;
42+
43+
const useNextGen = false;
44+
''')
45+
@Openapi(
46+
inputSpecFile: '',
47+
generatorName: Generator.dart,
48+
additionalProperties: AdditionalProperties(
49+
wrapper: Wrapper.flutterw,
50+
),
51+
)
52+
class TestClassHasCustomAnnotations extends OpenapiGeneratorConfig {}
53+
54+
@ShouldGenerate(r'''
55+
const additionalProperties = wrapper = 'flutterw', nullableFields = 'true';
56+
57+
const alwaysRun = false;
58+
59+
const fetchDependencies = true;
60+
61+
const generatorName = 'dart';
62+
63+
const inputSpecFile = '';
64+
65+
const runSourceGenOnOutput = true;
66+
67+
const skipSpecValidation = false;
68+
69+
const useNextGen = false;
70+
''')
71+
@Openapi(
72+
inputSpecFile: '',
73+
generatorName: Generator.dart,
74+
additionalProperties: DioProperties(
75+
wrapper: Wrapper.flutterw,
76+
nullableFields: true,
77+
),
78+
)
79+
class TestClassHasDioProperties extends OpenapiGeneratorConfig {}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import 'package:analyzer/dart/element/element.dart';
2+
import 'package:build/src/builder/build_step.dart';
3+
import 'package:openapi_generator/src/utils.dart';
4+
import 'package:openapi_generator_annotations/openapi_generator_annotations.dart';
5+
import 'package:source_gen/source_gen.dart' as src_gen;
6+
7+
class TestGenerator extends src_gen.GeneratorForAnnotation<Openapi> {
8+
final bool requireTestClassPrefix;
9+
10+
const TestGenerator({this.requireTestClassPrefix = true});
11+
12+
@override
13+
Iterable<String> generateForAnnotatedElement(Element element,
14+
src_gen.ConstantReader annotation, BuildStep buildStep) sync* {
15+
assert(!annotation.isNull, 'The source generator should\'nt be null');
16+
17+
if (element is! ClassElement) {
18+
throw src_gen.InvalidGenerationSourceError(
19+
'Only supports annotated classes.',
20+
todo: 'Remove `TestAnnotation` from the associated element.',
21+
element: element,
22+
);
23+
}
24+
25+
if (requireTestClassPrefix && !element.name.startsWith('TestClass')) {
26+
throw src_gen.InvalidGenerationSourceError(
27+
'All classes must start with `TestClass`.',
28+
todo: 'Rename the type or remove the `TestAnnotation` from class.',
29+
element: element,
30+
);
31+
}
32+
33+
if (!(annotation.read('useNextGen').literalValue as bool)) {
34+
if (annotation.read('cachePath').literalValue != null) {
35+
throw src_gen.InvalidGenerationSourceError(
36+
'useNextGen must be set when using cachePath');
37+
}
38+
}
39+
40+
// KEEP THIS IN LINE WITH THE FIELDS OF THE ANNOTATION CLASS
41+
final fields = [
42+
SupportedFields(name: 'additionalProperties', type: AdditionalProperties),
43+
SupportedFields(
44+
name: 'overwriteExistingFiles', isDeprecated: true, type: bool),
45+
SupportedFields(name: 'skipSpecValidation', type: bool),
46+
SupportedFields(name: 'inputSpecFile', isRequired: true, type: String),
47+
SupportedFields(name: 'templateDirectory', type: String),
48+
SupportedFields(name: 'generatorName', isRequired: true, type: Generator),
49+
SupportedFields(name: 'outputDirectory', type: Map),
50+
SupportedFields(name: 'typeMappings', type: Map),
51+
SupportedFields(name: 'importMappings', type: Map),
52+
SupportedFields(name: 'reservedWordsMappings', type: Map),
53+
SupportedFields(name: 'inlineSchemaNameMappings', type: Map),
54+
// SupportedFields(name:'inlineSchemaOptions'),
55+
SupportedFields(name: 'apiPackage', type: String),
56+
SupportedFields(name: 'fetchDependencies', type: bool),
57+
SupportedFields(name: 'runSourceGenOnOutput', type: bool),
58+
SupportedFields(name: 'alwaysRun', isDeprecated: true, type: bool),
59+
SupportedFields(name: 'cachePath', type: String),
60+
SupportedFields(name: 'useNextGen', type: bool),
61+
SupportedFields(name: 'projectPubspecPath', type: String),
62+
]..sort((a, b) => a.name.compareTo(b.name));
63+
for (final field in fields) {
64+
final v = annotation.read(field.name);
65+
try {
66+
if ([
67+
'inputSpecFile',
68+
'projectPubspecPath',
69+
'apiPackage',
70+
'templateDirectory',
71+
'generatorName'
72+
].any((element) => field.name == element)) {
73+
yield 'const ${field.name}=\'${convertToPropertyValue(v.objectValue)}\';\n';
74+
} else if (field.name == 'additionalProperties') {
75+
final mapping = v.revive().namedArguments.map(
76+
(key, value) => MapEntry(key, convertToPropertyValue(value)));
77+
// TODO: Is this the expected behaviour?
78+
// Iterable<MapEntry<String, dynamic>> entries;
79+
// if (v.objectValue.type is DioProperties) {
80+
// entries = DioProperties.fromMap(mapping).toMap().entries;
81+
// } else if (v.objectValue.type is DioAltProperties) {
82+
// entries = DioAltProperties.fromMap(mapping).toMap().entries;
83+
// } else {
84+
// entries = AdditionalProperties.fromMap(mapping).toMap().entries;
85+
// }
86+
yield 'const ${field.name}=${mapping.entries.fold('', foldStringMap(valueModifier: (value) => '\'$value\''))};';
87+
} else {
88+
yield 'const ${field.name}=${convertToPropertyValue(v.objectValue)};\n';
89+
}
90+
} catch (_, __) {
91+
continue;
92+
}
93+
}
94+
}
95+
96+
@override
97+
String toString() =>
98+
'TestGenerator (requireTestClassPrefix:$requireTestClassPrefix)';
99+
}
100+
101+
class SupportedFields<T> {
102+
final String name;
103+
final bool isRequired;
104+
final bool isDeprecated;
105+
final T? type;
106+
107+
const SupportedFields({
108+
required this.name,
109+
this.isDeprecated = false,
110+
this.isRequired = false,
111+
required this.type,
112+
});
113+
}

openapi-generator/test/utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Future<String> generate(String source) async {
4343
'${error ?? ''}\n${logRecord.level} ${logRecord.message} \n ${logRecord.additionalContext} \n ${logRecord.stackTrace}';
4444
} else {
4545
error =
46-
'${error ?? ''}\n${logRecord.message}\n${logRecord.error}\n${logRecord.stackTrace}';
46+
'${error ?? ''}\n${logRecord.message ?? ''}\n${logRecord.error ?? ''}\n${logRecord.stackTrace ?? ''}';
4747
}
4848
}
4949

0 commit comments

Comments
 (0)