DevToolsExtensionConfig.parse constructor

DevToolsExtensionConfig.parse(
  1. Map<String, Object?> json
)

Implementation

factory DevToolsExtensionConfig.parse(Map<String, Object?> json) {
  // Default to true if this value is not specified in the JSON.
  final requiresConnectionValue = json[requiresConnectionKey];
  final requiresConnection =
      requiresConnectionValue != false && requiresConnectionValue != 'false';

  if (json
      case {
        // The exptected keys below are required fields in the extension's
        // config.yaml file.
        nameKey: final String name,
        issueTrackerKey: final String issueTracker,
        versionKey: final String version,
        // ignore: avoid-unnecessary-type-assertions, this can be a String or an int
        materialIconCodePointKey: final Object codePointFromJson,
        // The expected keys below are not from the extension's config.yaml
        // file; they are generated during the extension detection mechanism
        // in the DevTools server.
        extensionAssetsPathKey: final String extensionAssetsPath,
        devtoolsOptionsUriKey: final String devtoolsOptionsUri,
        isPubliclyHostedKey: final String isPubliclyHosted,
        detectedFromStaticContextKey: final String detectedFromStaticContext,
        // Note that the field [requiresConnectionKey] is not required for
        // this check because it is optional.
      }) {
    final underscoresAndLetters = RegExp(r'^[a-z0-9_]*$');
    if (!underscoresAndLetters.hasMatch(name)) {
      throw StateError(
        'The "name" field in the extension config.yaml should only contain '
        'lowercase letters, numbers, and underscores but instead was '
        '"$name". This should be a valid Dart package name that matches the '
        'package name this extension belongs to.',
      );
    }

    // Defaults to the code point for [Icons.extensions_outlined] if parsing
    // fails.
    late int codePoint;
    const defaultCodePoint = 0xf03f;
    if (codePointFromJson is String) {
      codePoint = int.tryParse(codePointFromJson) ?? defaultCodePoint;
    } else {
      codePoint = codePointFromJson as int;
    }

    return DevToolsExtensionConfig._(
      // These values are required fields in the extension's config.yaml file.
      name: name,
      issueTrackerLink: issueTracker,
      version: version,
      materialIconCodePoint: codePoint,
      // These values are optional fields in the extension's config.yaml file
      // and will use default values if not specified.
      requiresConnection: requiresConnection,
      // These values are generated by the DevTools server.
      extensionAssetsPath: extensionAssetsPath,
      devtoolsOptionsUri: devtoolsOptionsUri,
      isPubliclyHosted: bool.parse(isPubliclyHosted),
      detectedFromStaticContext: bool.parse(detectedFromStaticContext),
    );
  } else {
    _assertGeneratedKeysPresent(json);
    final jsonKeysFromConfigFile = Set.of(json.keys.toSet())
      ..removeAll([
        ..._serverGeneratedKeys,
        ..._optionalKeys,
      ]);
    final diff = _requiredKeys.toSet().difference(
          jsonKeysFromConfigFile,
        );
    if (diff.isNotEmpty) {
      throw StateError(
        'Missing required fields ${diff.toString()} in the extension '
        'config.yaml.',
      );
    } else {
      // All the required keys are present, but the value types did not match.
      final sb = StringBuffer();
      for (final entry in json.entries) {
        sb.writeln(
          '   ${entry.key}: ${entry.value} (${entry.value.runtimeType})',
        );
      }
      throw StateError(
        'Unexpected value types in the extension config.yaml. Expected all '
        'values to be of type String, but one or more had a different type:\n'
        '${sb.toString()}',
      );
    }
  }
}