OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.28k stars 6.44k forks source link

[REQ] [dart] generate ready-to use dart blocs/cubits #6582

Open ayushin opened 4 years ago

ayushin commented 4 years ago

https://github.com/OpenAPITools/openapi-generator/issues/3785

I believe it would be a great help for the flutter/dart community if openapi-generator could generate the repositories boilerplate for the BLOC design patter as was discussed in the issue above.

I understand that many (including myself) have already done some work/research on the subject, so it would be great to combine the efforts and provide a joint uniform solution for this.

auto-labeler[bot] commented 4 years ago

👍 Thanks for opening this issue! 🏷 I have applied any labels matching special text in your issue.

The team will review the labels and make any necessary changes.

ayushin commented 4 years ago

The wish-list:

agilob commented 4 years ago

I believe it would be really useful to agree on a pattern here before anyone starts working on this. There are so many patterns supported with libraries or using basic language features that it needs to be recognised and discussed which implementation should come here. The linked flutter_bloc_patterns pub doesnt seem to be too popular: https://pub.dev/packages?q=bloc

Another thing, I strongly believe that such implementation must be dart-native, not dependent on Flutter, and package like analyser, builder or other that change frequently with their transitional dependencies.

ayushin commented 4 years ago

Hi @agilob, I actually agree with you on all points. I've played with flutter_bloc_patterns and while the idea is nice I don't really agree on the way the data abstraction is constructed. I'm going to publish my own take on the matter, with one basic DataBloc<T,F> and two extensions ListBloc<T,F> extends DataBloc<List<T,F>> and PaginatedBloc<T,F> extends DataBloc<Page<T,F>> which seems to be producing minimal boiler plate and covers all the cases.

I'm very open to the ideas and contribution to hopefully produce something of the quality of form_bloc for lists and pages so we don't reinvent the wheel over and over.

Bloc's built-in onError() handler is a nice bonus too.

Then on top of that one would have PaginatedRepository<Page , F> that would give access to the offset, limit interface in the api.

Ordering and filtering and page access is all done via the Filter that can extend OffsetLimitFilter (int offset; int limit)

ayushin commented 4 years ago

https://github.com/apexlabs-ai/list_bloc

I think the key here is to keep it very simple and flexible.

ayushin commented 4 years ago

The link with the openapi-generator would be to generate the Filter classes for the list endpoints and perhaps even both the Paginated or List repositories and the translation between the Filter class fields and api methods parameters.

ayushin commented 4 years ago

I'm actually very happy with the results - made the filter class a built_value and it all seem to fall into place.

agilob commented 4 years ago

The library you wrote looks nice, but I wouldnt rush into using it in openapi without it having some level of adoption in community, which will just take time and advertising effort (having it in openapi is also advertising effort?).

On an unrelated note, could you please take a look at my MRs? I've been asking for days on different channels for a review and not getting a single response/comment from anyone, I feel this is discoursing me from future contributions https://github.com/OpenAPITools/openapi-generator/pulls/agilob

ayushin commented 4 years ago

Hi, thanks for your remarks! I think we are all super busy (I know for sure I am really short on time) and I am not really looking for advertisement, I'd be very very happy if I did not have to write those libraries and use some other's, which I tried for a while and it did not really work and would not match with the automatic code generation.

So my only interest in terms of open source is to save myself time in the long run, therefore if you are interested in helping I'd be very happy to accept PRs and there are a few issues open, especially missing example and tests.

Looked at your PRs - I think enums and complex json types are especially helpful, haven't run into those problems yet but sure will keep it in mind if I do.

I opened another issue and had to hack it in my fork - with endpoints returning raw streams instead of json (like downloading a binary file via the api)

And well, again as I said I am not making this code just to make code, I could really save some time by auto-generating the api repositories instead of writing boiler plate.

ayushin commented 3 years ago

@wing328 @kuhnroyal

So some time later and we've been using https://pub.dev/packages/list_bloc in production for quite a while in conjunction with dio-next-gen and I think a viable pattern emerges, that I'd like to run by you guys. I am not 100% sure this is completely generic, but it seems that parts of it can always be useful. So we can either create a clone as dart-cubit or perhaps add some extra generated classes to the current generator.

Basically the idea is this:

  1. For each generated api operation we would like to generate a built_value Filter class that we can use instead of filter parameters, like such:
import 'package:built_value/built_value.dart';

part 'currency_filter.g.dart';

abstract class CurrencyFilter
    implements Built<CurrencyFilter, CurrencyFilterBuilder> {
  factory CurrencyFilter([void Function(CurrencyFilterBuilder)? updates]) =
      _$CurrencyFilter;

  String? get search;

  int get offset;

  int get limit;

  CurrencyFilter._();
}

This is easy to do (just list all query parameters) and is very useful for keeping track of the api states.

  1. Autogenerate the operations wrappers that would take the above classes and pass them as query parameters to the relative endpoints - also easy to do

  2. We could then auto-generate endpoint State classes, that's a combination of the Filter, progress, error and data, e.g.:

    class CurrencyListState extends Equatable {
    final CurrencyListFilter? filter;
    final <BuiltList<Currency>? data;
    double? progress; // null - loaded, < 0 undetermined, 0 >= progress >= 1 - loading progress
    DioError error; // non-null - Api Error
    }

I think this is pretty generic for working with all kind of lists and uploads and would allow to easily generate, for example Cubits, e.g:

class CurrencyListCubit extends Cubit<CurrencyListState?> {
    Future<CurrencyListState> load([CurrencyListFilter? filter]) async {
    try {
      emit (await apiCurrencyList(filter: filter)).data;
    } on DioError(e) {
      emit state.withError(e);
    }
}

And this covers 90% of use cases working with any REST api.

Any thoughts, ideas, suggestions?

We are using this pattern quite a lot so we could contribute resources to building the codegen.

kuhnroyal commented 3 years ago

I heavily use dart-dio and do nothing of this sort, I mainly write this into a database during some synchronisation.

Maybe 1. and 2. can be added behind a flag but I don't see 3. being useful for a broad audience tbh.

kuhnroyal commented 3 years ago

I suggest you start by making a PR for 1./2. by use of extension functions on the API classes in src/extensions and behind a feature flag. This leaves the original API classes untouched. And then we can see how that works out and if we want to take this further.

ayushin commented 3 years ago

I suggest you start by making a PR for 1./2. by use of extension functions on the API classes in src/extensions and behind a feature flag. This leaves the original API classes untouched. And then we can see how that works out and if we want to take this further.

Thanks, that's what we'll do, really appreciate your input, working through openapi-generator internals always gives me a headache. I always wonder why this was not built in python/jinja

ayushin commented 3 years ago

So what needs to be done:

  1. add a flag queryClasses=true to dio-next similar like we have now -p nullableFields=true we'll have -p queryClasses=true false by default
  2. This flag will generate /query similar to /model but for each apiEndpoint that has query parameters there will be /query/endpoint_name.dart

Here is an example, all those gets are query parameters:


import 'package:built_value/built_value.dart';

part 'flight_reports_list_filter.g.dart';

abstract class FlightReportsListFilter
    implements Built<FlightReportsListFilter, FlightReportsListFilterBuilder> {
  factory FlightReportsListFilter(
          [void Function(FlightReportsListFilterBuilder) updates]) =
      _$FlightReportsListFilter;

  FlightReportsListFilter._();

  String? get company;

  String? get handling;

  String? get date;

  num? get reporterId;

  String? get reporterCompany;

  String? get ordering;

  String? get search;

  int? get limit;

  int? get offset;

}
  1. When this is done for each endpoint class we need to generate a wrapper around each endpoint:
    reportsFlightsListQuery => ...
    await _api.reportsFlightsList(
          company: f.company,
          handling: f.handling,
          date: f.date,
          reporterId: f.reporterId,
          reporterCompany: f.reporterCompany,
          ordering: f.ordering,
          search: f.search,
          limit: f.limit,
          offset: f.offset,
        )