Skip to content

Commit 73e7bcd

Browse files
create models for first request
1 parent 0536f5e commit 73e7bcd

File tree

10 files changed

+258
-8
lines changed

10 files changed

+258
-8
lines changed

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ doc/api/
2020
*.js.deps
2121
*.js.map
2222

23-
# Generated files
24-
*.chopper.dart
23+
# Generated files
24+
*.chopper.dart
25+
*.freezed.dart

analysis_options.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ include: package:lint/analysis_options_package.yaml
77
# rules:
88
# - camel_case_types
99

10-
# analyzer:
11-
# exclude:
12-
# - path/to/excluded/files/**
10+
analyzer:
11+
exclude:
12+
- '**/*.chopper.dart'
13+
- '**/*.freezed.dart'

example/aha_client_example.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,31 @@
1-
void main() {}
1+
import 'dart:io';
2+
3+
import 'package:aha_client/src/api/aha_client.dart';
4+
5+
class _AhaClientHttpOverrides extends HttpOverrides {
6+
final String _host;
7+
final int _port;
8+
9+
_AhaClientHttpOverrides(this._host, this._port);
10+
11+
@override
12+
HttpClient createHttpClient(SecurityContext? context) {
13+
return super.createHttpClient(context)
14+
..badCertificateCallback =
15+
(X509Certificate cert, String host, int port) =>
16+
host == _host && port == _port;
17+
}
18+
}
19+
20+
Future<void> main() async {
21+
HttpOverrides.global =
22+
_AhaClientHttpOverrides(AhaClient.defaultHostName, 443);
23+
24+
final client = AhaClient();
25+
26+
final response = await client.login.getLoginStatus();
27+
28+
print(response.body);
29+
30+
client.dispose();
31+
}

lib/src/api/aha_client.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'package:aha_client/src/api/login_service.dart';
2+
import 'package:aha_client/src/api/models/session_info.dart';
3+
import 'package:aha_client/src/api/xml_typed_converter.dart';
24
import 'package:chopper/chopper.dart';
35

4-
abstract class AhaClient {
6+
class AhaClient {
57
static const defaultHostName = 'fritz.box';
68

79
final ChopperClient _client;
@@ -15,8 +17,14 @@ abstract class AhaClient {
1517
host: hostName,
1618
port: port,
1719
).toString(),
20+
converter: XmlTypedConverter()
21+
..registerConverter<SessionInfo>(SessionInfo.converter),
1822
services: [
1923
LoginService.create(),
2024
],
2125
);
26+
27+
void dispose() => _client.dispose();
28+
29+
LoginService get login => _client.getService();
2230
}

lib/src/api/login_service.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:aha_client/src/api/models/session_info.dart';
12
import 'package:chopper/chopper.dart';
23

34
part 'login_service.chopper.dart';
@@ -7,5 +8,5 @@ abstract class LoginService extends ChopperService {
78
static LoginService create([ChopperClient? client]) => _$LoginService(client);
89

910
@Get()
10-
Future<Response> getLoginStatus();
11+
Future<Response<SessionInfo>> getLoginStatus();
1112
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import 'package:aha_client/src/api/xml_typed_converter.dart';
2+
import 'package:freezed_annotation/freezed_annotation.dart';
3+
import 'package:xml/xml.dart';
4+
5+
import 'user.dart';
6+
7+
part 'session_info.freezed.dart';
8+
9+
@freezed
10+
class SessionInfo with _$SessionInfo {
11+
static const invalidSid = '0000000000000000';
12+
13+
static const converter = _SessionInfoTypeConverter();
14+
15+
const SessionInfo._();
16+
17+
const factory SessionInfo({
18+
required String sid,
19+
required String challange,
20+
required int blockTime,
21+
required List<User> users,
22+
}) = _SessionInfo;
23+
24+
factory SessionInfo.fromXml(XmlElement element) {
25+
assert(element.name.toString() == 'SessionInfo');
26+
return SessionInfo(
27+
sid: element.getElement('SID')!.text,
28+
challange: element.getElement('Challenge')!.text,
29+
blockTime: int.parse(element.getElement('BlockTime')!.text),
30+
users: element
31+
.getElement('Users')!
32+
.childElements
33+
.map((e) => User.fromXml(e))
34+
.toList(),
35+
);
36+
}
37+
38+
void toXml(XmlBuilder builder) {
39+
builder.element(
40+
'SessionInfo',
41+
nest: () => builder
42+
..element('SID', nest: sid)
43+
..element('Challange', nest: challange)
44+
..element('BlockTime', nest: blockTime)
45+
..element('Rights')
46+
..element(
47+
'Users',
48+
nest: () {
49+
for (final user in users) {
50+
user.toXml(builder);
51+
}
52+
},
53+
),
54+
);
55+
}
56+
}
57+
58+
class _SessionInfoTypeConverter extends SimpleTypeConverter<SessionInfo> {
59+
const _SessionInfoTypeConverter();
60+
61+
@override
62+
void buildXml(SessionInfo data, XmlBuilder builder) => data.toXml(builder);
63+
64+
@override
65+
SessionInfo parseXml(XmlElement element) => SessionInfo.fromXml(element);
66+
}

lib/src/api/models/user.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
import 'package:xml/xml.dart';
3+
4+
part 'user.freezed.dart';
5+
6+
@freezed
7+
class User with _$User {
8+
const User._();
9+
10+
const factory User(
11+
String user, {
12+
@Default(false) bool last,
13+
}) = _User;
14+
15+
factory User.fromXml(XmlElement element) {
16+
assert(element.name.toString() == 'User');
17+
return User(
18+
element.text,
19+
last: element.getAttribute('last') == '1',
20+
);
21+
}
22+
23+
void toXml(XmlBuilder builder) {
24+
builder.element(
25+
'User',
26+
attributes: {
27+
if (last) 'last': '1',
28+
},
29+
nest: user,
30+
);
31+
}
32+
}

lib/src/api/xml_converter.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'dart:async';
2+
3+
import 'package:chopper/chopper.dart';
4+
import 'package:xml/xml.dart';
5+
6+
class XmlConverter implements Converter {
7+
@override
8+
FutureOr<Request> convertRequest(Request request) {
9+
final rawBody = request.body;
10+
if (rawBody is XmlDocument) {
11+
return request.copyWith(body: rawBody.toXmlString());
12+
} else {
13+
return request;
14+
}
15+
}
16+
17+
@override
18+
FutureOr<Response<BodyType>> convertResponse<BodyType, InnerType>(
19+
Response response,
20+
) {
21+
if (BodyType == XmlDocument) {
22+
return response.copyWith(
23+
body: XmlDocument.parse(response.bodyString),
24+
) as Response<BodyType>;
25+
} else {
26+
return response.copyWith();
27+
}
28+
}
29+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import 'dart:async';
2+
3+
import 'package:aha_client/src/api/xml_converter.dart';
4+
import 'package:chopper/chopper.dart';
5+
import 'package:meta/meta.dart';
6+
import 'package:xml/xml.dart';
7+
8+
abstract class TypeConverter<T> {
9+
const TypeConverter._();
10+
11+
XmlDocument toXml(T data);
12+
T fromXml(XmlDocument xml);
13+
}
14+
15+
abstract class SimpleTypeConverter<T> implements TypeConverter<T> {
16+
const SimpleTypeConverter();
17+
18+
@override
19+
T fromXml(XmlDocument xml) => parseXml(xml.rootElement);
20+
21+
@override
22+
XmlDocument toXml(T data) {
23+
final builder = XmlBuilder()..processing('xml', 'version="1.0"');
24+
buildXml(data, builder);
25+
return builder.buildDocument();
26+
}
27+
28+
@protected
29+
void buildXml(T data, XmlBuilder builder);
30+
31+
@protected
32+
T parseXml(XmlElement element);
33+
}
34+
35+
class XmlTypedConverter extends Converter {
36+
final _xmlConverter = XmlConverter();
37+
38+
final _typeConverters = <Type, TypeConverter>{};
39+
40+
void registerConverter<T>(TypeConverter<T> converter) =>
41+
_typeConverters[T] = converter;
42+
43+
@override
44+
FutureOr<Request> convertRequest(Request request) {
45+
final converter = _typeConverters[request.body.runtimeType];
46+
if (converter != null) {
47+
return _xmlConverter.convertRequest(
48+
request.copyWith(
49+
body: converter.toXml(request.body),
50+
),
51+
);
52+
} else {
53+
return _xmlConverter.convertRequest(request);
54+
}
55+
}
56+
57+
@override
58+
FutureOr<Response<BodyType>> convertResponse<BodyType, InnerType>(
59+
Response response,
60+
) async {
61+
final converter = _typeConverters[BodyType] as TypeConverter<BodyType>?;
62+
if (converter != null) {
63+
final xmlResponse = await _xmlConverter
64+
.convertResponse<XmlDocument, XmlDocument>(response);
65+
if (xmlResponse.body == null) {
66+
return xmlResponse.copyWith();
67+
}
68+
69+
return xmlResponse.copyWith(
70+
body: converter.fromXml(xmlResponse.body!),
71+
);
72+
} else if (BodyType == XmlDocument) {
73+
return _xmlConverter.convertResponse<BodyType, InnerType>(response);
74+
} else {
75+
return response.copyWith();
76+
}
77+
}
78+
}

pubspec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ environment:
88

99
dependencies:
1010
chopper: ^4.0.1
11+
freezed_annotation: ^0.14.3
12+
meta: ^1.7.0
13+
xml: ^5.2.0
1114

1215
dev_dependencies:
1316
build_runner: ^2.1.2
1417
chopper_generator: ^4.0.1
18+
freezed: ^0.14.2
1519
lint: ^1.6.0
1620
test: ^1.16.0
1721

0 commit comments

Comments
 (0)