Skip to content

Commit d5555dd

Browse files
committed
feat: added fields skipIfSpecUnchanged and forceAlwaysRun
BREAKING CHANGE: `forceAlwaysRun` is used to represent if the library should run everytime `build_runner` is executed. This is done by modifying the annotated file after `build_runner` completes. Previous versions of this library did not have this flag but they had the equivalent of `true`. However, this now defaults to `false`. To keep previous behavior, set this flag to `true`.
1 parent 7f8770b commit d5555dd

33 files changed

+927
-564
lines changed

.github/workflows/code_quality.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ jobs:
4444
- name: Install Dependencies
4545
run: melos exec -- "dart pub get"
4646

47-
# - name: Build runner
48-
# run: melos exec --depends-on="build_runner" -- bash -c "cd \$MELOS_PACKAGE_PATH && dart run build_runner build --delete-conflicting-outputs"
47+
- name: Build runner
48+
run: melos exec --depends-on="build_runner" -- bash -c "cd \$MELOS_PACKAGE_PATH && dart run build_runner build --delete-conflicting-outputs"
49+
4950
- name: Validate formatting
5051
run: melos format --set-exit-if-changed
5152
- name: Run analyzer
@@ -68,7 +69,7 @@ jobs:
6869
run: dart pub global activate combine_coverage
6970

7071
- name: format coverage
71-
run: melos exec -- bash "$HOME/.pub-cache/bin/format_coverage --lcov --in=\$MELOS_PACKAGE_PATH/coverage/test --out=\$MELOS_PACKAGE_PATH/coverage/lcov.info --report-on=lib"
72+
run: melos exec -- bash "$HOME/.pub-cache/bin/format_coverage --lcov --in=\$MELOS_PACKAGE_PATH/coverage/test --out=\$MELOS_PACKAGE_PATH/coverage/lcov.info --report-on=lib --report-on=bin"
7273

7374
- name: Combine Coverage Reports
7475
run: dart pub global run combine_coverage --repo-path="."

example/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ void main() {
1212
RemoteSpec(path: 'https://petstore3.swagger.io/api/v3/openapi.json'),
1313
typeMappings: {'Pet': 'ExamplePet'},
1414
generatorName: Generator.dioAlt,
15-
updateAnnotatedFile: false,
15+
forceAlwaysRun: false,
1616
runSourceGenOnOutput: true,
1717
outputDirectory: 'api/petstore_api',
1818
)

melos.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ scripts:
2525
run: |
2626
melos exec -c 6 --fail-fast -- \
2727
"dart run test --chain-stack-traces --coverage='coverage'"
28-
description: Run `flutter test` for a specific package.
28+
description: Run `dart test`
2929
packageFilters:
3030
dirExists:
3131
- test

melos_openapi_generator_dart.iml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<module type="WEB_MODULE" version="4">
3+
<component name="NewModuleRootManager" inherit-compiler-output="true">
4+
<exclude-output />
5+
<content url="file://$MODULE_DIR$">
6+
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
7+
<excludeFolder url="file://$MODULE_DIR$/example/.dart_tool" />
8+
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
9+
<excludeFolder url="file://$MODULE_DIR$/example/build" />
10+
</content>
11+
<orderEntry type="sourceFolder" forTests="false" />
12+
<orderEntry type="library" name="Dart SDK" level="project" />
13+
<orderEntry type="library" name="Dart Packages" level="project" />
14+
</component>
15+
</module>

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

Lines changed: 213 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,34 @@ class Openapi {
125125
/// Include in depth logging output from run commands.
126126
final bool debugLogging;
127127

128-
/// If set to true, the annotated file will be added or updated comment lines as of the last run date on the top of the file.
129-
/// Defaults to true
130-
final bool updateAnnotatedFile;
128+
/// If `true`, the annotated file will be modified after code generation completes.
129+
///
130+
/// This is a workaround to ensure this library runs every time you execute the `build_runner` command.
131+
///
132+
/// **Why modify the file?**
133+
///
134+
/// `build_runner` only processes files that have changed since the last run. By modifying the file, you
135+
/// force `build_runner` to recognize it as changed and re-run the generation process.
136+
///
137+
/// Note: Setting this to `true` can lead to merge conflicts in team environments,
138+
/// as each developer may end up modifying the annotated file.
139+
///
140+
/// This setting is different from [skipIfSpecIsUnchanged], which only regenerates
141+
/// the client SDK if it detects changes in the OpenAPI specification.
142+
///
143+
/// Defaults to [false].
144+
final bool forceAlwaysRun;
131145

132-
/// Whether to disable caching the spec file. Defaults to `true` if the
133-
/// [inputSpec] is not a [RemoteSpec].
134-
final bool disableCache;
146+
/// Skips execution if the OpenAPI specification file is older than the output folder.
147+
///
148+
/// For remote specifications, the file will be downloaded and cached locally.
149+
/// The cache is then compared to the remote file to detect any changes.
150+
///
151+
/// **Default behavior:**
152+
/// - If [inputSpec] is a [RemoteSpec], this is set to `true`, meaning execution will be skipped if no changes are detected.
153+
/// - For all other cases, this is set to `false`.
154+
155+
final bool skipIfSpecIsUnchanged;
135156

136157
const Openapi({
137158
this.additionalProperties,
@@ -153,9 +174,79 @@ class Openapi {
153174
this.cachePath,
154175
this.projectPubspecPath,
155176
this.debugLogging = false,
156-
this.updateAnnotatedFile = true,
157-
bool? disableCache,
158-
}) : disableCache = disableCache ?? inputSpec is! RemoteSpec;
177+
this.forceAlwaysRun = true,
178+
bool? skipIfSpecIsUnchanged,
179+
}) : skipIfSpecIsUnchanged = skipIfSpecIsUnchanged ?? inputSpec is RemoteSpec;
180+
181+
@override
182+
String toString() {
183+
final buffer = StringBuffer();
184+
buffer.writeln('@Openapi(');
185+
if (additionalProperties != null) {
186+
buffer.writeln(' additionalProperties: $additionalProperties,');
187+
}
188+
if (apiPackage != null) {
189+
buffer.writeln(' apiPackage: "$apiPackage",');
190+
}
191+
buffer.writeln(' inputSpec: $inputSpec,');
192+
if (templateDirectory != null) {
193+
buffer.writeln(' templateDirectory: "$templateDirectory",');
194+
}
195+
buffer.writeln(' generatorName: $generatorName,');
196+
if (outputDirectory != null) {
197+
buffer.writeln(' outputDirectory: "$outputDirectory",');
198+
}
199+
if (cleanSubOutputDirectory != null) {
200+
buffer.writeln(
201+
' cleanSubOutputDirectory: [${cleanSubOutputDirectory!.join(", ")}],');
202+
}
203+
if (skipSpecValidation != null) {
204+
buffer.writeln(' skipSpecValidation: $skipSpecValidation,');
205+
}
206+
if (reservedWordsMappings != null) {
207+
buffer.writeln(
208+
' reservedWordsMappings: ${reservedWordsMappings.toString()},');
209+
}
210+
if (fetchDependencies != null) {
211+
buffer.writeln(' fetchDependencies: $fetchDependencies,');
212+
}
213+
if (runSourceGenOnOutput != null) {
214+
buffer.writeln(' runSourceGenOnOutput: $runSourceGenOnOutput,');
215+
}
216+
if (typeMappings != null) {
217+
buffer.writeln(' typeMappings: ${_formatMap(typeMappings!)},');
218+
}
219+
if (nameMappings != null) {
220+
buffer.writeln(' nameMappings: ${_formatMap(nameMappings!)},');
221+
}
222+
if (importMappings != null) {
223+
buffer.writeln(' importMappings: ${_formatMap(importMappings!)},');
224+
}
225+
if (inlineSchemaNameMappings != null) {
226+
buffer.writeln(
227+
' inlineSchemaNameMappings: ${_formatMap(inlineSchemaNameMappings!)},');
228+
}
229+
if (cachePath != null) {
230+
buffer.writeln(' cachePath: "$cachePath",');
231+
}
232+
if (projectPubspecPath != null) {
233+
buffer.writeln(' projectPubspecPath: "$projectPubspecPath",');
234+
}
235+
buffer.writeln(' debugLogging: $debugLogging,');
236+
buffer.writeln(' forceAlwaysRun: $forceAlwaysRun,');
237+
buffer.writeln(' skipIfSpecIsUnchanged: $skipIfSpecIsUnchanged,');
238+
buffer.write(')');
239+
return buffer.toString();
240+
}
241+
}
242+
243+
String _formatMap(Map<String, String> map) {
244+
final buffer = StringBuffer();
245+
buffer.write('{');
246+
buffer.writeAll(
247+
map.entries.map((entry) => "'${entry.key}':'${entry.value}'"), ', ');
248+
buffer.write('}');
249+
return buffer.toString();
159250
}
160251

161252
/// Provides the input spec file to be used.
@@ -173,6 +264,15 @@ class InputSpec {
173264
: this(path: 'openapi.y${shortExtension ? '' : 'a'}ml');
174265

175266
InputSpec.fromMap(Map<String, dynamic> map) : this(path: map['path']);
267+
268+
@override
269+
String toString() {
270+
final buffer = StringBuffer();
271+
buffer.writeln('InputSpec(');
272+
buffer.writeln(' path: "$path"');
273+
buffer.write(')');
274+
return buffer.toString();
275+
}
176276
}
177277

178278
/// Provides the location for the remote specification.
@@ -201,6 +301,16 @@ class RemoteSpec extends InputSpec {
201301
: headerDelegate =
202302
map['headerDelegate'] ?? const RemoteSpecHeaderDelegate(),
203303
super.fromMap(map);
304+
305+
@override
306+
String toString() {
307+
final buffer = StringBuffer();
308+
buffer.writeln('RemoteSpec(');
309+
buffer.writeln(' path: "$path",');
310+
buffer.writeln(' headerDelegate: $headerDelegate');
311+
buffer.write(')');
312+
return buffer.toString();
313+
}
204314
}
205315

206316
/// Default [RemoteSpecHeaderDelegate] used when retrieving a remote OAS spec.
@@ -210,6 +320,9 @@ class RemoteSpecHeaderDelegate {
210320
Map<String, String>? header() => null;
211321

212322
RemoteSpecHeaderDelegate.fromMap(Map<String, dynamic> map) : this();
323+
324+
@override
325+
String toString() => 'RemoteSpecHeaderDelegate()';
213326
}
214327

215328
/// Indicates whether or not the spec file live within AWS.
@@ -434,6 +547,42 @@ class AdditionalProperties {
434547
if (sourceFolder != null) 'sourceFolder': sourceFolder,
435548
'wrapper': EnumTransformer.wrapperName(wrapper)
436549
};
550+
551+
@override
552+
String toString() {
553+
final buffer = StringBuffer();
554+
buffer.writeln('AdditionalProperties(');
555+
if (allowUnicodeIdentifiers != null)
556+
buffer.writeln(' allowUnicodeIdentifiers: $allowUnicodeIdentifiers,');
557+
if (ensureUniqueParams != null)
558+
buffer.writeln(' ensureUniqueParams: $ensureUniqueParams,');
559+
if (prependFormOrBodyParameters != null)
560+
buffer.writeln(
561+
' prependFormOrBodyParameters: $prependFormOrBodyParameters,');
562+
if (pubAuthor != null) buffer.writeln(' pubAuthor: "$pubAuthor",');
563+
if (pubAuthorEmail != null)
564+
buffer.writeln(' pubAuthorEmail: "$pubAuthorEmail",');
565+
if (pubDescription != null)
566+
buffer.writeln(' pubDescription: "$pubDescription",');
567+
if (pubHomepage != null) buffer.writeln(' pubHomepage: "$pubHomepage",');
568+
if (pubName != null) buffer.writeln(' pubName: "$pubName",');
569+
if (pubVersion != null) buffer.writeln(' pubVersion: "$pubVersion",');
570+
if (sortModelPropertiesByRequiredFlag != null)
571+
buffer.writeln(
572+
' sortModelPropertiesByRequiredFlag: $sortModelPropertiesByRequiredFlag,');
573+
if (sortParamsByRequiredFlag != null)
574+
buffer.writeln(' sortParamsByRequiredFlag: $sortParamsByRequiredFlag,');
575+
if (sourceFolder != null)
576+
buffer.writeln(' sourceFolder: "$sourceFolder",');
577+
if (useEnumExtension != null)
578+
buffer.writeln(' useEnumExtension: $useEnumExtension,');
579+
buffer.writeln(' enumUnknownDefaultCase: $enumUnknownDefaultCase,');
580+
buffer.writeln(' wrapper: $wrapper,');
581+
buffer
582+
.writeln(' legacyDiscriminatorBehavior: $legacyDiscriminatorBehavior');
583+
buffer.write(')');
584+
return buffer.toString();
585+
}
437586
}
438587

439588
/// Allows you to customize how inline schemas are handled or named
@@ -479,6 +628,22 @@ class InlineSchemaOptions {
479628
'refactorAllofInlineSchemas': refactorAllofInlineSchemas,
480629
'resolveInlineEnums': resolveInlineEnums,
481630
};
631+
632+
@override
633+
String toString() {
634+
final buffer = StringBuffer();
635+
buffer.writeln('InlineSchemaOptions(');
636+
if (arrayItemSuffix != null)
637+
buffer.writeln(' arrayItemSuffix: "$arrayItemSuffix",');
638+
if (mapItemSuffix != null)
639+
buffer.writeln(' mapItemSuffix: "$mapItemSuffix",');
640+
buffer.writeln(' skipSchemaReuse: $skipSchemaReuse,');
641+
buffer
642+
.writeln(' refactorAllofInlineSchemas: $refactorAllofInlineSchemas,');
643+
buffer.writeln(' resolveInlineEnums: $resolveInlineEnums');
644+
buffer.write(')');
645+
return buffer.toString();
646+
}
482647
}
483648

484649
class DioProperties extends AdditionalProperties {
@@ -542,6 +707,23 @@ class DioProperties extends AdditionalProperties {
542707
'serializationLibrary':
543708
EnumTransformer.dioSerializationLibraryName(serializationLibrary!),
544709
});
710+
711+
@override
712+
String toString() {
713+
final buffer = StringBuffer();
714+
buffer.writeln('DioProperties(');
715+
buffer.writeln(super
716+
.toString()
717+
.replaceAll(RegExp(r'AdditionalProperties\(|\)$'), '')
718+
.replaceAll('\n', '\n ')); // Indent base class fields
719+
if (dateLibrary != null) buffer.writeln(' dateLibrary: $dateLibrary,');
720+
if (nullableFields != null)
721+
buffer.writeln(' nullableFields: $nullableFields,');
722+
if (serializationLibrary != null)
723+
buffer.writeln(' serializationLibrary: $serializationLibrary,');
724+
buffer.write(')');
725+
return buffer.toString();
726+
}
545727
}
546728

547729
class DioAltProperties extends AdditionalProperties {
@@ -613,6 +795,28 @@ class DioAltProperties extends AdditionalProperties {
613795
if (pubspecDevDependencies != null)
614796
'pubspecDevDependencies': pubspecDevDependencies,
615797
});
798+
799+
@override
800+
String toString() {
801+
final buffer = StringBuffer();
802+
buffer.writeln('DioAltProperties(');
803+
804+
// Indent the fields from AdditionalProperties
805+
buffer.writeln(super
806+
.toString()
807+
.replaceAll(RegExp(r'AdditionalProperties\(|\)$'), '')
808+
.replaceAll('\n', '\n '));
809+
810+
// Add DioAltProperties-specific fields
811+
if (listAnyOf != null) buffer.writeln(' listAnyOf: $listAnyOf,');
812+
if (pubspecDependencies != null)
813+
buffer.writeln(' pubspecDependencies: "$pubspecDependencies",');
814+
if (pubspecDevDependencies != null)
815+
buffer.writeln(' pubspecDevDependencies: "$pubspecDevDependencies",');
816+
817+
buffer.write(')');
818+
return buffer.toString();
819+
}
616820
}
617821

618822
enum DioDateLibrary {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<module type="WEB_MODULE" version="4">
3+
<component name="NewModuleRootManager" inherit-compiler-output="true">
4+
<exclude-output />
5+
<content url="file://$MODULE_DIR$">
6+
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
7+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
8+
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
9+
<excludeFolder url="file://$MODULE_DIR$/.pub" />
10+
<excludeFolder url="file://$MODULE_DIR$/build" />
11+
</content>
12+
<orderEntry type="sourceFolder" forTests="false" />
13+
<orderEntry type="library" name="Dart SDK" level="project" />
14+
<orderEntry type="library" name="Dart Packages" level="project" />
15+
</component>
16+
</module>

openapi-generator-annotations/test/openapi_generator_annotations_test.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ void main() {
5252
debugLogging: true);
5353
expect(api.debugLogging, isTrue);
5454
});
55-
test('Sets updateAnnotatedFile', () {
55+
test('Sets forceAlwaysRun', () {
5656
final api = Openapi(
5757
inputSpec: InputSpec.json(),
5858
generatorName: Generator.dart,
59-
updateAnnotatedFile: false);
60-
expect(api.updateAnnotatedFile, isFalse);
59+
forceAlwaysRun: false);
60+
expect(api.forceAlwaysRun, isFalse);
6161
});
6262
group('InputSpec', () {
6363
group('local spec', () {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<module type="WEB_MODULE" version="4">
3+
<component name="NewModuleRootManager" inherit-compiler-output="true">
4+
<exclude-output />
5+
<content url="file://$MODULE_DIR$">
6+
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
7+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
8+
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
9+
<excludeFolder url="file://$MODULE_DIR$/.pub" />
10+
<excludeFolder url="file://$MODULE_DIR$/build" />
11+
<excludeFolder url="file://$MODULE_DIR$/example/.dart_tool" />
12+
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
13+
<excludeFolder url="file://$MODULE_DIR$/example/build" />
14+
</content>
15+
<orderEntry type="sourceFolder" forTests="false" />
16+
<orderEntry type="library" name="Dart SDK" level="project" />
17+
<orderEntry type="library" name="Dart Packages" level="project" />
18+
</component>
19+
</module>

0 commit comments

Comments
 (0)