Skip to content

Commit

Permalink
Merge pull request #1025 from nextcloud/feat/dynamite/config
Browse files Browse the repository at this point in the history
feat(dynamite): introduce dynamite config to set ignore directives
  • Loading branch information
Leptopoda authored Oct 29, 2023
2 parents b42b775 + d74f8df commit 35de0d6
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 133 deletions.
29 changes: 29 additions & 0 deletions packages/dynamite/dynamite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Provides a [Dart Build System](https://github.com/dart-lang/build) builder for generating clients from an [OpenAPI specifications](https://swagger.io/specification/).

The builder generates code if it find files with an `.openapi.json` or `.openapi.yaml` extension in the lib directory.

# Build configuration

You can configure code generation by setting values in the `build.yaml`.

```yaml
targets:
$default:
builders:
dynamite:
options:
# Options configure how source code is generated.
#
# The following are sensible default values that ignores the schemas for the coverage.
analyzer_ignores:
- camel_case_types
- discarded_futures
- public_member_api_docs
- unreachable_switch_case
coverage_ignores:
- 'const .*\._\(\);'
- 'factory .*\.fromJson\(Map<String, dynamic> json\) => _jsonSerializers\.deserializeWith\(serializer, json\)!;'
- 'Map<String, dynamic> toJson\(\) => _jsonSerializers\.serializeWith\(serializer, this\)! as Map<String, dynamic>;'
- 'static BuiltSet<.*> get values => _\$.*Values;'

```
2 changes: 1 addition & 1 deletion packages/dynamite/dynamite/lib/builder.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:build/build.dart';
import 'package:dynamite/dynamite.dart';

Builder openAPIBuilder(final BuilderOptions options) => OpenAPIBuilder();
Builder openAPIBuilder(final BuilderOptions options) => OpenAPIBuilder(options);
58 changes: 32 additions & 26 deletions packages/dynamite/dynamite/lib/src/builder/imports.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,35 @@ import 'package:code_builder/code_builder.dart';
import 'package:dynamite/src/builder/state.dart';
import 'package:path/path.dart' as p;

List<Spec> generateImports(final AssetId outputId, final State state) => [
const Code('// ignore_for_file: camel_case_types'),
const Code('// ignore_for_file: discarded_futures'),
const Code('// ignore_for_file: public_member_api_docs'),
const Code('// ignore_for_file: unreachable_switch_case'),
Directive.import('dart:convert'),
Directive.import('dart:typed_data'),
const Code(''),
Directive.import('package:built_collection/built_collection.dart'),
Directive.import('package:built_value/built_value.dart'),
Directive.import('package:built_value/json_object.dart'),
Directive.import('package:built_value/serializer.dart'),
Directive.import('package:built_value/standard_json_plugin.dart'),
Directive.import('package:collection/collection.dart'),
Directive.import('package:dynamite_runtime/built_value.dart'),
Directive.import('package:dynamite_runtime/http_client.dart'),
Directive.import('package:dynamite_runtime/models.dart'),
Directive.import('package:dynamite_runtime/utils.dart'),
Directive.import('package:meta/meta.dart'),
Directive.import('package:universal_io/io.dart'),
const Code(''),
if (state.hasResolvedBuiltTypes) ...[
Directive.part(p.basename(outputId.changeExtension('.g.dart').path)),
const Code(''),
],
];
Iterable<Spec> generateImports(final AssetId outputId, final State state) sync* {
final analyzerIgnores = state.buildConfig.analyzerIgnores;
if (analyzerIgnores != null) {
for (final rule in analyzerIgnores) {
yield Code('// ignore_for_file: $rule');
}
}

yield* [
Directive.import('dart:convert'),
Directive.import('dart:typed_data'),
const Code(''),
Directive.import('package:built_collection/built_collection.dart'),
Directive.import('package:built_value/built_value.dart'),
Directive.import('package:built_value/json_object.dart'),
Directive.import('package:built_value/serializer.dart'),
Directive.import('package:built_value/standard_json_plugin.dart'),
Directive.import('package:collection/collection.dart'),
Directive.import('package:dynamite_runtime/built_value.dart'),
Directive.import('package:dynamite_runtime/http_client.dart'),
Directive.import('package:dynamite_runtime/models.dart'),
Directive.import('package:dynamite_runtime/utils.dart'),
Directive.import('package:meta/meta.dart'),
Directive.import('package:universal_io/io.dart'),
const Code(''),
];

if (state.hasResolvedBuiltTypes) {
yield Directive.part(p.basename(outputId.changeExtension('.g.dart').path));
yield const Code('');
}
}
5 changes: 4 additions & 1 deletion packages/dynamite/dynamite/lib/src/builder/state.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'package:code_builder/code_builder.dart';
import 'package:dynamite/src/models/dynamite_config/config.dart';
import 'package:dynamite/src/models/type_result.dart';

class State {
State();
State(this.buildConfig);

final DynamiteConfig buildConfig;

final output = <Spec>[];
final resolvedTypes = <TypeResult>{};
Expand Down
1 change: 1 addition & 0 deletions packages/dynamite/dynamite/lib/src/models/config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'dynamite_config/config.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';

part 'config.g.dart';

/// The configuration used by the dynamite builder.
abstract class DynamiteConfig implements Built<DynamiteConfig, DynamiteConfigBuilder> {
factory DynamiteConfig([final void Function(DynamiteConfigBuilder) updates]) = _$DynamiteConfig;

const DynamiteConfig._();

/// Constructs the dynamite config from a json like map.
factory DynamiteConfig.fromJson(final Map<String, dynamic> json) => _serializers.deserializeWith(serializer, json)!;

/// Serializes this configuration to json.
Map<String, dynamic> toJson() => _serializers.serializeWith(serializer, this)! as Map<String, dynamic>;

static Serializer<DynamiteConfig> get serializer => _$dynamiteConfigSerializer;

static const String configPath = 'dynamite.yaml';

/// A set of lint rules to ignore for the entire generated file.
@BuiltValueField(wireName: 'analyzer_ignores')
BuiltSet<String>? get analyzerIgnores;

/// A set of regular expressions used to exclude parts from code coverage.
///
/// All matches will be wrapped in `// coverage:ignore-start` and `// coverage:ignore-end` blocks.
@BuiltValueField(wireName: 'coverage_ignores')
BuiltSet<String>? get coverageIgnores;
}

@SerializersFor([
DynamiteConfig,
])
final Serializers _serializers = (_$_serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 21 additions & 21 deletions packages/dynamite/dynamite/lib/src/openapi_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import 'package:dynamite/src/builder/generate_schemas.dart';
import 'package:dynamite/src/builder/imports.dart';
import 'package:dynamite/src/builder/serializer.dart';
import 'package:dynamite/src/builder/state.dart';
import 'package:dynamite/src/models/config.dart';
import 'package:dynamite/src/models/openapi.dart' as openapi;
import 'package:version/version.dart';

class OpenAPIBuilder implements Builder {
OpenAPIBuilder(
final BuilderOptions options,
) : buildConfig = DynamiteConfig.fromJson(options.config);

@override
final buildExtensions = const {
'.openapi.json': ['.openapi.dart'],
Expand All @@ -26,6 +31,9 @@ class OpenAPIBuilder implements Builder {
/// The maximum openapi version supported by this builder.
static final Version maxSupportedVersion = minSupportedVersion.incrementMajor();

/// The configuration for this builder.
final DynamiteConfig buildConfig;

@override
Future<void> build(final BuildStep buildStep) async {
try {
Expand Down Expand Up @@ -54,7 +62,7 @@ class OpenAPIBuilder implements Builder {
throw Exception('Only OpenAPI between $minSupportedVersion and $maxSupportedVersion are supported.');
}

final state = State();
final state = State(buildConfig);

// Imports need to be generated after everything else so we know if we need the local part directive,
// but they need to be added to the beginning of the output.
Expand All @@ -64,28 +72,20 @@ class OpenAPIBuilder implements Builder {
..addAll(buildSerializer(state))
..insertAll(0, generateImports(outputId, state));

final patterns = [
RegExp(
r'const .*\._\(\);',
),
RegExp(
r'factory .*\.fromJson\(Map<String, dynamic> json\) => _jsonSerializers\.deserializeWith\(serializer, json\)!;',
),
RegExp(
r'Map<String, dynamic> toJson\(\) => _jsonSerializers\.serializeWith\(serializer, this\)! as Map<String, dynamic>;',
),
RegExp(
r'static BuiltSet<.*> get values => _\$.*Values;',
),
];

var outputString = output.build().map((final e) => e.accept(emitter)).join('\n');
for (final pattern in patterns) {
outputString = outputString.replaceAllMapped(
pattern,
(final match) => ' // coverage:ignore-start\n${match.group(0)}\n // coverage:ignore-end',
);

final coverageIgnores = state.buildConfig.coverageIgnores;
if (coverageIgnores != null) {
for (final ignore in coverageIgnores) {
final pattern = RegExp(ignore);

outputString = outputString.replaceAllMapped(
pattern,
(final match) => ' // coverage:ignore-start\n${match.group(0)}\n // coverage:ignore-end',
);
}
}

final formatter = DartFormatter(pageWidth: 120);
await buildStep.writeAsString(
outputId,
Expand Down
Loading

0 comments on commit 35de0d6

Please sign in to comment.