flap 0.0.4
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 backends —
package:dio(default, full-featured) orpackage:http(lightweight) - Sound null safety — generates both
null_safe/andnull_unsafe/output side-by-side - Full schema coverage — objects, arrays, maps, enums, discriminated unions (
oneOf+ discriminator), untagged unions (anyOf/oneOf),allOfinheritance, recursive types - PATCH tri-state semantics —
Optional<T?>wrapper fornullable: true+ optional fields, distinguishing "key absent" from "key explicitly null" - Security schemes —
apiKey(header, query, cookie), HTTP Bearer, HTTP Basic, OAuth 2.0, OpenID Connect; credentials injected via Dio interceptors orhttpheaders automatically - Response headers — typed Dart 3 named-record return types for operations with declared response headers
default:values — emitted as Freezed@Default(...)annotationsdate-timeformat →DateTime;float/doubleformat →double- Multipart uploads —
multipart/form-datarequest bodies viaFormData/MultipartRequest - Multiple servers —
servers:array emitted as a typedabstract 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.yaml → generated/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:
{template-dir}/{exact-filename}— verbatim copy, highest priority{template-dir}/model.dart.jinja— Jinja2 template applied to every model{template-dir}/client.dart.jinja— Jinja2 template applied to the client{template-dir}/flap_utils.dart— verbatim override for the runtime file- 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:
- Rust —
cargo fmt,cargo clippy,cargo teston Ubuntu, macOS, and Windows - Dart —
dart analyze,dart pub publish --dry-run
The release workflow (.github/workflows/release.yml) triggers on v* tags:
- Builds the Rust binary for all four targets in parallel
- Packages each into a
.tar.gz(Unix) or.zip(Windows) with a SHA-256 sidecar - Creates a GitHub Release and attaches all archives
- Publishes the Dart package to pub.dev via OIDC
License #
MIT — see LICENSE.
