flap 0.0.4 copy "flap: ^0.0.4" to clipboard
flap: ^0.0.4 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.


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 — generates both null_safe/ and null_unsafe/ output side-by-side
  • 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 Dio interceptors or http headers 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
  • 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 × mode × backend; 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
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
cargo build --release --bin generate_dart

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, one client file, flap_utils.dart (null-safe mode only), and .flap.lock.* incremental build markers.


Output layout #

generated/
└── petstore/
    ├── pet.dart              # @freezed model
    ├── pets.dart             # typedef List<Pet>
    ├── error.dart            # @freezed model
    ├── swagger_petstore_client.dart   # API client
    ├── flap_utils.dart       # Optional<T?> runtime (null-safe only)
    ├── .flap.lock.null_safe.dio
    └── .flap.lock.null_unsafe.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, :xRateLimitRemaining) = await client.listPets(limit: 10);

http #

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

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
  ),
);

The stripOptionalAbsent helper in flap_utils.dart removes absent keys from the JSON map before serialization.


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.dev 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
│
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 → IR
│       └── swagger.rs         # Swagger 2.0 parser → IR
└── 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)
│
fixtures/                      # OpenAPI 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, validates $ref integrity, operationId uniqueness, security scheme references, then lowers to IR.
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 https:// URL.
                          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.
  --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.

Dependencies #

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

# pubspec.yaml
dependencies:
  dio: ^5.0.0              # if using --client=dio (default)
  http: ^1.0.0             # if using --client=http
  freezed_annotation: ^2.0.0
  json_annotation: ^4.0.0

dev_dependencies:
  build_runner: ^2.0.0
  freezed: ^2.0.0
  json_serializable: ^6.0.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 on Ubuntu, macOS, and Windows
  • Dartdart analyze, dart pub publish --dry-run

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

  1. Builds the Rust binary for all four 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.dev via OIDC

License #

MIT — see LICENSE.

2
likes
160
points
35
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