Skip to content

Commit de88080

Browse files
authored
feat(dart_frog_gen): add route configuration validation to gen (#614)
* feat(dart_frog_gen): add route configuration validation to gen * fix dartdoc * fix tests
1 parent 6cc752d commit de88080

File tree

10 files changed

+571
-1
lines changed

10 files changed

+571
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.idea
2-
.vscode/settings.json
2+
.vscode/settings.json
3+
.DS_Store

packages/dart_frog_gen/lib/dart_frog_gen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
library dart_frog_gen;
33

44
export 'src/build_route_configuration.dart';
5+
export 'src/validate_route_configuration/validate_route_configuration.dart';
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'dart:io' as io;
2+
3+
import 'package:path/path.dart' as path;
4+
import 'package:pubspec_parse/pubspec_parse.dart';
5+
6+
/// Type definition for callbacks that report external path dependencies.
7+
typedef OnExternalPathDependency = void Function(
8+
String dependencyName,
9+
String dependencyPath,
10+
);
11+
12+
/// Reports existence of external path dependencies on a [io.Directory].
13+
Future<void> reportExternalPathDependencies(
14+
io.Directory directory, {
15+
/// Callback called when any external path dependency is found.
16+
void Function()? onViolationStart,
17+
18+
/// Callback called for each external path dependency found.
19+
OnExternalPathDependency? onExternalPathDependency,
20+
21+
/// Callback called when any external path dependency is found.
22+
void Function()? onViolationEnd,
23+
}) async {
24+
final pubspec = Pubspec.parse(
25+
await io.File(path.join(directory.path, 'pubspec.yaml')).readAsString(),
26+
);
27+
28+
final dependencies = pubspec.dependencies;
29+
final devDependencies = pubspec.devDependencies;
30+
final pathDependencies = [...dependencies.entries, ...devDependencies.entries]
31+
.where((entry) => entry.value is PathDependency)
32+
.map((entry) {
33+
final value = entry.value as PathDependency;
34+
return [entry.key, value.path];
35+
}).toList();
36+
final externalDependencies = pathDependencies.where(
37+
(dep) => !path.isWithin(directory.path, dep.last),
38+
);
39+
40+
if (externalDependencies.isNotEmpty) {
41+
onViolationStart?.call();
42+
for (final dependency in externalDependencies) {
43+
final dependencyName = dependency.first;
44+
final dependencyPath = path.normalize(dependency.last);
45+
onExternalPathDependency?.call(dependencyName, dependencyPath);
46+
}
47+
onViolationEnd?.call();
48+
}
49+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'package:dart_frog_gen/dart_frog_gen.dart';
2+
import 'package:path/path.dart' as path;
3+
4+
/// Type definition for callbacks that report rogue routes.
5+
typedef OnRogueRoute = void Function(String filePath, String idealPath);
6+
7+
/// Reports existence of rogue routes on a [RouteConfiguration].
8+
void reportRogueRoutes(
9+
RouteConfiguration configuration, {
10+
/// Callback called when any rogue route is found.
11+
void Function()? onViolationStart,
12+
13+
/// Callback called for each rogue route found.
14+
OnRogueRoute? onRogueRoute,
15+
16+
/// Callback called when any rogue route is found.
17+
void Function()? onViolationEnd,
18+
}) {
19+
if (configuration.rogueRoutes.isNotEmpty) {
20+
onViolationStart?.call();
21+
for (final route in configuration.rogueRoutes) {
22+
final filePath = path.normalize(path.join('routes', route.path));
23+
final fileDirectory = path.dirname(filePath);
24+
final idealPath = path.join(
25+
fileDirectory,
26+
path.basenameWithoutExtension(filePath),
27+
'index.dart',
28+
);
29+
onRogueRoute?.call(filePath, idealPath);
30+
}
31+
onViolationEnd?.call();
32+
}
33+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'package:dart_frog_gen/dart_frog_gen.dart';
2+
import 'package:path/path.dart' as path;
3+
4+
/// Type definition for callbacks that report route conflicts.
5+
typedef OnRouteConflict = void Function(
6+
String originalFilePath,
7+
String conflictingFilePath,
8+
String conflictingEndpoint,
9+
);
10+
11+
/// Reports existence of route conflicts on a [RouteConfiguration].
12+
void reportRouteConflicts(
13+
RouteConfiguration configuration, {
14+
/// Callback called when any route conflict is found.
15+
void Function()? onViolationStart,
16+
17+
/// Callback called for each route conflict found.
18+
OnRouteConflict? onRouteConflict,
19+
20+
/// Callback called when any route conflict is found.
21+
void Function()? onViolationEnd,
22+
}) {
23+
final conflictingEndpoints =
24+
configuration.endpoints.entries.where((entry) => entry.value.length > 1);
25+
if (conflictingEndpoints.isNotEmpty) {
26+
onViolationStart?.call();
27+
for (final conflict in conflictingEndpoints) {
28+
final originalFilePath = path.normalize(
29+
path.join('routes', conflict.value.first.path),
30+
);
31+
final conflictingFilePath = path.normalize(
32+
path.join('routes', conflict.value.last.path),
33+
);
34+
onRouteConflict?.call(
35+
originalFilePath,
36+
conflictingFilePath,
37+
conflict.key,
38+
);
39+
}
40+
onViolationEnd?.call();
41+
}
42+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export 'external_path_dependencies.dart';
2+
export 'rogue_routes.dart';
3+
export 'route_conflicts.dart';

packages/dart_frog_gen/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ environment:
1111

1212
dependencies:
1313
path: ^1.8.1
14+
pubspec_parse: ^1.2.2
1415

1516
dev_dependencies:
1617
mocktail: ^0.3.0
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import 'dart:io';
2+
3+
import 'package:dart_frog_gen/dart_frog_gen.dart';
4+
import 'package:path/path.dart' as path;
5+
import 'package:test/test.dart';
6+
7+
void main() {
8+
group('reportExternalPathDependencies', () {
9+
late bool violationStartCalled;
10+
late bool violationEndCalled;
11+
late List<String> externalPathDependencies;
12+
13+
setUp(() {
14+
violationStartCalled = false;
15+
violationEndCalled = false;
16+
externalPathDependencies = [];
17+
});
18+
19+
test('reports nothing when there are no external path dependencies',
20+
() async {
21+
final directory = Directory.systemTemp.createTempSync();
22+
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
23+
'''
24+
name: example
25+
version: 0.1.0
26+
environment:
27+
sdk: ^2.17.0
28+
dependencies:
29+
mason: any
30+
dev_dependencies:
31+
test: any
32+
''',
33+
);
34+
35+
await expectLater(
36+
reportExternalPathDependencies(
37+
directory,
38+
onViolationStart: () {
39+
violationStartCalled = true;
40+
},
41+
onViolationEnd: () {
42+
violationEndCalled = true;
43+
},
44+
onExternalPathDependency: (dependencyName, _) {
45+
externalPathDependencies.add(dependencyName);
46+
},
47+
),
48+
completes,
49+
);
50+
51+
expect(violationStartCalled, isFalse);
52+
expect(violationEndCalled, isFalse);
53+
expect(externalPathDependencies, isEmpty);
54+
});
55+
56+
test('reports when there is a single external path dependency', () async {
57+
final directory = Directory.systemTemp.createTempSync();
58+
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
59+
'''
60+
name: example
61+
version: 0.1.0
62+
environment:
63+
sdk: ^2.17.0
64+
dependencies:
65+
mason: any
66+
foo:
67+
path: ../../foo
68+
dev_dependencies:
69+
test: any
70+
''',
71+
);
72+
73+
await expectLater(
74+
reportExternalPathDependencies(
75+
directory,
76+
onViolationStart: () {
77+
violationStartCalled = true;
78+
},
79+
onViolationEnd: () {
80+
violationEndCalled = true;
81+
},
82+
onExternalPathDependency: (dependencyName, _) {
83+
externalPathDependencies.add(dependencyName);
84+
},
85+
),
86+
completes,
87+
);
88+
89+
expect(violationStartCalled, isTrue);
90+
expect(violationEndCalled, isTrue);
91+
expect(externalPathDependencies, ['foo']);
92+
93+
directory.delete(recursive: true).ignore();
94+
});
95+
96+
test('reports when there are multiple external path dependencies',
97+
() async {
98+
final directory = Directory.systemTemp.createTempSync();
99+
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
100+
'''
101+
name: example
102+
version: 0.1.0
103+
environment:
104+
sdk: ^2.17.0
105+
dependencies:
106+
mason: any
107+
foo:
108+
path: ../../foo
109+
dev_dependencies:
110+
test: any
111+
bar:
112+
path: ../../bar
113+
''',
114+
);
115+
await expectLater(
116+
reportExternalPathDependencies(
117+
directory,
118+
onViolationStart: () {
119+
violationStartCalled = true;
120+
},
121+
onViolationEnd: () {
122+
violationEndCalled = true;
123+
},
124+
onExternalPathDependency: (dependencyName, _) {
125+
externalPathDependencies.add(dependencyName);
126+
},
127+
),
128+
completes,
129+
);
130+
131+
expect(violationStartCalled, isTrue);
132+
expect(violationEndCalled, isTrue);
133+
expect(externalPathDependencies, ['foo', 'bar']);
134+
135+
directory.delete(recursive: true).ignore();
136+
});
137+
});
138+
}

0 commit comments

Comments
 (0)