|  | // Copyright 2020 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | import 'package:fxtest/fxtest.dart'; | 
|  |  | 
|  | /// Drills into a [Map] through a given sequence of keys. | 
|  | /// These keys should be [String] values for [Map] keys, or [int] values | 
|  | /// for array indices. | 
|  | /// | 
|  | /// If provided keys do not exist in a map, [fallback] will be returned. | 
|  | /// | 
|  | /// If bad indices are provided when encountering a map, a [BadMapPathException] | 
|  | /// will be thrown. | 
|  | /// | 
|  | /// And lastly, if *extra* keys are found (a literal is encountered before all | 
|  | /// keys are used), a [BadMapPathException] will be thrown. | 
|  | /// | 
|  | /// Usage: | 
|  | /// ```dart | 
|  | /// var data = <String, dynamic>{ | 
|  | ///   'key': { | 
|  | ///     'nested': 'value', | 
|  | ///   }, | 
|  | ///   'list': ['of', 'data'], | 
|  | ///   'some_numbers': [1, 3, 5], | 
|  | /// }; | 
|  | /// | 
|  | /// >>> getMapPath<String>(data, ['key', 'nested']) | 
|  | /// 'value' | 
|  | /// | 
|  | /// >>> getMapPath<String>(data, ['list', 1]) | 
|  | /// 'data' | 
|  | /// | 
|  | /// >>> getMapPath<String>(data, ['missing', 'path']) | 
|  | /// null | 
|  | /// | 
|  | /// >>> getMapPath<String>(data, ['missing', 'path'], fallback: 'A safe value') | 
|  | /// 'A safe value' | 
|  | /// | 
|  | /// >>> getMapPath<int>(data, ['some_numbers', 1]) | 
|  | /// 3 | 
|  | /// | 
|  | /// >>> getMapPath<String>(data, ['list', 7]) | 
|  | /// // throws BadMapPathException() | 
|  | /// ``` | 
|  | T getMapPath<T>( | 
|  | Map<String, dynamic> map, | 
|  | List<dynamic> keys, { | 
|  | // Optional default value in case the requisite entries are missing | 
|  | T fallback, | 
|  | // If supplied, casts non-null values into their correct type | 
|  | T Function(dynamic val) caster, | 
|  | }) { | 
|  | if (map == null) { | 
|  | return fallback; | 
|  | } | 
|  |  | 
|  | final dynamic key = keys[0]; | 
|  | final dynamic value = map[key]; | 
|  |  | 
|  | if (key is! String || value == null || (value is String && value.isEmpty)) { | 
|  | return fallback; | 
|  | } | 
|  | // We made it to the end! | 
|  | if (keys.length == 1) { | 
|  | if (caster == null) return value; | 
|  | if (value is T) return value; | 
|  | return caster(value); | 
|  | } else if (value is Map) { | 
|  | return getMapPath( | 
|  | value, | 
|  | keys.sublist(1, keys.length), | 
|  | fallback: fallback, | 
|  | caster: caster, | 
|  | ); | 
|  | } else if (value is List) { | 
|  | return _getListPath( | 
|  | value, | 
|  | keys.sublist(1, keys.length), | 
|  | fallback: fallback, | 
|  | caster: caster, | 
|  | ); | 
|  | } | 
|  | if (fallback != null) return fallback; | 
|  | throw BadMapPathException( | 
|  | 'Reached literal value before exhausting all keys', | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// [List]-based helper for [getMapPath]. | 
|  | /// | 
|  | /// Note: This function used to be woven into [getMapPath]'s own logic, but | 
|  | /// along with leading to unwieldy code, this also created complications when | 
|  | /// needing to handle lists-within-lists. | 
|  | T _getListPath<T>( | 
|  | List<dynamic> data, | 
|  | List<dynamic> keys, { | 
|  | // Optional default value in case the requisite entries are missing | 
|  | T fallback, | 
|  | // If supplied, casts non-null values into their correct type | 
|  | T Function(dynamic val) caster, | 
|  | }) { | 
|  | final key = keys.first; | 
|  |  | 
|  | // Length and type checks first, since unlike Maps which return `null` for bad | 
|  | // keys, Lists throw exceptions. | 
|  | if (key is! int) { | 
|  | if (fallback != null) return fallback; | 
|  | throw BadMapPathException('Reached list value without integer key'); | 
|  | } | 
|  | if (key >= data.length) { | 
|  | return fallback; | 
|  | } | 
|  |  | 
|  | final value = data[key]; | 
|  |  | 
|  | // We made it to the end! | 
|  | if (keys.length == 1) { | 
|  | if (caster == null) return value; | 
|  | if (value is T) return value; | 
|  | return caster(value); | 
|  | } else if (value is Map) { | 
|  | return getMapPath<T>( | 
|  | value, | 
|  | keys.sublist(1), | 
|  | fallback: fallback, | 
|  | caster: caster, | 
|  | ); | 
|  | } else if (value is List) { | 
|  | return _getListPath<T>( | 
|  | value, | 
|  | keys.sublist(1), | 
|  | fallback: fallback, | 
|  | caster: caster, | 
|  | ); | 
|  | } | 
|  | if (fallback != null) return fallback; | 
|  | throw BadMapPathException( | 
|  | 'Reached literal value before exhausting all keys', | 
|  | ); | 
|  | } |