gql-dart / ferry

Stream-based strongly typed GraphQL client for Dart
https://ferrygraphql.com/
MIT License
593 stars 113 forks source link

[Feature Request] Access properties using Maps (ferry_generator) #577

Open codakkk opened 5 months ago

codakkk commented 5 months ago

Accessing generated code members via a Map should be feasible. For example, in my current project, I'm utilizing 'proto_grid' for table management. This tool enables column filtering using various criteria such as "Greater than", "Contains", "Equals to", etc. Consequently, for each column that is available, I find myself manually scripting a filter like so:

Future<PlutoInfinityScrollRowsResponse> _fetch(
    PlutoInfinityScrollRowsRequest request,
  ) async {
    final filterBuilder = GVehiclesBoolExpBuilder();

    if (request.lastRow != null) {
      filterBuilder.id.G_gt = int.tryParse(request.lastRow?.cells['id']?.value);
    }

    if (request.filterRows.isNotEmpty) {
      final filters = FilterHelper.convertRowsToMap(request.filterRows);

      if (filters.containsKey('name')) {
          filterBuilder.name.G_eq = v[PlutoFilterTypeEquals.name];
          final containsValue = v[PlutoFilterTypeContains.name];
          final startsWithValue = v[PlutoFilterTypeStartsWith.name];
          final endsWithValue = v[PlutoFilterTypeEndsWith.name];

          if (containsValue != null) {
            filterBuilder.name.G_ilike = '%$containsValue%';
          } else if (startsWithValue != null) {
            filterBuilder.name.G_ilike = '$startsWithValue%';
          } else if (endsWithValue != null) {
            filterBuilder.name.G_ilike = '%$endsWithValue';
          }

          filterBuilder.name.G_gt = v[PlutoFilterTypeGreaterThan.name];
          filterBuilder.name.G_gte = v[PlutoFilterTypeGreaterThanOrEqualTo.name];
          filterBuilder.name.G_lt = v[PlutoFilterTypeLessThan.name];
          filterBuilder.name.G_lte = v[PlutoFilterTypeLessThanOrEqualTo.name];
       }

       if(filters.containsKey('companyOwner')) {
           // Same code as before but with filterBuilder.companyOwner
       }
    }

    final req = gqlClient.request(GVehicleListReq(
      (b) => b
        ..requestId = 'VehicleListReq'
        ..vars.where = filterBuilder
        ..vars.limit = 20
        ..fetchPolicy = FetchPolicy.NetworkOnly,
    ));

    final result = await req.first;
    final vehicles = result.data?.vehicles ?? BuiltList();

    return PlutoInfinityScrollRowsResponse(
      isLast: vehicles.length < 20,
      rows: vehicles.map(_createRowFromData).toList(),
    );
  }

Hence, the feature request aims to provide a Map<String, TComparisonExpBuilder> or a similar construct (like overriding the [] operator with a set of if-else statements) to enable this kind of access:

      for (final filter in filters) {
        filterBuilder[filter].G_eq = filters[filter][PlutoFilterTypeEquals.name];
        filterBuilder[filter].G_gt = filters[filter][PlutoFilterTypeGreaterThan.name];
        // etc.
      }

I understand this approach might lead to some type errors, but I am willing to consider a dynamic type and perform manual casting, like this:

    for (final filter in filters) {
        filterBuilder.get<GStringComparisonExpBuilder>(filter).G_eq = filters[filter][PlutoFilterTypeEquals.name];
        filterBuilder.get<GStringComparisonExpBuilder>(filter).G_gt = filters[filter][PlutoFilterTypeGreaterThan.name];
        // etc.
      }

An alternative, albeit less elegant, solution I've considered is the following, which aims to avoid manually setting all the fields:

extension GStringComparisonExpBuilderX on GStringComparisonExpBuilder {
  void buildFromMap(List<Map<String, String>>? map) {
    if (map == null) {
      return;
    }
    for (final v in map) {
      G_eq = v[PlutoFilterTypeEquals.name];
      final containsValue = v[PlutoFilterTypeContains.name];
      final startsWithValue = v[PlutoFilterTypeStartsWith.name];
      final endsWithValue = v[PlutoFilterTypeEndsWith.name];

      if (containsValue != null) {
        G_ilike = '%$containsValue%';
      } else if (startsWithValue != null) {
        G_ilike = '$startsWithValue%';
      } else if (endsWithValue != null) {
        G_ilike = '%$endsWithValue';
      }

      G_gt = v[PlutoFilterTypeGreaterThan.name];
      G_gte = v[PlutoFilterTypeGreaterThanOrEqualTo.name];
      G_lt = v[PlutoFilterTypeLessThan.name];
      G_lte = v[PlutoFilterTypeLessThanOrEqualTo.name];
    }
  }
}

However, this method still requires manual verification of each filter's existence, similar to the first example. I think it's possible to better use code generation for those use-cases. Thank you!

knaeckeKami commented 5 months ago

I don't really understand what you are trying to do here.

Specifically:

codakkk commented 5 months ago
  • what does your schema/inputs look like?

The schema is really big and I don't think this is important here. I'm trying to access to generated code members using [] operator. In my case instead of accessing name like this: filterBuilder.name.G_ilike, access it like this: filterBuilder['name'].G_ilike

what does ` FilterHelper.convertRowsToMap(request.filterRows);? do?

This just converts from ProtoRow to a Map<String, List<Map<String, String>>>. An example result is the following: {all: [{Contains: abc}, {Contains: 123}]}

what does v[] do?

Just access the value in the previous example (for example: abc or 123).

why does GVehiclesBoolExp.fromJson() not work?

Didn't know it even existed, there's no mention on the examples or in the official documentation. Can you provide me an example? Thanks!

would using JsonOperationRequest() help?

Well didn't know about this either. Can you provide an example?

Thank you for your answer, anyway

knaeckeKami commented 5 months ago

ferry uses built_value for serialization. All generated request- data and variable classes can be serialized and deserialized from and to json. see https://pub.dev/packages/built_value

JsonOperationRequest

is just a request that takes a raw graphql document, takes in json variables and returns json. it is meant as an escape hatch when the generated code is inconvenient.