flap 0.0.7 copy "flap: ^0.0.7" to clipboard
flap: ^0.0.7 copied to clipboard

OpenAPI → Dart/Flutter client generator for 3.0, 3.1 and Swagger 2.0. Generates Freezed + json_serializable clients, no Java required.

flap #

Fast, zero-dependency OpenAPI → Dart/Flutter client generator.

flap lowers OpenAPI 3.0, 3.1, and Swagger 2.0 specs into idiomatic, production-ready Dart/Flutter clients using package:freezed and json_serializable — no Java, no Node.js, no Docker required.


Table of contents #


Features #

  • Spec support — OpenAPI 3.0, OpenAPI 3.1, and Swagger 2.0 (YAML or JSON, local file or remote URL)
  • Two HTTP backendspackage:dio (default, full-featured) or package:http (lightweight)
  • Sound null safety — Dart 3 / freezed 3 output; legacy null-unsafe output is available opt-in via --null-unsafe
  • Full schema coverage — objects, arrays, maps, enums, discriminated unions (oneOf + discriminator), untagged unions (anyOf/oneOf), allOf inheritance, recursive types
  • PATCH tri-state semanticsOptional<T?> wrapper for nullable: true + optional fields, distinguishing "key absent" from "key explicitly null"
  • Security schemesapiKey (header, query, cookie), HTTP Bearer, HTTP Basic, OAuth 2.0, OpenID Connect; credentials injected via a Dio interceptor or per-request http headers/query/cookies automatically
  • Response headers — typed Dart 3 named-record return types for operations with declared response headers
  • default: values — emitted as Freezed @Default(...) annotations
  • date-time formatDateTime; float/double format → double; binary format → List<int>
  • Parameter serialization — OpenAPI style/explode (form, deepObject, spaceDelimited, pipeDelimited) and Swagger collectionFormat for query parameters; form-encoded bodies use bracket keys for nested objects
  • Battle-tested — the full Stripe OpenAPI document (594 operations, 1,454 schemas) generates, builds with build_runner, and analyzes cleanly with both backends
  • Lenient loading — inline objects become named classes, root-level enums/primitives are supported, $ref parameters/responses/request bodies and path-level parameters resolve, and unsupported constructs degrade to dynamic with a warning instead of aborting
  • Multipart uploadsmultipart/form-data request bodies via FormData / MultipartRequest
  • Multiple serversservers: array emitted as a typed abstract final class FooClientUrls
  • Incremental builds — lockfile per spec × backend keyed on the spec's SHA-256; unchanged specs are skipped
  • Template overrides — swap any generated file with a Jinja2 template or a verbatim file
  • Type/import mapping — replace generated schema types with your own hand-written classes

Installation #

dart pub global activate flap

On first run, flap downloads the pre-built Rust binary for your platform and caches it at ~/.flap/bin/<version>/. Subsequent runs are instant.

Supported platforms #

Platform Architecture Asset
Linux x86_64 flap-linux-x64.tar.gz
Linux arm64 flap-linux-arm64.tar.gz
macOS x86_64 flap-macos-x64.tar.gz
macOS arm64 (Apple Silicon) flap-macos-arm64.tar.gz
Windows x86_64 flap-windows-x64.zip

Build from source #

Requires the Rust toolchain (stable).

git clone https://github.com/miracle101000/flap-dart
cd flap-dart
cargo build --release --bin generate_dart

Point the Dart wrapper at the result with the FLAP_BINARY environment variable:

FLAP_BINARY=$PWD/target/release/generate_dart flap --out ./generated path/to/spec.yaml

Quick start #

# Generate a Dio client (default)
flap --out ./generated path/to/openapi.yaml

# Generate an http-package client
flap --out ./generated --client=http path/to/openapi.yaml

# Generate from a remote URL
flap --out ./generated https://petstore3.swagger.io/api/v3/openapi.yaml

# Multiple specs in one pass
flap --out ./generated specs/users.yaml specs/payments.yaml

# Force regeneration even if the spec has not changed
flap --out ./generated --force path/to/openapi.yaml

Each spec gets its own subdirectory under --out, named after the spec file stem (e.g. openapi.yamlgenerated/openapi/). Inside it you will find one Dart file per schema (plus one per inline enum), one client file, flap_utils.dart (the Optional<T?> runtime), and a .flap.lock.* incremental build marker. Pass --null-unsafe to additionally emit legacy null-unsafe code into a null_unsafe/ subfolder — note that Dart 3 SDKs cannot compile that dialect.


Output layout #

generated/
└── petstore/
    ├── pet.dart              # @freezed model
    ├── pets.dart             # typedef List<Pet>
    ├── error_model.dart      # @freezed model (`Error` is renamed to avoid dart:core)
    ├── swagger_petstore_client.dart   # API client
    ├── flap_utils.dart       # Optional<T?> runtime + converters
    └── .flap.lock.null_safe.dio

Generated client #

Dio (default) #

final client = SwaggerPetstoreClient(
  baseUrl: SwaggerPetstoreClientUrls.server0, // when multiple servers declared
  bearerAuth: myJwtToken,                     // or apiKey: ..., appId: ...
);

// Typed return — body deserialized automatically
final pet = await client.showPetById(petId: '42');

// Response headers included in a named record when declared in the spec
final (:body, :xNext) = await client.listPets(limit: 10);

// Enum parameters send their wire value; DateTime parameters are ISO 8601;
// path parameters are URI-encoded.

http #

final client = SwaggerPetstoreClient(
  bearerAuth: myJwtToken,
  client: myCustomHttpClient, // optional; defaults to http.Client()
);

try {
  final pet = await client.showPetById(petId: '42');
} on SwaggerPetstoreClientException catch (e) {
  print('${e.statusCode}: ${e.body}'); // thrown for every non-2xx response
}

PATCH / tri-state fields #

For nullable: true + optional fields (the "PATCH cell" — absent vs. explicitly null), flap emits Optional<T?>:

await client.updateUser(
  id: '42',
  body: UpdateUserRequest(
    id: 'required-field',
    bio: null,                         // required + nullable → String?
    nickname: Optional.present(null),  // optional + nullable → set to null
    displayName: Optional.absent(),    // optional + nullable → omit key entirely
  ),
);

Classes with such fields opt out of the freezed-generated toJson and emit one that omits Optional.absent() keys. flap_utils.dart provides Optional, Optional.of(value) (absent when value is null) and the per-type JsonConverters (OptionalStringConverter, OptionalIntConverter, …) the models use. Wire-side null on input deserialises as Optional.absent().

Enums are emitted as enhanced enums with a value field and an unknown fallback, so a server that adds a new value never breaks deserialisation:

final kind = ShapeKind.fromJson('circle'); // ShapeKind.circle
final later = ShapeKind.fromJson('hexagon'); // ShapeKind.unknown
print(kind.value);                         // 'circle'

Schema mapping #

Type mapping #

Replace a generated schema with your own class:

flap --out ./generated \
  --type-map=Pet=MyAppPet \
  --import-map=MyAppPet=package:myapp/models/pet.dart \
  path/to/petstore.yaml

The generated client will import and use MyAppPet wherever Pet would have appeared. The Pet model file is not emitted.

Template overrides #

Customise any generated file via Jinja2 or verbatim replacement:

flap --out ./generated --template-dir=./templates path/to/openapi.yaml

Resolution order per output file:

  1. {template-dir}/{exact-filename} — verbatim copy, highest priority
  2. {template-dir}/model.dart.jinja — Jinja2 template applied to every model
  3. {template-dir}/client.dart.jinja — Jinja2 template applied to the client
  4. {template-dir}/flap_utils.dart — verbatim override for the runtime file
  5. Built-in emitter — fallback

The Jinja2 context exposes class_name, schema_name, fields (with dart_name, dart_type, required, nullable, uses_optional_wrapper, json_name, default_expr), imports, extends, null_safety, and more.


Programmatic use #

import 'package:flap/flap.dart';

Future<void> main() async {
  final exitCode = await FlapRunner().run([
    '--out', './generated',
    '--client=http',
    'api/openapi.yaml',
  ]);
  if (exitCode != 0) throw Exception('flap failed with exit code $exitCode');
}

Project structure #

flap/                          # Dart package (pub.flutter-io.cn distribution)
├── bin/flap.dart              # CLI entry point
├── lib/
│   ├── flap.dart              # Public API export
│   └── src/
│       ├── runner.dart        # FlapRunner — locates and invokes the binary
│       ├── binary_manager.dart # Downloads and caches the platform binary
│       ├── platform_info.dart  # Platform slug / archive extension helpers
│       └── version.dart       # Package version (must match pubspec.yaml)
├── test/                      # Dart wrapper tests
│
crates/                        # Rust workspace
├── flap-ir/                   # Intermediate representation (language-agnostic)
│   └── src/lib.rs             # Api, Schema, Operation, TypeRef, SecurityScheme …
├── flap-spec/                 # OpenAPI / Swagger loader and lowering pass
│   └── src/
│       ├── lib.rs             # OpenAPI 3.x parser, validation and lowering → IR
│       └── swagger.rs         # Swagger 2.0 raw types, translated into the 3.x shape
└── flap-emit-dart/            # Dart code emitter
    └── src/lib.rs             # emit_models(), emit_client(), Jinja2 support
│
src/
└── bin/generate_dart.rs       # Rust CLI binary (the thing Dart shells out to)
│
tests/generate.rs              # Fixture-driven integration tests for lowering, emission and the CLI
fixtures/                      # OpenAPI / Swagger spec fixtures used in tests

Crate responsibilities #

Crate Role
flap-ir Pure data types. No YAML, no Dart. The contract between loader and emitter.
flap-spec Parses raw YAML/JSON (OpenAPI 3.x, or Swagger 2.0 translated into the 3.x shape), validates $ref integrity, operationId uniqueness and security scheme references, then lowers to IR — degrading unsupported constructs to dynamic with warnings.
flap-emit-dart Consumes IR; emits @freezed models, client files, and flap_utils.dart. Supports Jinja2 template overrides via minijinja.

CLI reference #

flap --out <dir> [options] <spec> [<spec> ...]

Arguments:
  <spec>                  Path to a local .yaml/.yml/.json file, or an http(s):// URL.
                          OpenAPI 3.x and Swagger 2.0 are detected automatically.
                          Multiple specs may be provided; each is written to its own subdirectory.

Options:
  --out, -o <dir>         Output directory (required).
  --client=<backend>      HTTP client backend: dio (default) or http.
  --force, -f             Regenerate even if the spec has not changed.
  --null-unsafe           Additionally emit legacy null-unsafe code to <dir>/<spec>/null_unsafe/.
  --type-map=A=B          Replace schema A with Dart type B in all generated files.
                          May be repeated.
  --import-map=B=pkg:...  Import URI for a type introduced by --type-map.
                          May be repeated.
  --template-dir, -t <d>  Directory of Jinja2 / verbatim template overrides.
  --help, -h              Print usage.
  --version, -V           Print the version.

Non-fatal spec problems (unsupported constructs degraded to dynamic, skipped headers, …) are printed as warnings; dangling $refs and duplicate operationIds fail the run.

Set FLAP_BINARY=/path/to/generate_dart to make the Dart wrapper use a locally built generator instead of downloading a release binary.


Dependencies #

The generated Dart code requires these packages in the consuming project:

# pubspec.yaml
environment:
  sdk: ^3.8.0              # json_serializable ≥ 6.9 emits null-aware elements

dependencies:
  dio: ^5.0.0              # if using --client=dio (default)
  http: ^1.0.0             # if using --client=http
  freezed_annotation: ^3.0.0
  json_annotation: ^4.12.0

dev_dependencies:
  build_runner: ^2.4.0
  freezed: ^3.0.0
  json_serializable: ^6.9.0

After generation, run the build runner once:

dart run build_runner build --delete-conflicting-outputs

CI / release workflow #

The GitHub Actions workflow (.github/workflows/ci.yml) runs on every push and pull request:

  • Rustcargo fmt, cargo clippy, cargo test (unit + fixture-driven integration tests) on Ubuntu, macOS, and Windows
  • Dartdart analyze, dart test, dart pub publish --dry-run

The release workflow (.github/workflows/release.yml) triggers on v* tags:

  1. Checks the tag matches pubspec.yaml, then builds the Rust binary for all five targets in parallel
  2. Packages each into a .tar.gz (Unix) or .zip (Windows) with a SHA-256 sidecar
  3. Creates a GitHub Release and attaches all archives
  4. Publishes the Dart package to pub.flutter-io.cn via OIDC

License #

MIT — see LICENSE.


Author #

Miracle Okolo — LinkedIn · GitHub

2
likes
160
points
148
downloads
screenshot

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

OpenAPI → Dart/Flutter client generator for 3.0, 3.1 and Swagger 2.0. Generates Freezed + json_serializable clients, no Java required.

Repository (GitHub)
View/report issues

Topics

#code-generation #openapi #flutter #dart #rest-api

License

MIT (license)

Dependencies

archive, crypto, http, path

More

Packages that depend on flap