Fantom
Fantom is a cli tool for generating API layer in dart language based on OpenAPI Spec.
With fantom you can simply take an openapi file and generate all the code you need for your api. we do not impose
Get Started Quickly
- Install
fantom
$ dart pub global activate fantom
- Generate API client
Result: it will generate models in
$ fantom generate openapi.yaml --dir=lib/src/network
lib/src/network/model
directory and APIs inlib/src/network/api
directory
NOTE: you're openapi file can be in json or yaml format.
More Advanced Options
-
Separate models and APIs directories
$ fantom generate openapi.yaml --model-dir=lib/src/model --api-dir=lib/src/network
-
Generate API layer as a standalone dart package
$ fantom generate openapi.yaml --package=packages/network
Note: it will generates a package called
network
insidepackages/network
from wherefantom generate
command runs.
Use a config file instead of cli
With fantom you can define all youre configuration once inside a fantom.yaml
file or your project's pubspec.yaml
file once and then every time you want to generate you're network client code you simply just run below commands
- Define configs inside project's
pubspec.yaml
or create afantom.yaml
and run command
$ fantom generate
NOTE: you can use a any yaml (or json) file to define you're fantom config in then run generate command with path to file
$ fantom generate my-config.yaml
Here is an example of fantom config file
- Simple fantom configuration
fantom:
openapi: path/to/openapi/file
api-dir: packages/network/lib
model-dir: packages/network/lib/models
package-name: network
- Fantom config for standalone network client package
fantom:
openapi: path/to/openapi/file
package: path/to/export/package/
package-name: my_network_client
recreate-package: false # by default is true
by default recreate-package
is set to true. which deletes the old generated network client package completely each time you run generate
command.
if you set it to false
only the generated files inside the lib
directory of the
generated package will be removed and new one will replace them
NOTE: fantom uses the file header in generated files to recognize they are generated by fantom. same recognision method is used when deleting generated files, so try not to edit the file header by hand.
Side Note: fantom almost everywhere uses the same method to read json
or yaml
files
the file name and extension is irrelevent to fantom just the file content should be in valid format whether it is json
or yaml
. you can even write your fantom config in json format if you need to
Read openapi file from URL
With fantom it is possible to path a url as the source of openapi
file.
NOTE: you must also provide a path to store the downloaded file
here is how your fantom config will look like
fantom:
openapi: https://myopenapi.com/source/of/openapi
download-path: where/to/save/openapi.json
api-dir: packages/network/lib
model-dir: packages/network/lib/models
package-name: network
Exclude Paths and Components
you can exclude some paths and components from being generated by fantom cli. what happens is fantom will read the openapi files and then remove the paths and components you excluded so they won't be generated.
you can add the components and path you want to your fantom config to be excluded like below example
fantom:
openapi: openapi_files/petstore.openapi.json
package: path/to/package/
package-name: my_api_package
excluded-paths:
- /pet -- [get, post] # only exclude get & post operation
- /pet/findByStatus # when no operation specified everything gets excluded
excluded-components:
- components/schemas/Shalgham
Use the Generated code
Create an instance of the api class
import 'package:network/api.dart';
void createApiInstance(){
final dio = provideDio();
final api = FantomApi(dio);
}
Fantom uses Dio
and needs an instance of Dio
to operate. Note that we do not
impose any custom or extra configurations to the Dio
instance provided. so you can be sure you're in control of how you configure you're Dio
instance
Understand Api method options
A Fantom generated api method will have extra parameters to give you more ability to customize the request you want to send
Here is an example of an api method generated
Future<Result<ChatMessageListWrapper, Exception>> getMessages({
...
// define custom content type for request
String? contentType,
// define custom content type for response
String? responseContentType,
// useCompute will make deserialization to run on a different Isolate
bool useCompute = false,
// add new or override headers
Map<String, String>? extraHeaders,
// path a CancelToken so you will be able to cancel request
CancelToken? cancelToken,
}) async {
Understand return type Result
Generated api methods will have a return type of Future<Result<MODEL,Exception>>
if the return type specified in openapi file is MODEL
.
a Result
object allows you to check whether the request was successful or not
you can do that simply by
final result = await fantomApi.userApi.getUserProfile();
if (result.isSuccessful) {
// show profile
} else {
// show error
}
Set Exception Mapper
Fantom provide exception mapping. this feature is very usefull to map network exceptions to something that can be easily used and understood by your logic or ui layer
FantomExceptionMapping.setMapper((final Exception e, stacktrace) {
_log(e);
_log(stacktrace);
if (e is SocketException) {
return MyCustomError(S.current.noInternet, exception: e);
}
if (e is FormatException) {
return MyCustomError(S.current.somethingWentWrongTryAgain, exception: e);
}
if (e is DioError) {
// return something else
}
});
Custom request body
Fantom provides each method that requires a body to be completely customzied. there are times that you need to completely customize the request body. Fantom provides this option. here is an example
final customDioRequestBody = SetPaymentRequestBody.custom({
"method": 'credit_card',
"price": {"amount": price, "currency": "USD"},
});
final result = await fantomApi.paymentApi.setPayment(
rideCode: task.ride.code,
body: body,
);
Another example
final pictureUploadingResult = await fantomApi.userApi.uploadAvatar(
body: UploadAvatarRequestBody.custom(
FormData.fromMap(
{'avatar': MultipartFile.fromFileSync(avatarFilePath)},
),
),
extraHeaders: {
"Authorization": "Bearer ${_currentAccessToken}",
},
);