@@ -13,12 +13,15 @@ String dartRetrofitClientTemplate({
1313 required bool useMultipartFile,
1414 bool extrasParameterByDefault = false ,
1515 bool dioOptionsParameterByDefault = false ,
16+ bool addOpenApiMetadata = false ,
1617 bool originalHttpResponse = false ,
1718 String ? fileName,
1819}) {
1920 final parameterTypes = restClient.requests
2021 .expand ((r) => r.parameters.map ((p) => p.type))
2122 .toSet ();
23+ final includeExtras = extrasParameterByDefault;
24+ final includeMetadata = addOpenApiMetadata;
2225 final sb = StringBuffer ('''
2326${_convertImport (restClient )}${ioImport (parameterTypes , useMultipartFile : useMultipartFile )}import 'package:dio/dio.dart'${_hideHeaders (restClient , defaultContentType )};
2427import 'package:retrofit/retrofit.dart';
@@ -29,26 +32,47 @@ part '${fileName ?? name.toSnake}.g.dart';
2932abstract class $name {
3033 factory $name (Dio dio, {String? baseUrl}) = _$name ;
3134''' );
35+
36+ if (includeMetadata && restClient.requests.isNotEmpty) {
37+ sb.write ('\n ' );
38+ for (final request in restClient.requests) {
39+ sb.write (_openApiExtrasConst (request));
40+ }
41+ }
42+
3243 for (final request in restClient.requests) {
44+ final openApiExtrasConstName =
45+ includeMetadata ? _openApiConstName (request) : null ;
46+ sb.write ('\n ' );
3347 sb.write (
34- _toClientRequest (request, defaultContentType,
35- originalHttpResponse: originalHttpResponse,
36- extrasParameterByDefault: extrasParameterByDefault,
37- dioOptionsParameterByDefault: dioOptionsParameterByDefault,
38- useMultipartFile: useMultipartFile),
48+ _toClientRequest (
49+ request,
50+ defaultContentType,
51+ className: name,
52+ originalHttpResponse: originalHttpResponse,
53+ addExtrasParameter: includeExtras,
54+ addDioOptionsParameter: dioOptionsParameterByDefault,
55+ includeMetadata: includeMetadata,
56+ useMultipartFile: useMultipartFile,
57+ openApiExtrasConstName: openApiExtrasConstName,
58+ ),
3959 );
4060 }
61+
4162 sb.write ('}\n ' );
4263 return sb.toString ();
4364}
4465
4566String _toClientRequest (
4667 UniversalRequest request,
4768 String defaultContentType, {
69+ required String className,
4870 required bool originalHttpResponse,
49- required bool extrasParameterByDefault,
50- required bool dioOptionsParameterByDefault,
71+ required bool addExtrasParameter,
72+ required bool addDioOptionsParameter,
73+ required bool includeMetadata,
5174 required bool useMultipartFile,
75+ String ? openApiExtrasConstName,
5276}) {
5377 final responseType = request.returnType == null
5478 ? 'void'
@@ -71,32 +95,41 @@ String _toClientRequest(
7195 final dioResponseTypeAnnotation =
7296 isBinaryResponse ? '\n @DioResponseType(ResponseType.bytes)' : '' ;
7397
74- final sb = StringBuffer (
75- '''
98+ final defaultExtras = includeMetadata && addExtrasParameter
99+ ? _openApiExtrasReference (
100+ openApiExtrasConstName,
101+ request,
102+ className: className,
103+ )
104+ : null ;
105+
106+ final sb = StringBuffer ()
107+ ..write (
108+ " ${descriptionComment (request .description , tabForFirstLine : false , tab : ' ' , end : ' ' )}${request .isDeprecated ? "@Deprecated('This method is marked as deprecated')\n " : '' }${_contentTypeHeader (request , defaultContentType )}@${request .requestType .name .toUpperCase ()}('${request .route }')$dioResponseTypeAnnotation \n Future<$finalResponseType > ${request .name }(" ,
109+ );
76110
77- ${descriptionComment (request .description , tabForFirstLine : false , tab : ' ' , end : ' ' )}${request .isDeprecated ? "@Deprecated('This method is marked as deprecated')\n " : '' }${_contentTypeHeader (request , defaultContentType )}@${request .requestType .name .toUpperCase ()}('${request .route }')$dioResponseTypeAnnotation
78- Future<$finalResponseType > ${request .name }(''' ,
79- );
80111 if (request.parameters.isNotEmpty ||
81- extrasParameterByDefault ||
82- dioOptionsParameterByDefault ) {
112+ addExtrasParameter ||
113+ addDioOptionsParameter ) {
83114 sb.write ('{\n ' );
84115 }
116+
85117 final sortedByRequired = List <UniversalRequestType >.from (
86118 request.parameters.sorted ((a, b) => a.type.compareTo (b.type)),
87119 );
88120 for (final parameter in sortedByRequired) {
89121 sb.write ('${_toParameter (parameter , useMultipartFile )}\n ' );
90122 }
91- if (extrasParameterByDefault ) {
92- sb.write (_addExtraParameter ());
123+ if (addExtrasParameter ) {
124+ sb.write (_addExtraParameter (defaultExtras ));
93125 }
94- if (dioOptionsParameterByDefault ) {
126+ if (addDioOptionsParameter ) {
95127 sb.write (_addDioOptionsParameter ());
96128 }
129+
97130 if (request.parameters.isNotEmpty ||
98- extrasParameterByDefault ||
99- dioOptionsParameterByDefault ) {
131+ addExtrasParameter ||
132+ addDioOptionsParameter ) {
100133 sb.write (' });\n ' );
101134 } else {
102135 sb.write (');\n ' );
@@ -111,7 +144,43 @@ String _convertImport(UniversalRestClient restClient) =>
111144 ? "import 'dart:convert';\n "
112145 : '' ;
113146
114- String _addExtraParameter () => ' @Extras() Map<String, dynamic>? extras,\n ' ;
147+ String _addExtraParameter (String ? defaultExtras) =>
148+ ' @Extras() Map<String, dynamic>? extras${defaultExtras != null ? ' =\n $defaultExtras ' : '' },\n ' ;
149+
150+ String _openApiExtrasReference (
151+ String ? openApiExtrasConstName,
152+ UniversalRequest request, {
153+ required String className,
154+ }) {
155+ return openApiExtrasConstName != null
156+ ? '$className .$openApiExtrasConstName '
157+ : _openApiExtrasLiteral (request);
158+ }
159+
160+ String _openApiExtrasConst (UniversalRequest request) =>
161+ ' static const Map<String, dynamic> ${_openApiConstName (request )} =\n '
162+ ' ${_openApiExtrasLiteral (request )};\n ' ;
163+
164+ String _openApiConstName (UniversalRequest request) =>
165+ '${request .name }OpenapiExtras' ;
166+
167+ String _openApiExtrasLiteral (UniversalRequest request) {
168+ final tags = request.tags.map (_quoteJson).join (', ' );
169+ final operationId = _quoteJson (request.operationId ?? request.name);
170+ final externalDocsUrl = request.externalDocsUrl != null
171+ ? _quoteJson (request.externalDocsUrl! )
172+ : 'null' ;
173+ return '''<String, dynamic>{
174+ 'openapi': <String, dynamic>{
175+ 'tags': <String>[$tags ],
176+ 'operationId': $operationId ,
177+ 'externalDocsUrl': $externalDocsUrl ,
178+ },
179+ }''' ;
180+ }
181+
182+ String _quoteJson (String value) =>
183+ '"${value .replaceAll (r'\\' , r'\\\\' ).replaceAll ('"' , r'\\"' )}"' ;
115184
116185String _addDioOptionsParameter () =>
117186 ' @DioOptions() RequestOptions? options,\n ' ;
@@ -140,7 +209,7 @@ String _toParameter(UniversalRequestType parameter, bool useMultipartFile) {
140209 : '' ;
141210
142211 return '$deprecatedAnnotation @${parameter .parameterType .type }'
143- "(${parameter .name != null && !parameter .parameterType .isBody ? "${parameter .parameterType .isPart ? 'name: ' : '' }${_startWith$ (parameter .name !) ? 'r' : '' }'${parameter .name }'" : '' }) "
212+ "(${parameter .name != null && !parameter .parameterType .isBody ? "${parameter .parameterType .isPart ? 'name: ' : '' }${_startsWithDollar (parameter .name !) ? 'r' : '' }'${parameter .name }'" : '' }) "
144213 '${_required (parameter .type )}'
145214 '$parameterType '
146215 '$keywordArguments ${_defaultValue (parameter .type )},' ;
@@ -182,4 +251,4 @@ String _defaultValue(UniversalType t) => !t.isRequired && t.defaultValue != null
182251 '${t .enumType != null ? '${t .type }.${protectDefaultEnum (t .defaultValue ?.toCamel )?.toCamel }' : protectDefaultValue (t .defaultValue , type : t .type )}'
183252 : '' ;
184253
185- bool _startWith$ (String name) => name.isNotEmpty && name.startsWith (r'$' );
254+ bool _startsWithDollar (String name) => name.isNotEmpty && name.startsWith (r'$' );
0 commit comments