Skip to content

Commit 269f473

Browse files
committed
final glob
1 parent 3821790 commit 269f473

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

packages/flutter_tools/lib/src/web/devfs_proxy.dart

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:glob/glob.dart';
56
import 'package:yaml/yaml.dart';
67
import '../base/logger.dart';
78

9+
bool _twoCanHandle(bool a, bool b, bool c) {
10+
return (a && b) || (a && c) || (b && c);
11+
}
12+
813
/// Represents a rule for proxying requests based on a specific pattern.
914
/// Subclasses must implement the [matches], [replace], and [getTargetUri] methods.
1015
sealed class ProxyRule {
1116
static const _kLogEntryPrefix = '[ProxyRule]';
1217
static const _kTarget = 'target';
1318
static const _kRegex = 'regex';
1419
static const _kPrefix = 'prefix';
20+
static const _kSource = 'source';
1521
static const _kReplace = 'replace';
1622

1723
/// Checks if the given [path] matches the rule's pattern.
@@ -27,16 +33,22 @@ sealed class ProxyRule {
2733
/// If both or neither 'prefix' and 'regex' are defined, it logs an error and returns null.
2834
/// Otherwise, it tries to create a [PrefixProxyRule] or [RegexProxyRule] based on the [yaml] keys.
2935
static ProxyRule? fromYaml(YamlMap yaml, Logger logger) {
30-
if (PrefixProxyRule.canHandle(yaml) && RegexProxyRule.canHandle(yaml)) {
36+
if (_twoCanHandle(
37+
PrefixProxyRule.canHandle(yaml),
38+
RegexProxyRule.canHandle(yaml),
39+
SourceProxyRule.canHandle(yaml),
40+
)) {
3141
logger.printError(
32-
'$_kLogEntryPrefix Both $_kPrefix and $_kRegex are defined in the proxy rule YAML.'
42+
'$_kLogEntryPrefix Multiple patterns ($_kPrefix, $_kRegex, $_kSource) are defined in the proxy rule YAML.'
3343
' Only one should be used.',
3444
);
3545
return null;
3646
} else if (PrefixProxyRule.canHandle(yaml)) {
3747
return PrefixProxyRule.fromYaml(yaml, logger);
3848
} else if (RegexProxyRule.canHandle(yaml)) {
3949
return RegexProxyRule.fromYaml(yaml, logger);
50+
} else if (SourceProxyRule.canHandle(yaml)) {
51+
return SourceProxyRule.fromYaml(yaml, logger);
4052
} else {
4153
logger.printError('$_kLogEntryPrefix Invalid proxy rule in YAML: $yaml');
4254
return null;
@@ -172,3 +184,54 @@ class PrefixProxyRule extends RegexProxyRule {
172184
return PrefixProxyRule(prefix: prefix, target: target, replacement: replacement?.trim());
173185
}
174186
}
187+
188+
/// A [ProxyRule] implementation that uses glob syntax for matching and
189+
/// replacement.
190+
///
191+
/// This rule matches paths against a provided glob syntax [_source].
192+
/// If a [_replacement] string is provided, it replaces parts of the matched
193+
/// path based on regex group capturing.
194+
class SourceProxyRule extends RegexProxyRule {
195+
/// Creates a [SourceProxyRule] with the given glob [source],
196+
/// [target] URI base, and optional [replacement] string.
197+
SourceProxyRule({required Glob source, required super.target, super.replacement})
198+
: _source = source,
199+
super(pattern: RegExp(RegExp.escape(source.pattern)));
200+
final Glob _source;
201+
202+
@override
203+
bool matches(String path) {
204+
return _source.matches(path);
205+
}
206+
207+
@override
208+
String toString() {
209+
return '{${ProxyRule._kSource}: ${_source.pattern}, ${ProxyRule._kTarget}: $_target, ${ProxyRule._kReplace}: ${_replacement ?? 'null'}}';
210+
}
211+
212+
/// Checks if the given [yaml] can be handled by this rule.
213+
/// It requires the 'source' key to be present and non-empty.
214+
static bool canHandle(YamlMap yaml) {
215+
return yaml.containsKey(ProxyRule._kSource) &&
216+
yaml[ProxyRule._kSource] is String &&
217+
(yaml[ProxyRule._kSource] as String).isNotEmpty;
218+
}
219+
220+
/// Attempts to create a [SourceProxyRule] from the provided [yaml] map.
221+
/// If the 'source' or 'target' keys are missing or invalid, it logs an error
222+
/// and returns null.
223+
static SourceProxyRule? fromYaml(YamlMap yaml, Logger effectiveLogger) {
224+
final source = yaml[ProxyRule._kSource] as String?;
225+
final target = yaml[ProxyRule._kTarget] as String?;
226+
final replacement = yaml[ProxyRule._kReplace] as String?;
227+
if (source == null || source.isEmpty) {
228+
return null;
229+
} else if (target == null || target.isEmpty) {
230+
effectiveLogger.printError(
231+
'${ProxyRule._kLogEntryPrefix} Invalid ${ProxyRule._kTarget} for ${ProxyRule._kSource}: $source. ${ProxyRule._kTarget} cannot be null',
232+
);
233+
return null;
234+
}
235+
return SourceProxyRule(source: Glob(source), target: target, replacement: replacement?.trim());
236+
}
237+
}

packages/flutter_tools/test/general.shard/web/proxy_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66
import 'package:flutter_tools/src/base/logger.dart';
77
import 'package:flutter_tools/src/isolated/proxy_middleware.dart';
88
import 'package:flutter_tools/src/web/devfs_proxy.dart';
9+
import 'package:glob/glob.dart';
910
import 'package:shelf/shelf.dart';
1011
import 'package:test/test.dart';
1112
import 'package:yaml/yaml.dart';
@@ -291,6 +292,81 @@ void main() {
291292
});
292293
});
293294

295+
group('SourceProxyRule', () {
296+
test('canHandle returns true for valid source', () {
297+
final yaml = YamlMap.wrap(<String, String>{
298+
'source': '/assets/**',
299+
'target': 'http://localhost:8080',
300+
});
301+
expect(SourceProxyRule.canHandle(yaml), isTrue);
302+
});
303+
304+
test('canHandle returns false for missing source', () {
305+
final yaml = YamlMap.wrap(<String, String>{'target': 'http://localhost:8080'});
306+
expect(SourceProxyRule.canHandle(yaml), isFalse);
307+
});
308+
309+
test('canHandle returns false for empty source', () {
310+
final yaml = YamlMap.wrap(<String, String>{'source': '', 'target': 'http://localhost:8080'});
311+
expect(SourceProxyRule.canHandle(yaml), isFalse);
312+
});
313+
314+
test('fromYaml creates a SourceProxyRule', () {
315+
final yaml = YamlMap.wrap(<String, String>{
316+
'source': '/assets/**',
317+
'target': 'http://localhost:8080/data',
318+
'replace': '/static_files',
319+
});
320+
final SourceProxyRule? rule = SourceProxyRule.fromYaml(yaml, logger);
321+
expect(rule, isNotNull);
322+
expect(
323+
rule.toString(),
324+
'{source: /assets/**, target: http://localhost:8080/data, replace: /static_files}',
325+
);
326+
});
327+
328+
test('fromYaml returns null if target is missing', () {
329+
final yaml = YamlMap.wrap(<String, String>{'source': '/assets/**'});
330+
final SourceProxyRule? rule = SourceProxyRule.fromYaml(yaml, logger);
331+
expect(rule, isNull);
332+
expect(
333+
logger.errorText,
334+
contains('[ProxyRule] Invalid target for source: /assets/**. target cannot be null'),
335+
);
336+
});
337+
338+
test('matches returns true when glob matches path', () {
339+
final rule = SourceProxyRule(source: Glob('/assets/**.jpg'), target: 'http://localhost:8080');
340+
expect(rule.matches('/assets/images/cat.jpg'), isTrue);
341+
expect(rule.matches('/assets/cat.jpg'), isTrue);
342+
expect(rule.matches('/assets/images/subfolder/cat.jpg'), isTrue);
343+
});
344+
345+
test('matches returns false when glob does not match path', () {
346+
final rule = SourceProxyRule(
347+
source: Glob('/assets/**/*.jpg'),
348+
target: 'http://localhost:8080',
349+
);
350+
expect(rule.matches('/assets/images/dog.png'), isFalse);
351+
expect(rule.matches('/assets/images/dog'), isFalse);
352+
expect(rule.matches('/style.css'), isFalse);
353+
});
354+
355+
test('replace correctly replaces with replacement string', () {
356+
final rule = SourceProxyRule(
357+
source: Glob('/assets/**'),
358+
target: 'http://localhost:8080',
359+
replacement: '/static',
360+
);
361+
expect(rule.replace('/assets/images/image.jpg'), '/static');
362+
});
363+
364+
test('replace returns original path for no replacement', () {
365+
final rule = SourceProxyRule(source: Glob('/assets/**'), target: 'http://localhost:8080');
366+
expect(rule.replace('/assets/images/image.jpg'), '/assets/images/image.jpg');
367+
});
368+
});
369+
294370
group('proxyRequest', () {
295371
test('should correctly proxy all request elements', () async {
296372
final Uri originalUrl = Uri.parse('http://original.example.com/path');

0 commit comments

Comments
 (0)