GenProjectConfig.parse constructor

GenProjectConfig.parse(
  1. Map<String, Object?> config
)

Implementation

factory GenProjectConfig.parse(Map<String, Object?> config) {
  final permissions = ((config['permissions'] as Map?) ?? const {})
      .cast<String, Object?>();

  ({Set<String> serverAssigned, Set<String> redacted}) markersFor(
    String modelName,
  ) {
    final entry = (permissions[modelName] as Map?)?.cast<String, Object?>();
    if (entry == null) {
      return (serverAssigned: const {}, redacted: const {});
    }
    final assigned = <String>{};
    final redacted = <String>{};
    final operations = ((entry['operations'] as Map?) ?? const {})
        .cast<String, Object?>();
    final create =
        (operations['create'] as Map?)?.cast<String, Object?>() ?? const {};
    if (create['allow'] == 'owner' && create['ownerField'] is String) {
      assigned.add(create['ownerField'] as String);
    }
    final markers = ((entry['fields'] as Map?) ?? const {})
        .cast<String, Object?>();
    for (final marker in markers.entries) {
      // readOnly: readable, never writable. writeOnly: writable, redacted
      // from reads (rules.ts renderRecord). serverOnly: both.
      if (marker.value == 'readOnly' || marker.value == 'serverOnly') {
        assigned.add(marker.key);
      }
      if (marker.value == 'writeOnly' || marker.value == 'serverOnly') {
        redacted.add(marker.key);
      }
    }
    return (serverAssigned: assigned, redacted: redacted);
  }

  final models = <GenModel>[
    for (final entry
        in ((config['models'] as Map?) ?? const {})
            .cast<String, Object?>()
            .entries)
      GenModel(
        name: entry.key,
        pluralName:
            ((entry.value as Map)['pluralName'] as String?) ??
            '${entry.key}s',
        description: ((entry.value as Map)['description'] as String?) ?? '',
        fields: [
          for (final field
              in (((entry.value as Map)['fields'] as Map?) ?? const {})
                  .cast<String, Object?>()
                  .entries)
            GenField.parse(
              field.key,
              (field.value as Map).cast<String, Object?>(),
            ),
        ],
        serverAssignedFields: markersFor(entry.key).serverAssigned,
        redactedFields: markersFor(entry.key).redacted,
      ),
  ];

  GenModel? modelNamed(String name) {
    for (final model in models) {
      if (model.name == name) return model;
    }
    return null;
  }

  final queries = <GenQuery>[];
  for (final entry
      in ((config['queries'] as Map?) ?? const {})
          .cast<String, Object?>()
          .entries) {
    final spec = (entry.value as Map).cast<String, Object?>();
    final queryModel = spec['model'] as String;
    final where = ((spec['where'] as List?) ?? const [])
        .whereType<Map>()
        .map((clause) => clause.cast<String, Object?>())
        .toList();

    // Resolves the model field (and its owner namespace) a param binds
    // to: a map subfield's owner is '<model> <mapField>', matching the
    // owner its support types are emitted under.
    (GenField, String)? bindingFor(String paramName) {
      for (final clause in where) {
        if (clause['param'] != paramName) continue;
        final path = clause['field'] as String;
        final root = path.split('.').first;
        final field = modelNamed(queryModel)?.field(root);
        if (field == null) return null;
        if (path.contains('.') && field.kind == GenFieldKind.map) {
          final sub = path.split('.')[1];
          for (final mapField in field.mapFields) {
            if (mapField.name == sub) {
              return (mapField, '$queryModel ${field.name}');
            }
          }
          return null;
        }
        return (field, queryModel);
      }
      return null;
    }

    final params = <GenParam>[];
    for (final param
        in ((spec['params'] as Map?) ?? const {})
            .cast<String, Object?>()
            .entries) {
      final paramSpec = (param.value as Map).cast<String, Object?>();
      final type = paramSpec['type'] as String;
      final isList = type.startsWith('list<');
      final base = isList ? type.substring(5, type.length - 1) : type;
      final bound = bindingFor(param.key);
      params.add(
        GenParam(
          name: param.key,
          kind: GenFieldKind.parseBase(base),
          isList: isList,
          description: (paramSpec['description'] as String?) ?? '',
          referenceModel: paramSpec['model'] as String?,
          enumValues: bound?.$1.enumValues ?? const [],
          boundField: bound?.$1,
          boundFieldOwner: bound?.$2,
        ),
      );
    }

    queries.add(
      GenQuery(
        name: entry.key,
        description: (spec['description'] as String?) ?? '',
        isAggregate: spec['kind'] == 'aggregate',
        model: queryModel,
        params: params,
        aggregations: [
          for (final aggregation
              in ((spec['aggregations'] as List?) ?? const [])
                  .whereType<Map>())
            GenAggregation(
              aggregate: aggregation['aggregate'] as String,
              field: aggregation['field'] as String?,
              alias: aggregation['alias'] as String,
            ),
        ],
        pageSizeDefault: (spec['pageSizeDefault'] as num?)?.toInt(),
        pageSizeMax: (spec['pageSizeMax'] as num?)?.toInt(),
        deprecated: (spec['deprecated'] as bool?) ?? false,
      ),
    );
  }

  final storage = ((config['storage'] as Map?) ?? const {})
      .cast<String, Object?>();
  final fileCollections = <GenFileCollection>[
    for (final entry
        in ((storage['fileCollections'] as Map?) ?? const {})
            .cast<String, Object?>()
            .entries)
      GenFileCollection(
        name: entry.key,
        description: ((entry.value as Map)['description'] as String?) ?? '',
        visibility:
            ((entry.value as Map)['visibility'] as String?) ?? 'private',
        maxSizeBytes:
            (((entry.value as Map)['maxSizeBytes']) as num?)?.toInt() ?? 0,
        allowedContentTypes:
            (((entry.value as Map)['allowedContentTypes']) as List? ??
                    const [])
                .cast<String>(),
        metadataFields: [
          for (final field
              in (((entry.value as Map)['metadataFields'] as Map?) ??
                      const {})
                  .cast<String, Object?>()
                  .entries)
            GenField.parse(
              field.key,
              (field.value as Map).cast<String, Object?>(),
            ),
        ],
      ),
  ];

  final auth = ((config['auth'] as Map?) ?? const {}).cast<String, Object?>();
  final profileFields = <GenField>[
    for (final entry
        in ((auth['profileFields'] as Map?) ?? const {})
            .cast<String, Object?>()
            .entries)
      GenField.parse(entry.key, (entry.value as Map).cast<String, Object?>()),
  ];

  final functions = validateFunctionDefinitions(
    config['functions'],
    modelNames: models.map((model) => model.name).toSet(),
    queryNames: queries.map((query) => query.name).toSet(),
    fileCollectionNames: fileCollections.map((files) => files.name).toSet(),
  );
  return GenProjectConfig(
    functions: [
      for (final entry in functions.entries)
        GenFunction._(entry.key, entry.value),
    ],
    models: models,
    queries: queries,
    fileCollections: fileCollections,
    profileFields: profileFields,
    sells: config['payments'] is Map,
  );
}