implementing-openapi-in-dart — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited implementing-openapi-in-dart (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Given an OpenAPI 3.0 spec, implement a type-safe Dart API layer with:
components/schemas for models, paths for endpoints.DioException → domain ApiError.dependencies:
dio: ^5.9.2
freezed_annotation: ^3.1.0 # if using Freezed
json_annotation: ^4.11.0
equatable: ^2.0.5 # if using Equatable
dev_dependencies:
build_runner: ^2.14.1
freezed: ^3.2.5 # if using Freezed
json_serializable: ^6.13.1lib/
└── data/
├── network/
│ ├── dio_client.dart # Dio instance + interceptors
│ ├── api_error.dart # global error model
│ └── services/
│ └── product_service.dart # one file per resource tag
└── models/
├── product.dart
└── ...import 'package:dio/dio.dart';
Dio createDioClient({required String baseUrl, String? accessToken}) {
final dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {'Content-Type': 'application/json'},
));
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
if (accessToken != null) {
options.headers['Authorization'] = 'Bearer $accessToken';
}
handler.next(options);
},
onError: (error, handler) {
// Convert DioException to domain ApiError before propagating
handler.next(error);
},
));
return dio;
}| Use case | Package | When |
|---|---|---|
Needs copyWith, pattern matching, union types (oneOf) | Freezed | Complex domain models |
Only needs value equality (== / hashCode) | Equatable | Simple request/response DTOs |
// OpenAPI: components/schemas/Product
import 'package:freezed_annotation/freezed_annotation.dart';
part 'product.freezed.dart';
part 'product.g.dart';
@freezed
sealed class Product with _$Product {
const factory Product({
required String id,
required String title,
required double price,
@JsonKey(name: 'created_at') DateTime? createdAt, // snake_case from spec
}) = _Product;
factory Product.fromJson(Map<String, dynamic> json) =>
_$ProductFromJson(json);
}// OpenAPI: components/schemas/CreateProductRequest
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'create_product_request.g.dart';
@JsonSerializable()
class CreateProductRequest extends Equatable {
const CreateProductRequest({required this.title, required this.price});
final String title;
final double price;
Map<String, dynamic> toJson() => _$CreateProductRequestToJson(this);
@override
List<Object?> get props => [title, price];
}| OpenAPI | Dart |
|---|---|
string | String |
integer | int |
number | double |
boolean | bool |
string / format: date-time | DateTime |
string / format: uuid | String |
string / format: uri | Uri |
required array → non-nullable (String id)required → nullable (String? name)// OpenAPI: type: string, enum: [active, draft, archived]
@JsonEnum(fieldRename: FieldRename.snake)
enum ProductStatus { active, draft, archived }// OpenAPI: oneOf: [{$ref: Cat}, {$ref: Dog}]
@freezed
sealed class Pet with _$Pet {
const factory Pet.cat(Cat data) = _PetCat;
const factory Pet.dog(Dog data) = _PetDog;
factory Pet.fromJson(Map<String, dynamic> json) => _$PetFromJson(json);
}
// Exhaustive pattern matching at the call site:
switch (pet) {
case Pet(:final cat): ...
case Pet(:final dog): ...
}{"$ref": "#/components/schemas/Product"} → use Product class directly. Never inline duplicate definitions.
One service class per OpenAPI tag. Method signatures follow: {httpMethod}{Resource}.
// Covers: GET /products, POST /products, GET /products/{id}
class ProductService {
const ProductService(this._dio);
final Dio _dio;
Future<List<Product>> getProducts({int page = 1, int limit = 20}) async {
final response = await _dio.get<List<dynamic>>(
'/products',
queryParameters: {'page': page, 'limit': limit},
);
return response.data!.map((e) => Product.fromJson(e as Map<String, dynamic>)).toList();
}
Future<Product> getProduct(String id) async {
final response = await _dio.get<Map<String, dynamic>>('/products/$id');
return Product.fromJson(response.data!);
}
Future<Product> createProduct(CreateProductRequest request) async {
final response = await _dio.post<Map<String, dynamic>>(
'/products',
data: request.toJson(),
);
return Product.fromJson(response.data!);
}
}Define a global error model matching the spec's error schema:
@freezed
sealed class ApiError with _$ApiError {
const factory ApiError({
required int status,
required String message,
String? detail,
}) = _ApiError;
factory ApiError.fromJson(Map<String, dynamic> json) =>
_$ApiErrorFromJson(json);
}Wrap Dio calls in the repository to catch DioException:
class ProductRepository {
const ProductRepository(this._service);
final ProductService _service;
Future<List<Product>> fetchProducts({int page = 1}) async {
try {
return await _service.getProducts(page: page);
} on DioException catch (e) {
if (e.response?.data != null) {
throw ApiError.fromJson(e.response!.data as Map<String, dynamic>);
}
rethrow;
}
}
}| OpenAPI element | Dart name |
|---|---|
components/schemas/ProductItem | class ProductItem |
POST /products request body | CreateProductRequest |
GET /products response | List<Product> or typedef GetProductsResponse = List<Product> |
snake_case JSON key | @JsonKey(name: 'snake_case') on camelCase field |
copyWith.ApiError.dart run build_runner build --delete-conflicting-outputs after generating models.Freezed 2.x used abstract class instead of sealed class. Both patterns still work, but prefer sealed for exhaustive pattern matching with Dart 3.
# Freezed 2.x (no longer recommended)
dependencies:
freezed_annotation: ^2.4.1
dev_dependencies:
freezed: ^2.5.2~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.