Skip to content

Commit

Permalink
Add support for relative base path in @RESTapi (#579)
Browse files Browse the repository at this point in the history
* feat: Add support for relative baseUrl in RestApi

* docs: Add example project for relative baseUrl in RestApi
  • Loading branch information
KernelPanic92 authored Jun 20, 2023
1 parent 7f5c050 commit 0e51fd2
Show file tree
Hide file tree
Showing 11 changed files with 384 additions and 3 deletions.
5 changes: 5 additions & 0 deletions example_relative_base_url/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include: package:lints/recommended.yaml
analyzer:
exclude:
- lib/**/*.reflectable.dart
- lib/**/*.g.dart
6 changes: 6 additions & 0 deletions example_relative_base_url/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
targets:
$default:
builders:
reflectable:
generate_for:
- lib/json_mapper_example.dart
32 changes: 32 additions & 0 deletions example_relative_base_url/lib/api_result.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:json_annotation/json_annotation.dart';

part 'api_result.g.dart';

@JsonSerializable(genericArgumentFactories: true)
class ApiResult<T> {
const ApiResult({
required this.code,
required this.data,
this.msg,
});

factory ApiResult.fromJson(
Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
_$ApiResultFromJson(json, fromJsonT);

///接口调用成功的code码
static const success = 0;
static const unknown = -1;
final int code;
final T data;
final String? msg;

///业务接口执行成功
bool get isSuccess => code == success;

Map<String, dynamic> toJson(Object? Function(T) toJsonT) =>
_$ApiResultToJson(this, toJsonT);

@override
String toString() => 'ApiResult{code: $code, data: $data, msg: $msg}';
}
119 changes: 119 additions & 0 deletions example_relative_base_url/lib/example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart' hide Headers;
import 'package:http_parser/http_parser.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';
import 'package:retrofit_example/api_result.dart';

part 'example.g.dart';

@RestApi(baseUrl: 'tasks')
abstract class TasksRestClient {
factory TasksRestClient(Dio dio, {String baseUrl}) = _TasksRestClient;

@GET('/tasks/{id}')
Future<List<Task?>> getTaskById();

@GET('/')
Future<List<Task>> getTasks();

@GET('/{id}')
Future<Task> getTask(@Path('id') String id);

@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('/tasks')
Future<List<Task>> createTasks(@Body() List<Task> tasks);
}

@JsonSerializable()
class Task {
const Task({
required this.id,
required this.name,
required this.avatar,
required this.createdAt,
});

factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);

final String id;
final String name;
final String avatar;
final String createdAt;

Map<String, dynamic> toJson() => _$TaskToJson(this);
}

// ignore_for_file: constant_identifier_names
enum Status {
@JsonValue('new')
New,
@JsonValue('on_going')
OnGoing,
@JsonValue('closed')
Closed,
}

@JsonSerializable()
class TaskQuery {
const TaskQuery(this.statuses);

factory TaskQuery.fromJson(Map<String, dynamic> json) =>
_$TaskQueryFromJson(json);

final List<Status> statuses;

Map<String, dynamic> toJson() => _$TaskQueryToJson(this);
}

@JsonSerializable()
class TaskGroup {
const TaskGroup({
required this.date,
required this.todos,
required this.completed,
required this.inProgress,
});

factory TaskGroup.fromJson(Map<String, dynamic> json) =>
_$TaskGroupFromJson(json);

final DateTime date;
final List<Task> todos;
final List<Task> completed;
final List<Task> inProgress;

Map<String, dynamic> toJson() => _$TaskGroupToJson(this);
}

@JsonSerializable(genericArgumentFactories: true)
class ValueWrapper<T> {
const ValueWrapper({required this.value});

factory ValueWrapper.fromJson(
Map<String, dynamic> json,
T Function(Object? json) fromJsonT,
) =>
_$ValueWrapperFromJson(json, fromJsonT);

final T value;

Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
_$ValueWrapperToJson(this, toJsonT);
}
7 changes: 7 additions & 0 deletions example_relative_base_url/mono_pkg.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dart:
- dev

stages:
# Register two jobs to run under the `analyze` stage.
- analyze:
- dartanalyzer
28 changes: 28 additions & 0 deletions example_relative_base_url/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: retrofit_example
description: Retrofit generator
version: 1.0.0

environment:
sdk: '>=2.18.0 <3.0.0'

dependencies:
retrofit:
json_annotation:
logger: ^1.2.2
dio: ^5.0.1
http_parser:

dev_dependencies:
test: ^1.23.1
retrofit_generator:
build_runner: ^2.3.3
json_serializable: ^6.6.1
mock_web_server: ^5.0.0-nullsafety.1
lints: ^2.0.1

dependency_overrides:
retrofit:
path: ../retrofit
retrofit_generator:
path: ../generator
source_gen: ^1.2.7
104 changes: 104 additions & 0 deletions example_relative_base_url/test/example_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'package:dio/dio.dart';
import 'package:mock_web_server/mock_web_server.dart';
import 'package:retrofit_example/example.dart';
import 'package:test/test.dart';

import 'task_data.dart';

late MockWebServer _server;
late TasksRestClient _client;
final _headers = {'Content-Type': 'application/json'};
final dispatcherMap = <String, MockResponse>{};

void main() {
setUp(() async {
_server = MockWebServer();
await _server.start();
final dio = Dio(BaseOptions(baseUrl: _server.url));
dio.interceptors.add(LogInterceptor(responseBody: true));
dio.interceptors.add(DateTimeInterceptor());
_client = TasksRestClient(dio);
});

tearDown(() {
_server.shutdown();
});

test('test empty task list', () async {
_server.enqueue(
body: demoEmptyListJson, headers: {'Content-Type': 'application/json'});
final tasks = await _client.getTasks();
expect(tasks, isNotNull);
expect(tasks.length, 0);
});

test('test task list', () async {
_server.enqueue(body: demoTaskListJson, headers: _headers);
final tasks = await _client.getTasks();
expect(tasks, isNotNull);
expect(tasks.length, 1);
});

test('test task detail', () async {
_server.enqueue(headers: _headers, body: demoTaskJson);
final task = await _client.getTask('id');
expect(task, isNotNull);
expect(task.id, demoTask.id);
expect(task.avatar, demoTask.avatar);
expect(task.name, demoTask.name);
expect(task.createdAt, demoTask.createdAt);
});

test('create new task', () async {
_server.enqueue(headers: _headers, body: demoTaskJson);
final task = await _client.createTask(demoTask);
expect(task, isNotNull);
expect(task.id, demoTask.id);
expect(task.avatar, demoTask.avatar);
expect(task.name, demoTask.name);
expect(task.createdAt, demoTask.createdAt);
});

test('update task all content', () async {
_server.enqueue(headers: _headers, body: demoTaskJson);
final task = await _client.updateTask('id', demoTask);
expect(task, isNotNull);
expect(task.id, demoTask.id);
expect(task.avatar, demoTask.avatar);
expect(task.name, demoTask.name);
expect(task.createdAt, demoTask.createdAt);
});

test('update task part content', () async {
_server.enqueue(headers: _headers, body: demoTaskJson);
final task = await _client
.updateTaskPart('id', <String, String>{'name': 'demo name 2'});
expect(task, isNotNull);
expect(task.id, demoTask.id);
expect(task.avatar, demoTask.avatar);
expect(task.name, demoTask.name);
expect(task.createdAt, demoTask.createdAt);
});

test('delete a task', () async {
_server.enqueue();
await _client.deleteTask('id').then((it) {
expect(null, null);
});
});
}

class DateTimeInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.queryParameters = options.queryParameters.map((key, value) {
if (value is DateTime) {
//may be change to string from any you use object
return MapEntry(key, value.toString());
} else {
return MapEntry(key, value);
}
});
handler.next(options);
}
}
27 changes: 27 additions & 0 deletions example_relative_base_url/test/task_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'dart:convert';

import 'package:retrofit_example/example.dart';

final demoTask = Task(
id: '123455151',
name: 'demo task',
avatar:
'https://p7.hiclipart.com/preview/312/283/679/avatar-computer-icons-user-profile-business-user-avatar.jpg',
createdAt: '2017/09/08 21:35:19',
);

final demoTaskJson = jsonEncode(demoTask);
final List<Task> demoTaskList = [demoTask];
final demoTaskListJson = jsonEncode(demoTaskList);
final List<Task> demoEmptyList = [];
final demoEmptyListJson = jsonEncode(demoEmptyList);

final groupTask = TaskGroup(
date: DateTime.now(),
todos: demoTaskList,
completed: demoTaskList,
inProgress: demoEmptyList,
);

final groupTaskList = [groupTask];
final groupTaskListJson = jsonEncode(groupTaskList);
Loading

0 comments on commit 0e51fd2

Please sign in to comment.