[Open source, API, Dart, Flutter] Как перестать писать код для взаимодействия с бэкендом
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет! Начну с главного - я лентяй. Я очень-очень ленивый разработчик. Мне приходится писать много кода - как для бэка, так и для фронта. И моя лень постоянно терзает меня, говоря: Ты мог бы не писать этот код, а ты пишешь... Так и живем.Но что делать? Как можно избавиться от необходимости писать хотя бы часть кода?Есть много подходов к решению этой проблемы. Давайте посмотрим на некоторые из них.OpenAPIПредположим, ваш backend - это набор REST-сервисов. Первое, с чего стоило бы начать - изучить документацию вашего бэка в надежде наткнуться на спецификацию OpenAPI. Идеальной будет ситуация, когда ваш бэк предоставляет максимально полную спеку, в которой будут описаны все методы, которые используются клиентами, а также все передаваемые и получаемые данные и возможные ошибки. На самом деле, я пишу эти строки и думаю, что это само собой разумеющееся: кажется очевидным, что если ты разрабатываешь API, то должна быть и спецификация, причем не в виде простого перечисления методов, а максимально полная, и, самое главное - генерируемая из кода, а не написанная руками, но так дело обстоит далеко не везде, поэтому надо стремиться к лучшему.Ну ок, вот мы нашли нашу спеку, она полноценная, без темных пятен. Отлично - дело почти сделано. Теперь осталось использовать ее для достижения наших коварных целей. Так уж вышло, что я пишу еще и приложения на Flutter и в качестве клиента буду рассматривать именно его, но подход, применяемый тут - подойдет и для web-клиентов (да и для любых других тоже найдется что заюзать).Генерация по инициативе клиентаДумаю, не будет откровением, что магии то и нет. Чтобы фича появилась - все равно должен появиться некий код. И да, мы не будем его писать, но будет кодогенератор. И вот тут-то и начинается самое интересное. Есть библиотеки для Flutter (да и не только для него), которые сгенерируют код для работы с бэком исходя из аннотаций, которые вы можете накидать на псевдо-сервисы (которые вам все еще придется написать). Выглядит это примерно так:
import 'package:dio/dio.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';
part 'example.g.dart';
@RestApi(baseUrl: "https://5d42a6e2bc64f90014a56ca0.mockapi.io/api/v1/")
abstract class RestClient {
factory RestClient(Dio dio, {String baseUrl}) = _RestClient;
@GET("/tasks/{id}")
Future<Task> getTask(@Path("id") String id);
@GET('/demo')
Future<String> queries(@Queries() Map<String, dynamic> queries);
@GET("https://httpbin.org/get")
Future<String> namedExample(@Query("apikey") String apiKey, @Query("scope") String scope, @Query("type") String type, @Query("from") int from);
@PATCH("/tasks/{id}")
Future<Task> updateTaskPart(@Path() String id, @Body() Map<String, dynamic> map);
@PUT("/tasks/{id}")
Future<Task> updateTask(@Path() String id, @Body() Task task);
@DELETE("/tasks/{id}")
Future<void> deleteTask(@Path() String id);
@POST("/tasks")
Future<Task> createTask(@Body() Task task);
@POST("http://httpbin.org/post")
Future<void> createNewTaskFromFile(@Part() File file);
@POST("http://httpbin.org/post")
@FormUrlEncoded()
Future<String> postUrlEncodedFormData(@Field() String hello);
}
@JsonSerializable()
class Task {
String id;
String name;
String avatar;
String createdAt;
Task({this.id, this.name, this.avatar, this.createdAt});
factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);
Map<String, dynamic> toJson() => _$TaskToJson(this);
}
После запуска генератора мы получим рабочий сервис, готовый к использованию:
import 'package:logger/logger.dart';
import 'package:retrofit_example/example.dart';
import 'package:dio/dio.dart';
final logger = Logger();
void main(List<String> args) {
final dio = Dio(); // Provide a dio instance
dio.options.headers["Demo-Header"] = "demo header";
final client = RestClient(dio);
client.getTasks().then((it) => logger.i(it));
}
Данный способ (применимый на любых типах клиентов) может сэкономить вам немало времени и в случае, если ваш бэк не имеет нормальной OpenAPI схемы - то у вас то и не особо большой выбор, однако, если качественная схема есть, то по сравнению с тем способом генерации кода, о котором мы поговорим дальше у текущего варианта есть несколько недостатков:
- Вам все еще нужно писать код, меньше, чем раньше, но немало
- Вы должны самостоятельно отслеживать изменения в бэкенде и вслед за ними менять написанный вами код
На последнем пункте стоит остановиться немного подробнее - если (когда) произойдут изменения на бэке в методах, которые уже используются в вашем приложении - то вам нужно самостоятельно отслеживать эти изменения, дорабатывать модели DTO, и, возможно, endpoint'а. Также, если по какой-то невероятнейшей причине произойдут обратно-несовместимые изменения метода, то узнаете вы об этом только в рантайме (в момент вызова данного метода) - чего может не произойти во время разработки (особенно, если у вас нет или недостаточно тестов) и тогда у вас будет крайне неприятный баг в проде.Генерация без "тумана войны"Вы же еще не забыли, что у нас есть качественная OpenAPI-схема? Отлично! Все поле боя вам открыто и нет смысла идти наощупь (я добавил эту фразу, чтобы хоть как-то оправдать заголовок этого блока, придумывать которые, со скрипом, приходится самому; генерация тут не поможет). Тогда стоит обратить внимание на те инструменты, которые предлагает вся экосистема OpenAPI в принципе!Из всего многообразия молотков и микроскопов сейчас нас интересует всего один. И имя ему - OpenAPI Generator. Данный напильник позволяет генерировать код для любого языка (ну почти), а также - как для клиентов, так и для сервера (чтобы сделать mock-сервер, к примеру).Давайте уже перейдем к коду:В качестве схемы мы возьмем то, что предлагает демка Swagger. Затем, нам надо установить сам генератор. Вот прекрасное пособие для этого. Если вы читаете эту статью, то с высокой долей вероятности у вас уже установлена Node.js, а значит, одним из самых простых способов установки будет использование npm-версии.Следующий шаг - сама генерация. Есть парочка способов сделать это:
- Использование сугубо консольной команды
- Использование команды в сочетании с файлом конфигурации
На нашем примере 1й способ будет выглядить следующим образом:
openapi-generator-cli generate -i https://petstore.swagger.io/v2/swagger.json -g dart-dio -o .pet_api --additional-properties pubName=pet_api
Альтернативный способ - описание параметров в файле openapitools.json, например так:
{
"$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "5.1.1",
"generators": {
"pet": {
"input-spec": "https://petstore.swagger.io/v2/swagger.json",
"generator-name": "dart-dio",
"output": ".pet_api",
"additionalProperties": {
"pubName": "pet_api"
}
}
}
}
}
И последующий запуск команды:
openapi-generator-cli generate
Полный перечень доступных параметров для Dart представлен здесь. А для любого другого генератора список этих параметров можно узнать, выполнив следующую консольную команду:
# <generator-name>, dart-dio - for example
openapi-generator-cli config-help -g dart-dio
Даже если вы выберите полностью консольный вариант, после первого запуска генератора, у вас появится файл конфигурации с прописанной в нем версией используемого генератора, как в данном примере - 5.1.1. В случае с Dart / Flutter эта версия имеет весьма важное значение, так как каждая из них может нести определенные изменения, в том числе, с обратной несовместимостью или интересными эффектами.Так, начиная с версии 5.1.0 генератор использует null-safety, но реализует это посредством явных проверок, а не возможностей самого языка Dart (пока это так, к сожалению). К примеру - если в вашей схеме некоторые из полей модели размечены как обязательные, то если ваш бэкенд вернет модель без этого поля - то случится ошибка в рантайме.
flutter: Deserializing '[id, 9, category, {id: 0, name: cats}, photoUrls, [string], tags, [{id: 0, na...' to 'Pet' failed due to: Tried to construct class "Pet" with null field "name". This is forbidden; to allow it, mark "name" with @nullable.
А все из-за того, что поле name модели Pet явным образом указано, как обязательное, но отсутствует в ответе запроса:
{
"Pet": {
"type": "object",
"required": [
"name", // <- required field
"photoUrls" // <- and this too
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"category": {
"$ref": "#/definitions/Category"
},
"name": {
"type": "string",
"example": "doggie"
},
"photoUrls": {
"type": "array",
"xml": {
"wrapped": true
},
"items": {
"type": "string",
"xml": {
"name": "photoUrl"
}
}
},
"tags": {
"type": "array",
"xml": {
"wrapped": true
},
"items": {
"xml": {
"name": "tag"
},
"$ref": "#/definitions/Tag"
}
},
"status": {
"type": "string",
"description": "pet status in the store",
"enum": [
"available",
"pending",
"sold"
]
}
},
"xml": {
"name": "Pet"
}
},
"Category": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
}
},
"xml": {
"name": "Category"
}
},
"Tag": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
}
},
"xml": {
"name": "Tag"
}
}
}
Что же, генератор запущен - дело сделано и осталось несколько несложных шагов, в которых нам почти не придется писать код (ради этого же все затевалось!). Стандартный openapi-generator сгенерирует только базовый код, в котором используются библиотеки, полагающиеся уже на кодогенерацию средствами самого Dart. Поэтому, после завершения базовой генерации необходимо запустить и Dart генератор:
cd .pet_api
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
На выходе мы получаем готовый пакет, который будет располагаться там, где вы указали в файле конфигурации или консольной команде. Осталось включить его в pubspec.yaml:
name: openapi_sample
description: Sample for OpenAPI
version: 1.0.0
publish_to: none
environment:
flutter: ">=2.0.0"
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
pet_api: # <- our generated library
path: .pet_api
И использовать данную библиотеку следующим образом:
import 'package:dio/dio.dart';
import 'package:pet_api/api/pet_api.dart';
import 'package:pet_api/model/pet.dart';
import 'package:pet_api/serializers.dart'; // <- we must use [standartSerializers] from this package module
Future<Pet> loadPet() async {
final Dio dio = Dio(BaseOptions(baseUrl: 'https://petstore.swagger.io/v2'));
final PetApi petApi = PetApi(dio, standardSerializers);
const petId = 9;
final Response<Pet> response = await petApi.getPetById(petId, headers: <String, String>{'Authorization': 'Bearer special-key'});
return response.data;
}
Из важного в ней - необходимость прописать, какие мы будем использовать сериализаторы (standartSerializers) для того, чтобы JSON'ы превращались в нормальные модели. А также прокидывать инстансы Dio в сгенерированные ...Api, указывая в них базовые урлы серверов.Нюансы DartВроде бы и все, что можно сказать по этой теме, но Dart не так давно получил крупное обновление, в нем появилась null-safety. И все пакеты активно обновляются, а проекты мигрируют на новую версию языка, более устойчивую к ошибкам нашим. Однако, на данный момент, генератор не поддерживает эту новую версию, причем - сразу по нескольким направлениям:
- Версия языка в пакете (в последней версии генератора - 5.1.1, используется Dart 2.7.0)
- Устаревшие пакеты
- Обратная несовместимость некоторых из используемых пакетов (в актуальной версии Dio некоторые методы имеют другие названия и много чего еще)
name: pet_api
version: 1.0.0
description: OpenAPI API client
environment:
sdk: '>=2.7.0 <3.0.0' # -> '>=2.12.0 <3.0.0'
dependencies:
dio: '^3.0.9' # Actual -> 4.0.0
built_value: '>=7.1.0 <8.0.0' # -> 8.1.0
built_collection: '>=4.3.2 <5.0.0' # -> 5.1.0
dev_dependencies:
built_value_generator: '>=7.1.0 <8.0.0' # -> 8.1.0
build_runner: any # -> 2.0.5
test: '>=1.3.0 <1.16.0' # -> 1.17.9
И это может доставлять сразу несколько проблем - если вы уже перешли на Flutter 2.0+ и Dart 2.12+, то, чтобы запустить кодогенерацию второго этапа (которая на Dart) - вам придется переключать язык на старую версию, FVM позволяет это сделать довольно быстро, но это все равно неудобство.Второй минус заключается в том, что данный сгенированный api-пакет теперь является legacy-зависимостью, что не позволит запустить ваш новый проект с sound-null-safety. Вы сможете использовать преимущества null-safety при написании кода, но рантайм проверки и оптимизации вам будут недоступны, а проект будет работоспособен только при использовании дополнительного параметра Flutter: --no-sound-null-safety.Варианта исправления этой ситуации три:
- Сделать pull-request, с обновлением openapi-generator
- Дождаться, пока это сделает кто-то другой, через пол года это, скорее всего, случится
- Исправить сгенерированный код, чтобы он уже сейчас стал sound-null-safety
Третий пункт звучит так, будто нам придется писать код... Немного придется, но не тот.До начала наших манипуляций покажу вам bash-скрипт, который получился на данный момент и который запускает всю нашу логику генерации кода:
openapi-generator-cli generate
cd .pet_api || exit
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
Данный скрипт полагается и на файл конфигурации, который мы обсуждали выше. Давайте дополним этот скрипт, чтобы он сразу же и обновлял все зависимости нашего сгенированного пакета:
openapi-generator-cli generate
cd .pet_api || exit
echo "name: pet_api
version: 1.0.0
description: OpenAPI API client
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
dio: ^4.0.0 built_value: ^8.1.0 built_collection: ^5.1.0
dev_dependencies:
built_value_generator: ^8.1.0 build_runner: ^2.0.5 test: ^1.17.9" > pubspec.yaml
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
Теперь - наш генератор корректно запустится и с новой версией Dart (>2.12.0) в системе. Все бы ничего, но использовать наш api-пакет по прежнему не получится! Во первых - сгенерированный код изобилует аннотациями, привязывающими его к старой версии языка:
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.7 <--
// ignore_for_file: unused_import
А во вторых - есть обратная несовместимость в логике Dio и пакетов, которые используются для сериализации / десериализации моделей. Давайте исправим это! Для исправления нам потребуется написать совсем немного утилитарного кода, который будет исправлять несовместимости, которые появятся в нашем сгенерированном коде. Я упоминал выше, что советовал бы ставить генератор посредством npm, как самого простого способа, если у вас есть Node.js, соответственно, по инерции - и утилитарный код будет написан на JS. При желании его несложно переписать на Dart, если у вас нет Node.js и нет желания с ней связываться.Давайте взглянем на эти нехитрые манипуляции:
const fs = require('fs');
const p = require('path');
const dartFiles = [];
function main() {
const openapiDirPath = p.resolve(__dirname, '.pet_api');
searchDartFiles(openapiDirPath);
for (const filePath of dartFiles) {
fixFile(filePath);
console.log('Fixed file:', filePath);
}
}
function searchDartFiles(path) {
const isDir = fs.lstatSync(path).isDirectory();
if (isDir) {
const dirContent = fs.readdirSync(path);
for (const dirContentPath of dirContent) {
const fullPath = p.resolve(path, dirContentPath);
searchDartFiles(fullPath);
}
} else {
if (path.includes('.dart')) {
dartFiles.push(path);
}
}
}
function fixFile(path) {
const fileContent = fs.readFileSync(path).toString();
const fixedContent = fixOthers(fileContent);
fs.writeFileSync(path, fixedContent);
}
const fixOthers = fileContent => {
let content = fileContent;
for (const entry of otherFixers.entries()) {
content = content.replace(entry[0], entry[1]);
}
return content;
};
const otherFixers = new Map([
// ? Base fixers for Dio and standard params
[
'// @dart=2.7',
'// ',
],
[
/response\.request/gm,
'response.requestOptions',
],
[
/request: /gm,
'requestOptions: ',
],
[
/Iterable<Object> serialized/gm,
'Iterable<Object?> serialized',
],
[
/(?<type>^ +Uint8List)(?<value> file,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +String)(?<value> additionalMetadata,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +ProgressCallback)(?<value> onReceiveProgress,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +ProgressCallback)(?<value> onSendProgress,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +ValidateStatus)(?<value> validateStatus,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +Map<String, dynamic>)(?<value> extra,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +Map<String, dynamic>)(?<value> headers,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +CancelToken)(?<value> cancelToken,)/gm,
'$<type>?$<value>',
],
[
/(@nullable\n)(?<annotation>^ +@.*\n)(?<type>.*)(?<getter> get )(?<variable>.*\n)/gm,
'$<annotation>$<spaces>$<type>?$<getter>$<variable>',
],
[
'final result = <Object>[];',
'final result = <Object?>[];',
],
[
'Iterable<Object> serialize',
'Iterable<Object?> serialize',
],
[
/^ *final _response = await _dio.request<dynamic>\(\n +_request\.path,\n +data: _bodyData,\n +options: _request,\n +\);/gm,
`_request.data = _bodyData;
final _response = await _dio.fetch<dynamic>(_request);
`,
],
// ? Special, custom params for concrete API
[
/(?<type>^ +String)(?<value> apiKey,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +String)(?<value> name,)/gm,
'$<type>?$<value>',
],
[
/(?<type>^ +String)(?<value> status,)/gm,
'$<type>?$<value>',
],
]);
main();
Включим использование данного скрипта после openapi-генератора и до Dart-генератора:
rm -rf ".pet_api" || echo ".pet_api folder not found"
openapi-generator-cli generate
cd .pet_api || exit
echo "name: pet_api
version: 1.0.0
description: OpenAPI API client
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
dio: ^4.0.0
built_value: ^8.1.0
built_collection: ^5.1.0
dev_dependencies:
built_value_generator: ^8.1.0
build_runner: ^2.0.5
test: ^1.17.9
" > pubspec.yaml
node ../openapi_updater.js # <--
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
Теперь все готово! Большая часть всех этих регулярок исправляют базовую логику сгенерированного кода, однако есть и тройка кастомных, которые нужны для конкретного API. В каждом конкретном будут свои кастомные регулярки, но очень вероятно, что добавить их не составит большого труда, а все базовые будут работать на любом API.ВыводыПодход к генерации клиентского кода, при наличии качественной OpenAPI схемы является крайне простой задачей, вне зависимости от языка клиента. В случае Dart - все еще есть определенные неудобства, вызванные, сугубо, переходным периодом на null-safety. Но в рамках данной заметки мы успешно преодолели все неурядицы и получили полностью работоспособную библиотеку для работы с бэкендом, зависимости которой (и сама она) обновлены до самой новой версии и могут быть использованными в проекте на Flutter с sound-null-safety без каких-либо ограничений.Дополнительный плюс подхода, когда источником истины является именно схема - в случае ее изменения с потерей обратной совместимости наш сгенерированный код тут же отреагирует на это и покажет все ошибки на этапе статического анализа что убережет ваши нервишки от отлова багов в рантайме.Также, есть и другие способы, которые позволят не писать то, что можно не писать. А тем временем, весь код из статьи с рабочими заплатками, позволяющими использовать генератор с null-safety уже сейчас можно найти тут.
===========
Источник:
habr.com
===========
Похожие новости:
- [Open source, Программирование, Учебный процесс в IT] Как я учил студентов Северной Кореи разрабатывать ПО с открытым исходным кодом (перевод)
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android, Swift] Онлайн-митап DevDay Mobile: C++ -> Swift, скрытый API Android и будни разработчика
- [Open source, C++, Qt, Софт] Haiku, Inc. проспонсировала приобретение RISC-V материнских плат для портирования системы Haiku
- [Программирование, Разработка игр, Интерфейсы, Яндекс API, Голосовые интерфейсы] Лучшие навыки Алисы и советы от их разработчиков
- [Open source, Системы сборки, DevOps, Kubernetes] werf vs Docker. Чем лучше собирать образы
- [Python, API] Сохраняем комментарии youtube в csv
- [API, Разработка под e-commerce, Управление e-commerce] Как продавать на маркетплейсе, не вздрагивая при слове «инфообмен»
- [Open source, Разработка игр, HTML, Дизайн игр, DIY или Сделай сам] Что случилось с игрой «Колобок» в июне
- [Информационная безопасность, Разработка веб-сайтов, API, Тестирование веб-сервисов] Nemesida WAF 2021: защита сайтов и API от хакерских атак
- [Laravel] Проверка тестов PHP API на соответствие определениям OpenAPI — пример Laravel
Теги для поиска: #_open_source, #_api, #_dart, #_flutter, #_dart, #_flutter, #_api, #_openapi, #_swagger, #_kodogeneratsija (кодогенерация), #_open_source, #_api, #_dart, #_flutter
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:29
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет! Начну с главного - я лентяй. Я очень-очень ленивый разработчик. Мне приходится писать много кода - как для бэка, так и для фронта. И моя лень постоянно терзает меня, говоря: Ты мог бы не писать этот код, а ты пишешь... Так и живем.Но что делать? Как можно избавиться от необходимости писать хотя бы часть кода?Есть много подходов к решению этой проблемы. Давайте посмотрим на некоторые из них.OpenAPIПредположим, ваш backend - это набор REST-сервисов. Первое, с чего стоило бы начать - изучить документацию вашего бэка в надежде наткнуться на спецификацию OpenAPI. Идеальной будет ситуация, когда ваш бэк предоставляет максимально полную спеку, в которой будут описаны все методы, которые используются клиентами, а также все передаваемые и получаемые данные и возможные ошибки. На самом деле, я пишу эти строки и думаю, что это само собой разумеющееся: кажется очевидным, что если ты разрабатываешь API, то должна быть и спецификация, причем не в виде простого перечисления методов, а максимально полная, и, самое главное - генерируемая из кода, а не написанная руками, но так дело обстоит далеко не везде, поэтому надо стремиться к лучшему.Ну ок, вот мы нашли нашу спеку, она полноценная, без темных пятен. Отлично - дело почти сделано. Теперь осталось использовать ее для достижения наших коварных целей. Так уж вышло, что я пишу еще и приложения на Flutter и в качестве клиента буду рассматривать именно его, но подход, применяемый тут - подойдет и для web-клиентов (да и для любых других тоже найдется что заюзать).Генерация по инициативе клиентаДумаю, не будет откровением, что магии то и нет. Чтобы фича появилась - все равно должен появиться некий код. И да, мы не будем его писать, но будет кодогенератор. И вот тут-то и начинается самое интересное. Есть библиотеки для Flutter (да и не только для него), которые сгенерируют код для работы с бэком исходя из аннотаций, которые вы можете накидать на псевдо-сервисы (которые вам все еще придется написать). Выглядит это примерно так: import 'package:dio/dio.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:retrofit/retrofit.dart'; part 'example.g.dart'; @RestApi(baseUrl: "https://5d42a6e2bc64f90014a56ca0.mockapi.io/api/v1/") abstract class RestClient { factory RestClient(Dio dio, {String baseUrl}) = _RestClient; @GET("/tasks/{id}") Future<Task> getTask(@Path("id") String id); @GET('/demo') Future<String> queries(@Queries() Map<String, dynamic> queries); @GET("https://httpbin.org/get") Future<String> namedExample(@Query("apikey") String apiKey, @Query("scope") String scope, @Query("type") String type, @Query("from") int from); @PATCH("/tasks/{id}") Future<Task> updateTaskPart(@Path() String id, @Body() Map<String, dynamic> map); @PUT("/tasks/{id}") Future<Task> updateTask(@Path() String id, @Body() Task task); @DELETE("/tasks/{id}") Future<void> deleteTask(@Path() String id); @POST("/tasks") Future<Task> createTask(@Body() Task task); @POST("http://httpbin.org/post") Future<void> createNewTaskFromFile(@Part() File file); @POST("http://httpbin.org/post") @FormUrlEncoded() Future<String> postUrlEncodedFormData(@Field() String hello); } @JsonSerializable() class Task { String id; String name; String avatar; String createdAt; Task({this.id, this.name, this.avatar, this.createdAt}); factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json); Map<String, dynamic> toJson() => _$TaskToJson(this); } import 'package:logger/logger.dart';
import 'package:retrofit_example/example.dart'; import 'package:dio/dio.dart'; final logger = Logger(); void main(List<String> args) { final dio = Dio(); // Provide a dio instance dio.options.headers["Demo-Header"] = "demo header"; final client = RestClient(dio); client.getTasks().then((it) => logger.i(it)); }
openapi-generator-cli generate -i https://petstore.swagger.io/v2/swagger.json -g dart-dio -o .pet_api --additional-properties pubName=pet_api
{
"$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", "spaces": 2, "generator-cli": { "version": "5.1.1", "generators": { "pet": { "input-spec": "https://petstore.swagger.io/v2/swagger.json", "generator-name": "dart-dio", "output": ".pet_api", "additionalProperties": { "pubName": "pet_api" } } } } } openapi-generator-cli generate
# <generator-name>, dart-dio - for example
openapi-generator-cli config-help -g dart-dio flutter: Deserializing '[id, 9, category, {id: 0, name: cats}, photoUrls, [string], tags, [{id: 0, na...' to 'Pet' failed due to: Tried to construct class "Pet" with null field "name". This is forbidden; to allow it, mark "name" with @nullable.
{
"Pet": { "type": "object", "required": [ "name", // <- required field "photoUrls" // <- and this too ], "properties": { "id": { "type": "integer", "format": "int64" }, "category": { "$ref": "#/definitions/Category" }, "name": { "type": "string", "example": "doggie" }, "photoUrls": { "type": "array", "xml": { "wrapped": true }, "items": { "type": "string", "xml": { "name": "photoUrl" } } }, "tags": { "type": "array", "xml": { "wrapped": true }, "items": { "xml": { "name": "tag" }, "$ref": "#/definitions/Tag" } }, "status": { "type": "string", "description": "pet status in the store", "enum": [ "available", "pending", "sold" ] } }, "xml": { "name": "Pet" } }, "Category": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" } }, "xml": { "name": "Category" } }, "Tag": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" } }, "xml": { "name": "Tag" } } } cd .pet_api
flutter pub get flutter pub run build_runner build --delete-conflicting-outputs name: openapi_sample
description: Sample for OpenAPI version: 1.0.0 publish_to: none environment: flutter: ">=2.0.0" sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter pet_api: # <- our generated library path: .pet_api import 'package:dio/dio.dart';
import 'package:pet_api/api/pet_api.dart'; import 'package:pet_api/model/pet.dart'; import 'package:pet_api/serializers.dart'; // <- we must use [standartSerializers] from this package module Future<Pet> loadPet() async { final Dio dio = Dio(BaseOptions(baseUrl: 'https://petstore.swagger.io/v2')); final PetApi petApi = PetApi(dio, standardSerializers); const petId = 9; final Response<Pet> response = await petApi.getPetById(petId, headers: <String, String>{'Authorization': 'Bearer special-key'}); return response.data; }
name: pet_api
version: 1.0.0 description: OpenAPI API client environment: sdk: '>=2.7.0 <3.0.0' # -> '>=2.12.0 <3.0.0' dependencies: dio: '^3.0.9' # Actual -> 4.0.0 built_value: '>=7.1.0 <8.0.0' # -> 8.1.0 built_collection: '>=4.3.2 <5.0.0' # -> 5.1.0 dev_dependencies: built_value_generator: '>=7.1.0 <8.0.0' # -> 8.1.0 build_runner: any # -> 2.0.5 test: '>=1.3.0 <1.16.0' # -> 1.17.9
openapi-generator-cli generate
cd .pet_api || exit flutter pub get flutter pub run build_runner build --delete-conflicting-outputs openapi-generator-cli generate
cd .pet_api || exit echo "name: pet_api version: 1.0.0 description: OpenAPI API client environment: sdk: '>=2.12.0 <3.0.0' dependencies: dio: ^4.0.0 built_value: ^8.1.0 built_collection: ^5.1.0 dev_dependencies: built_value_generator: ^8.1.0 build_runner: ^2.0.5 test: ^1.17.9" > pubspec.yaml flutter pub get flutter pub run build_runner build --delete-conflicting-outputs //
// AUTO-GENERATED FILE, DO NOT MODIFY! // // @dart=2.7 <-- // ignore_for_file: unused_import const fs = require('fs');
const p = require('path'); const dartFiles = []; function main() { const openapiDirPath = p.resolve(__dirname, '.pet_api'); searchDartFiles(openapiDirPath); for (const filePath of dartFiles) { fixFile(filePath); console.log('Fixed file:', filePath); } } function searchDartFiles(path) { const isDir = fs.lstatSync(path).isDirectory(); if (isDir) { const dirContent = fs.readdirSync(path); for (const dirContentPath of dirContent) { const fullPath = p.resolve(path, dirContentPath); searchDartFiles(fullPath); } } else { if (path.includes('.dart')) { dartFiles.push(path); } } } function fixFile(path) { const fileContent = fs.readFileSync(path).toString(); const fixedContent = fixOthers(fileContent); fs.writeFileSync(path, fixedContent); } const fixOthers = fileContent => { let content = fileContent; for (const entry of otherFixers.entries()) { content = content.replace(entry[0], entry[1]); } return content; }; const otherFixers = new Map([ // ? Base fixers for Dio and standard params [ '// @dart=2.7', '// ', ], [ /response\.request/gm, 'response.requestOptions', ], [ /request: /gm, 'requestOptions: ', ], [ /Iterable<Object> serialized/gm, 'Iterable<Object?> serialized', ], [ /(?<type>^ +Uint8List)(?<value> file,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +String)(?<value> additionalMetadata,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +ProgressCallback)(?<value> onReceiveProgress,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +ProgressCallback)(?<value> onSendProgress,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +ValidateStatus)(?<value> validateStatus,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +Map<String, dynamic>)(?<value> extra,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +Map<String, dynamic>)(?<value> headers,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +CancelToken)(?<value> cancelToken,)/gm, '$<type>?$<value>', ], [ /(@nullable\n)(?<annotation>^ +@.*\n)(?<type>.*)(?<getter> get )(?<variable>.*\n)/gm, '$<annotation>$<spaces>$<type>?$<getter>$<variable>', ], [ 'final result = <Object>[];', 'final result = <Object?>[];', ], [ 'Iterable<Object> serialize', 'Iterable<Object?> serialize', ], [ /^ *final _response = await _dio.request<dynamic>\(\n +_request\.path,\n +data: _bodyData,\n +options: _request,\n +\);/gm, `_request.data = _bodyData; final _response = await _dio.fetch<dynamic>(_request); `, ], // ? Special, custom params for concrete API [ /(?<type>^ +String)(?<value> apiKey,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +String)(?<value> name,)/gm, '$<type>?$<value>', ], [ /(?<type>^ +String)(?<value> status,)/gm, '$<type>?$<value>', ], ]); main(); rm -rf ".pet_api" || echo ".pet_api folder not found"
openapi-generator-cli generate cd .pet_api || exit echo "name: pet_api version: 1.0.0 description: OpenAPI API client environment: sdk: '>=2.12.0 <3.0.0' dependencies: dio: ^4.0.0 built_value: ^8.1.0 built_collection: ^5.1.0 dev_dependencies: built_value_generator: ^8.1.0 build_runner: ^2.0.5 test: ^1.17.9 " > pubspec.yaml node ../openapi_updater.js # <-- flutter pub get flutter pub run build_runner build --delete-conflicting-outputs =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:29
Часовой пояс: UTC + 5