Skip to content

Commit

Permalink
feat(dart_frog_gen): add route configuration validation to gen (felan…
Browse files Browse the repository at this point in the history
…gel#614)

* feat(dart_frog_gen): add route configuration validation to gen

* fix dartdoc

* fix tests
  • Loading branch information
renancaraujo committed Apr 27, 2023
1 parent 6cc752d commit de88080
Show file tree
Hide file tree
Showing 10 changed files with 571 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,2 +1,3 @@
.idea
.vscode/settings.json
.vscode/settings.json
.DS_Store
1 change: 1 addition & 0 deletions packages/dart_frog_gen/lib/dart_frog_gen.dart
Expand Up @@ -2,3 +2,4 @@
library dart_frog_gen;

export 'src/build_route_configuration.dart';
export 'src/validate_route_configuration/validate_route_configuration.dart';
@@ -0,0 +1,49 @@
import 'dart:io' as io;

import 'package:path/path.dart' as path;
import 'package:pubspec_parse/pubspec_parse.dart';

/// Type definition for callbacks that report external path dependencies.
typedef OnExternalPathDependency = void Function(
String dependencyName,
String dependencyPath,
);

/// Reports existence of external path dependencies on a [io.Directory].
Future<void> reportExternalPathDependencies(
io.Directory directory, {
/// Callback called when any external path dependency is found.
void Function()? onViolationStart,

/// Callback called for each external path dependency found.
OnExternalPathDependency? onExternalPathDependency,

/// Callback called when any external path dependency is found.
void Function()? onViolationEnd,
}) async {
final pubspec = Pubspec.parse(
await io.File(path.join(directory.path, 'pubspec.yaml')).readAsString(),
);

final dependencies = pubspec.dependencies;
final devDependencies = pubspec.devDependencies;
final pathDependencies = [...dependencies.entries, ...devDependencies.entries]
.where((entry) => entry.value is PathDependency)
.map((entry) {
final value = entry.value as PathDependency;
return [entry.key, value.path];
}).toList();
final externalDependencies = pathDependencies.where(
(dep) => !path.isWithin(directory.path, dep.last),
);

if (externalDependencies.isNotEmpty) {
onViolationStart?.call();
for (final dependency in externalDependencies) {
final dependencyName = dependency.first;
final dependencyPath = path.normalize(dependency.last);
onExternalPathDependency?.call(dependencyName, dependencyPath);
}
onViolationEnd?.call();
}
}
@@ -0,0 +1,33 @@
import 'package:dart_frog_gen/dart_frog_gen.dart';
import 'package:path/path.dart' as path;

/// Type definition for callbacks that report rogue routes.
typedef OnRogueRoute = void Function(String filePath, String idealPath);

/// Reports existence of rogue routes on a [RouteConfiguration].
void reportRogueRoutes(
RouteConfiguration configuration, {
/// Callback called when any rogue route is found.
void Function()? onViolationStart,

/// Callback called for each rogue route found.
OnRogueRoute? onRogueRoute,

/// Callback called when any rogue route is found.
void Function()? onViolationEnd,
}) {
if (configuration.rogueRoutes.isNotEmpty) {
onViolationStart?.call();
for (final route in configuration.rogueRoutes) {
final filePath = path.normalize(path.join('routes', route.path));
final fileDirectory = path.dirname(filePath);
final idealPath = path.join(
fileDirectory,
path.basenameWithoutExtension(filePath),
'index.dart',
);
onRogueRoute?.call(filePath, idealPath);
}
onViolationEnd?.call();
}
}
@@ -0,0 +1,42 @@
import 'package:dart_frog_gen/dart_frog_gen.dart';
import 'package:path/path.dart' as path;

/// Type definition for callbacks that report route conflicts.
typedef OnRouteConflict = void Function(
String originalFilePath,
String conflictingFilePath,
String conflictingEndpoint,
);

/// Reports existence of route conflicts on a [RouteConfiguration].
void reportRouteConflicts(
RouteConfiguration configuration, {
/// Callback called when any route conflict is found.
void Function()? onViolationStart,

/// Callback called for each route conflict found.
OnRouteConflict? onRouteConflict,

/// Callback called when any route conflict is found.
void Function()? onViolationEnd,
}) {
final conflictingEndpoints =
configuration.endpoints.entries.where((entry) => entry.value.length > 1);
if (conflictingEndpoints.isNotEmpty) {
onViolationStart?.call();
for (final conflict in conflictingEndpoints) {
final originalFilePath = path.normalize(
path.join('routes', conflict.value.first.path),
);
final conflictingFilePath = path.normalize(
path.join('routes', conflict.value.last.path),
);
onRouteConflict?.call(
originalFilePath,
conflictingFilePath,
conflict.key,
);
}
onViolationEnd?.call();
}
}
@@ -0,0 +1,3 @@
export 'external_path_dependencies.dart';
export 'rogue_routes.dart';
export 'route_conflicts.dart';
1 change: 1 addition & 0 deletions packages/dart_frog_gen/pubspec.yaml
Expand Up @@ -11,6 +11,7 @@ environment:

dependencies:
path: ^1.8.1
pubspec_parse: ^1.2.2

dev_dependencies:
mocktail: ^0.3.0
Expand Down
@@ -0,0 +1,138 @@
import 'dart:io';

import 'package:dart_frog_gen/dart_frog_gen.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

void main() {
group('reportExternalPathDependencies', () {
late bool violationStartCalled;
late bool violationEndCalled;
late List<String> externalPathDependencies;

setUp(() {
violationStartCalled = false;
violationEndCalled = false;
externalPathDependencies = [];
});

test('reports nothing when there are no external path dependencies',
() async {
final directory = Directory.systemTemp.createTempSync();
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
'''
name: example
version: 0.1.0
environment:
sdk: ^2.17.0
dependencies:
mason: any
dev_dependencies:
test: any
''',
);

await expectLater(
reportExternalPathDependencies(
directory,
onViolationStart: () {
violationStartCalled = true;
},
onViolationEnd: () {
violationEndCalled = true;
},
onExternalPathDependency: (dependencyName, _) {
externalPathDependencies.add(dependencyName);
},
),
completes,
);

expect(violationStartCalled, isFalse);
expect(violationEndCalled, isFalse);
expect(externalPathDependencies, isEmpty);
});

test('reports when there is a single external path dependency', () async {
final directory = Directory.systemTemp.createTempSync();
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
'''
name: example
version: 0.1.0
environment:
sdk: ^2.17.0
dependencies:
mason: any
foo:
path: ../../foo
dev_dependencies:
test: any
''',
);

await expectLater(
reportExternalPathDependencies(
directory,
onViolationStart: () {
violationStartCalled = true;
},
onViolationEnd: () {
violationEndCalled = true;
},
onExternalPathDependency: (dependencyName, _) {
externalPathDependencies.add(dependencyName);
},
),
completes,
);

expect(violationStartCalled, isTrue);
expect(violationEndCalled, isTrue);
expect(externalPathDependencies, ['foo']);

directory.delete(recursive: true).ignore();
});

test('reports when there are multiple external path dependencies',
() async {
final directory = Directory.systemTemp.createTempSync();
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
'''
name: example
version: 0.1.0
environment:
sdk: ^2.17.0
dependencies:
mason: any
foo:
path: ../../foo
dev_dependencies:
test: any
bar:
path: ../../bar
''',
);
await expectLater(
reportExternalPathDependencies(
directory,
onViolationStart: () {
violationStartCalled = true;
},
onViolationEnd: () {
violationEndCalled = true;
},
onExternalPathDependency: (dependencyName, _) {
externalPathDependencies.add(dependencyName);
},
),
completes,
);

expect(violationStartCalled, isTrue);
expect(violationEndCalled, isTrue);
expect(externalPathDependencies, ['foo', 'bar']);

directory.delete(recursive: true).ignore();
});
});
}

0 comments on commit de88080

Please sign in to comment.