| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:convert'; | |
| /// Parse the given [jsonText] into the [AllowedExperiments]. | |
| /// | |
| /// Throw [FormatException] is any format issues are found. | |
| AllowedExperiments parseAllowedExperiments(String jsonText) { | |
| return new _AllowedExperimentsParser(jsonText).parse(); | |
| } | |
| /// The set of experiments enabled for SDK and packages. | |
| class AllowedExperiments { | |
| /// The set of experiments that are enabled for all SDK libraries other than | |
| /// for those which are specified in [sdkLibraryExperiments]. | |
| final List<String> sdkDefaultExperiments; | |
| /// Mapping from individual SDK libraries, e.g. 'core', to the set of | |
| /// experiments that are enabled for this library. | |
| final Map<String, List<String>> sdkLibraryExperiments; | |
| /// Mapping from package names, e.g. 'path', to the set of experiments that | |
| /// are enabled for all files of this package. | |
| final Map<String, List<String>> packageExperiments; | |
| new({ | |
| required this.sdkDefaultExperiments, | |
| required this.sdkLibraryExperiments, | |
| required this.packageExperiments, | |
| }); | |
| /// Return the set of enabled experiments for the package with the [name], | |
| /// e.g. "path", possibly `null`. | |
| List<String>? forPackage(String name) { | |
| return packageExperiments[name]; | |
| } | |
| /// Return the set of enabled experiments for the library with the [name], | |
| /// e.g. "core". | |
| List<String> forSdkLibrary(String name) { | |
| return sdkLibraryExperiments[name] ?? sdkDefaultExperiments; | |
| } | |
| } | |
| class _AllowedExperimentsParser { | |
| final String _jsonText; | |
| final List<String> _parsePath = []; | |
| final Map<String, List<String>> _experimentSets = {}; | |
| new(this._jsonText); | |
| AllowedExperiments parse() { | |
| Object rootObject = json.decode(_jsonText); | |
| Map<String, Object?> map = _mustBeMap(rootObject); | |
| _ensureVersion(map); | |
| _withParsePath('experimentSets', () { | |
| Map<String, Object?> experimentSetMap = _requiredMap( | |
| map, | |
| 'experimentSets', | |
| ); | |
| for (MapEntry<String, Object?> entry in experimentSetMap.entries) { | |
| String setName = entry.key; | |
| _withParsePath(setName, () { | |
| _experimentSets[setName] = _mustBeListOfStrings(entry.value); | |
| }); | |
| } | |
| }); | |
| List<String> sdkDefaultExperimentSet = <String>[]; | |
| Map<String, List<String>> sdkLibraryExperiments = <String, List<String>>{}; | |
| _withParsePath('sdk', () { | |
| Map<String, Object?> sdkMap = _requiredMap(map, 'sdk'); | |
| _withParsePath('default', () { | |
| sdkDefaultExperimentSet = _experimentList( | |
| _requiredMap(sdkMap, 'default'), | |
| ); | |
| }); | |
| _withParsePath('libraries', () { | |
| Map<String, Object?>? sdkLibraryExperimentsMap = _optionalMap( | |
| sdkMap, | |
| 'libraries', | |
| ); | |
| if (sdkLibraryExperimentsMap != null) { | |
| for (MapEntry<String, Object?> entry | |
| in sdkLibraryExperimentsMap.entries) { | |
| String libraryName = entry.key; | |
| _withParsePath(libraryName, () { | |
| Map<String, Object?> libraryMap = _mustBeMap(entry.value); | |
| List<String> experimentList = _experimentList(libraryMap); | |
| sdkLibraryExperiments[libraryName] = experimentList; | |
| }); | |
| } | |
| } | |
| }); | |
| }); | |
| Map<String, List<String>> packageExperiments = <String, List<String>>{}; | |
| _withParsePath('packages', () { | |
| Map<String, Object?>? packageExperimentsMap = _optionalMap( | |
| map, | |
| 'packages', | |
| ); | |
| if (packageExperimentsMap != null) { | |
| for (MapEntry<String, Object?> entry in packageExperimentsMap.entries) { | |
| String packageName = entry.key; | |
| _withParsePath(packageName, () { | |
| Map<String, Object?> libraryMap = _mustBeMap(entry.value); | |
| List<String> experimentList = _experimentList(libraryMap); | |
| packageExperiments[packageName] = experimentList; | |
| }); | |
| } | |
| } | |
| }); | |
| return new AllowedExperiments( | |
| sdkDefaultExperiments: sdkDefaultExperimentSet, | |
| sdkLibraryExperiments: sdkLibraryExperiments, | |
| packageExperiments: packageExperiments, | |
| ); | |
| } | |
| void _ensureVersion(Map<String, Object?> map) { | |
| Object? versionObject = map['version']; | |
| if (versionObject is! int || versionObject != 1) { | |
| throw new FormatException( | |
| "Expected field 'version' with value '1'; " | |
| "actually ${versionObject.runtimeType}: $versionObject", | |
| _jsonText, | |
| ); | |
| } | |
| } | |
| List<String> _experimentList(Map<String, Object?> map) { | |
| String experimentSetName = _requiredString(map, 'experimentSet'); | |
| List<String>? result = _experimentSets[experimentSetName]; | |
| if (result != null) { | |
| return result; | |
| } | |
| throw new FormatException( | |
| "No experiment set '$experimentSetName in $_experimentSets", | |
| _jsonText, | |
| ); | |
| } | |
| List<String> _mustBeListOfStrings(Object? object) { | |
| if (object is List<Object?> && object.every((e) => e is String)) { | |
| return List.castFrom(object); | |
| } | |
| String path = _parsePath.join(' / '); | |
| throw new FormatException( | |
| "Expected list of strings at $path, " | |
| "actually ${object.runtimeType}: $object", | |
| _jsonText, | |
| ); | |
| } | |
| Map<String, Object?> _mustBeMap(Object? object) { | |
| if (object is Map<String, Object?>) { | |
| return object; | |
| } | |
| String path = _parsePath.isNotEmpty ? _parsePath.join(' / ') : 'root'; | |
| throw new FormatException( | |
| "Expected map at $path, " | |
| "actually ${object.runtimeType}: $object", | |
| _jsonText, | |
| ); | |
| } | |
| Map<String, Object?>? _optionalMap(Map<String, Object?> map, String name) { | |
| Object? result = map[name]; | |
| if (result is Map<String, Object?>?) { | |
| return result; | |
| } | |
| String path = _parsePath.join(' / '); | |
| throw new FormatException( | |
| "Expected map at $path, actually ${result.runtimeType}: $result", | |
| _jsonText, | |
| ); | |
| } | |
| Map<String, Object?> _requiredMap(Map<String, Object?> map, String name) { | |
| Object? result = map[name]; | |
| if (result is Map<String, Object?>) { | |
| return result; | |
| } | |
| String path = _parsePath.join(' / '); | |
| throw new FormatException( | |
| "Expected map at $path, actually ${result.runtimeType}: $result", | |
| _jsonText, | |
| ); | |
| } | |
| String _requiredString(Map<String, Object?> map, String name) { | |
| Object? result = map[name]; | |
| if (result is String) { | |
| return result; | |
| } | |
| String path = _parsePath.join(' / '); | |
| throw new FormatException( | |
| "Expected string at $path, actually ${result.runtimeType}: $result", | |
| _jsonText, | |
| ); | |
| } | |
| void _withParsePath(String name, void Function() f) { | |
| _parsePath.add(name); | |
| try { | |
| f(); | |
| } finally { | |
| _parsePath.removeLast(); | |
| } | |
| } | |
| } |