Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

patrol_gen: add support for generic Maps #1810

Merged
merged 2 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,51 @@ class PatrolAppServiceClient {
}

func listDartTests(completion: @escaping (Result<ListDartTestsResponse, Error>) -> Void) {
performRequest(requestName: "listDartTests", completion: completion)
performRequestWithResult(requestName: "listDartTests", completion: completion)
}

func runDartTest(request: RunDartTestRequest, completion: @escaping (Result<RunDartTestResponse, Error>) -> Void) {
do {
let body = try JSONEncoder().encode(request)
performRequest(requestName: "runDartTest", body: body, completion: completion)
performRequestWithResult(requestName: "runDartTest", body: body, completion: completion)
} catch let err {
completion(.failure(err))
}
}

private func performRequest<TResult: Codable>(
private func performRequestWithResult<TResult: Decodable>(
requestName: String, body: Data? = nil, completion: @escaping (Result<TResult, Error>) -> Void
) {
performRequest(requestName: requestName, body: body) { result in
switch result {
case .success(let data):
do {
let object = try JSONDecoder().decode(TResult.self, from: data)
completion(.success(object))
} catch let err {
completion(.failure(err))
}
case .failure(let error):
completion(.failure(error))
}
}
}

private func performRequestWithEmptyResult(
requestName: String, body: Data? = nil, completion: @escaping (Error?) -> Void
) {
performRequest(requestName: requestName, body: body) { result in
switch result {
case .success(_):
completion(nil)
case .failure(let error):
completion(error)
}
}
}

private func performRequest(
requestName: String, body: Data? = nil, completion: @escaping (Result<Data, Error>) -> Void
) {
let url = URL(string: "http://\(address):\(port)/\(requestName)")!

Expand All @@ -47,12 +78,7 @@ class PatrolAppServiceClient {

session.dataTask(with: request) { data, response, error in
if (response as? HTTPURLResponse)?.statusCode == 200 {
do {
let object = try JSONDecoder().decode(TResult.self, from: data!)
completion(.success(object))
} catch let err {
completion(.failure(err))
}
completion(.success(data!))
} else {
let message =
"Invalid response: \(String(describing: response)) \(String(describing: data))"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ package ${config.package};
String _createMessage(Message message) {
final fields = message.fields.map((e) {
final optional = e.isOptional ? '? = null' : '';
return e.isList
? ' val ${e.name}: List<${_transformType(e.type)}>$optional'
: ' val ${e.name}: ${_transformType(e.type)}$optional';
return switch (e.type) {
MapFieldType(keyType: final keyType, valueType: final valueType) =>
' val ${e.name}: Map<${_transformType(keyType)}, ${_transformType(valueType)}>$optional',
ListFieldType(type: final type) =>
' val ${e.name}: List<${_transformType(type)}>$optional',
OrdinaryFieldType(type: final type) =>
' val ${e.name}: ${_transformType(type)}$optional',
};
}).join(',\n');

final dataKeyword = fields.isNotEmpty ? 'data ' : '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ $endpoints
val response = performRequest("${endpoint.name}"$serializeParameter)
return json.fromJson(response, Contracts.${endpoint.response!.name}::class.java)'''
: '''
return performRequest("${endpoint.name}"$serializeParameter)''';
performRequest("${endpoint.name}"$serializeParameter)''';

return '''
fun ${endpoint.name}($parameterDef)$returnDef {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@ enum ${enumDefinition.name} {
String? _createMessage(Message message) {
final fieldsContent = message.fields
.map(
(f) => f.isList
? 'final List<${f.type}>${f.isOptional ? '?' : ''} ${f.name};'
: 'final ${f.type}${f.isOptional ? '?' : ''} ${f.name};',
(f) => switch (f.type) {
ListFieldType(type: final type) =>
'final List<$type>${f.isOptional ? '?' : ''} ${f.name};',
MapFieldType(keyType: final keyType, valueType: final valueType) =>
'final Map<$keyType,$valueType>${f.isOptional ? '?' : ''} ${f.name};',
OrdinaryFieldType(type: final type) =>
'final $type${f.isOptional ? '?' : ''} ${f.name};'
},
)
.join('\n');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import '${path.basename(config.contractsFilename)}';
String _generateHandlerCalls(Service service) {
return service.endpoints.map((e) {
var requestDeserialization = '';
var responseSerialization = '';

if (e.request != null) {
requestDeserialization = '''
Expand All @@ -49,16 +48,19 @@ final requestObj = ${e.request!.name}.fromJson(json as Map<String,dynamic>);
''';
}

final handlerCall = e.request != null
? 'final result = await ${e.name}(requestObj);'
: 'final result = await ${e.name}();';

var handlerCall = e.request != null
? 'await ${e.name}(requestObj);'
: 'await ${e.name}();';
if (e.response != null) {
responseSerialization = '''
final body = jsonEncode(result.toJson());
return Response.ok(body);''';
handlerCall = 'final result = $handlerCall';
}

final responseSerialization = e.response != null
? '''
final body = jsonEncode(result.toJson());
return Response.ok(body);'''
: "return Response.ok('');";

final elseKeyword = e == service.endpoints.first ? '' : 'else';

return '''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ class IOSContractsGenerator {
String _createMessage(Message message) {
final fields = message.fields.map((e) {
final optional = e.isOptional ? '?' : '';
return e.isList
? ' var ${e.name}: [${_transformType(e.type)}]$optional'
: ' var ${e.name}: ${_transformType(e.type)}$optional';
return switch (e.type) {
MapFieldType(keyType: final keyType, valueType: final valueType) =>
' var ${e.name}: [${_transformType(keyType)}: ${_transformType(valueType)}]$optional',
ListFieldType(type: final type) =>
' var ${e.name}: [${_transformType(type)}]$optional',
OrdinaryFieldType(type: final type) =>
' var ${e.name}: ${_transformType(type)}$optional',
};
}).join('\n');

return '''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,39 @@ class ${service.name}Client {

$endpoints

private func performRequest<TResult: Codable>(
private func performRequestWithResult<TResult: Decodable>(
requestName: String, body: Data? = nil, completion: @escaping (Result<TResult, Error>) -> Void
) {
performRequest(requestName: requestName, body: body) { result in
switch result {
case .success(let data):
do {
let object = try JSONDecoder().decode(TResult.self, from: data)
completion(.success(object))
} catch let err {
completion(.failure(err))
}
case .failure(let error):
completion(.failure(error))
}
}
}

private func performRequestWithEmptyResult(
requestName: String, body: Data? = nil, completion: @escaping (Error?) -> Void
) {
performRequest(requestName: requestName, body: body) { result in
switch result {
case .success(_):
completion(nil)
case .failure(let error):
completion(error)
}
}
}

private func performRequest(
requestName: String, body: Data? = nil, completion: @escaping (Result<Data, Error>) -> Void
) {
let url = URL(string: "$url")!

Expand All @@ -66,12 +97,7 @@ $endpoints

session.dataTask(with: request) { data, response, error in
if (response as? HTTPURLResponse)?.statusCode == 200 {
do {
let object = try JSONDecoder().decode(TResult.self, from: data!)
completion(.success(object))
} catch let err {
completion(.failure(err))
}
completion(.success(data!))
} else {
let message =
"$exceptionMessage"
Expand All @@ -89,7 +115,7 @@ $endpoints

final completionDef = endpoint.response != null
? 'completion: @escaping (Result<${endpoint.response!.name}, Error>) -> Void'
: 'completion: @escaping (Result<Void, Error>) -> Void';
: 'completion: @escaping (Error?) -> Void';

final parameters = endpoint.request != null
? '$requestDef, $completionDef'
Expand All @@ -98,18 +124,25 @@ $endpoints
final arguments = [
'requestName: "${endpoint.name}"',
if (endpoint.request != null) 'body: body',
'completion: completion'
'completion: completion',
].join(', ');

final performRequest = endpoint.response != null
? 'performRequestWithResult'
: 'performRequestWithEmptyResult';
final failureCompletion = endpoint.response != null
? 'completion(.failure(err))'
: 'completion(err)';

final bodyCode = endpoint.request != null
? '''
do {
let body = try JSONEncoder().encode(request)
performRequest($arguments)
$performRequest($arguments)
} catch let err {
completion(.failure(err))
$failureCompletion
}'''
: ' performRequest($arguments)';
: ' $performRequest($arguments)';

return '''
func ${endpoint.name}($parameters) {
Expand Down
18 changes: 13 additions & 5 deletions packages/patrol_gen/lib/src/resolve_schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,28 @@ Message _createMessage(ClassDeclaration declaration) {
final isOptional = type.question != null;
final fieldName = e.variables.first.name.lexeme;

if (type.type?.isDartCoreList ?? false) {
if (type.type?.isDartCoreMap ?? false) {
final arguments = type.typeArguments!.arguments;
return MessageField(
isOptional: isOptional,
name: fieldName,
type: MapFieldType(
keyType: (arguments[0] as NamedType).name2.lexeme,
valueType: (arguments[1] as NamedType).name2.lexeme,
),
);
} else if (type.type?.isDartCoreList ?? false) {
final genericType = type.typeArguments!.arguments.first as NamedType;
return MessageField(
isOptional: isOptional,
name: fieldName,
type: genericType.name2.lexeme,
isList: true,
type: ListFieldType(type: genericType.name2.lexeme),
);
} else {
return MessageField(
isOptional: isOptional,
name: fieldName,
type: type.name2.lexeme,
isList: false,
type: OrdinaryFieldType(type: type.name2.lexeme),
);
}
} else {
Expand Down
25 changes: 22 additions & 3 deletions packages/patrol_gen/lib/src/schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,37 @@ class Enum {
final List<String> fields;
}

sealed class MessageFieldType {}

class OrdinaryFieldType implements MessageFieldType {
const OrdinaryFieldType({required this.type});

final String type;
}

class ListFieldType implements MessageFieldType {
const ListFieldType({required this.type});

final String type;
}

class MapFieldType implements MessageFieldType {
const MapFieldType({required this.keyType, required this.valueType});

final String keyType;
final String valueType;
}

class MessageField {
const MessageField({
required this.name,
required this.type,
required this.isOptional,
required this.isList,
});

final bool isOptional;
final bool isList;
final String name;
final String type;
final MessageFieldType type;
}

class Message {
Expand Down