Last active
December 18, 2020 05:44
-
-
Save Grohden/1fd8d110c806d8b239e88d07847c1e5a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{{>header}} | |
{{>part_of}} | |
class ApiClient { | |
ApiClient({this.basePath = '{{{basePath}}}'}) { | |
{{#hasAuthMethods}} | |
// Setup authentications (key: authentication name, value: authentication). | |
{{#authMethods}} | |
{{#isBasic}} | |
{{#isBasicBasic}} | |
_authentications[r'{{{name}}}'] = HttpBasicAuth(); | |
{{/isBasicBasic}} | |
{{#isBasicBearer}} | |
_authentications[r'{{{name}}}'] = HttpBearerAuth(); | |
{{/isBasicBearer}} | |
{{/isBasic}} | |
{{#isApiKey}} | |
_authentications[r'{{{name}}}'] = ApiKeyAuth({{#isKeyInCookie}}'cookie'{{/isKeyInCookie}}{{^isKeyInCookie}}{{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{^isKeyInHeader}}'query'{{/isKeyInHeader}}{{/isKeyInCookie}}, '{{{keyParamName}}}'); | |
{{/isApiKey}} | |
{{#isOAuth}} | |
_authentications[r'{{{name}}}'] = OAuth(); | |
{{/isOAuth}} | |
{{/authMethods}} | |
{{/hasAuthMethods}} | |
} | |
final String basePath; | |
var _client = Client(); | |
/// Returns the current HTTP [Client] instance to use in this class. | |
/// | |
/// The return value is guaranteed to never be null. | |
Client get client => _client; | |
/// Requests to use a new HTTP [Client] in this class. | |
/// | |
/// If the [newClient] is null, an [ArgumentError] is thrown. | |
set client(Client newClient) { | |
if (newClient == null) { | |
throw ArgumentError('New client instance cannot be null.'); | |
} | |
_client = newClient; | |
} | |
final _defaultHeaderMap = <String, String>{}; | |
final _authentications = <String, Authentication>{}; | |
void addDefaultHeader(String key, String value) { | |
_defaultHeaderMap[key] = value; | |
} | |
dynamic deserialize(String json, String targetType, {bool growable}) { | |
// Remove all spaces. Necessary for reg expressions as well. | |
targetType = targetType.replaceAll(' ', ''); | |
return targetType == 'String' | |
? json | |
: _deserialize(jsonDecode(json), targetType, growable: true == growable); | |
} | |
String serialize(Object obj) => obj == null ? '' : json.encode(obj); | |
T getAuthentication<T extends Authentication>(String name) { | |
final authentication = _authentications[name]; | |
return authentication is T ? authentication : null; | |
} | |
// We don’t use a Map<String, String> for queryParams. | |
// If collectionFormat is 'multi', a key might appear multiple times. | |
Future<Response> invokeAPI( | |
String path, | |
String method, | |
List<QueryParam> queryParams, | |
Object body, | |
Map<String, String> headerParams, | |
Map<String, String> formParams, | |
String nullableContentType, | |
List<String> authNames, | |
) async { | |
_updateParamsForAuth(authNames, queryParams, headerParams); | |
headerParams.addAll(_defaultHeaderMap); | |
final urlEncodedQueryParams = queryParams | |
.where((param) => param.value != null) | |
.map((param) => '$param'); | |
final queryString = urlEncodedQueryParams.isNotEmpty | |
? '?${urlEncodedQueryParams.join('&')}' | |
: ''; | |
final url = '$basePath$path$queryString'; | |
if (nullableContentType != null) { | |
headerParams['Content-Type'] = nullableContentType; | |
} | |
try { | |
// Special case for uploading a single file which isn’t a 'multipart/form-data'. | |
if ( | |
body is MultipartFile && (nullableContentType == null || | |
!nullableContentType.toLowerCase().startsWith('multipart/form-data')) | |
) { | |
final request = StreamedRequest(method, Uri.parse(url)); | |
request.headers.addAll(headerParams); | |
request.contentLength = body.length; | |
body.finalize().listen( | |
request.sink.add, | |
onDone: request.sink.close, | |
onError: (error, trace) => request.sink.close(), | |
cancelOnError: true, | |
); | |
final response = await _client.send(request); | |
return Response.fromStream(response); | |
} | |
if (body is MultipartRequest) { | |
final request = MultipartRequest(method, Uri.parse(url)); | |
request.fields.addAll(body.fields); | |
request.files.addAll(body.files); | |
request.headers.addAll(body.headers); | |
request.headers.addAll(headerParams); | |
final response = await _client.send(request); | |
return Response.fromStream(response); | |
} | |
final msgBody = nullableContentType == 'application/x-www-form-urlencoded' | |
? formParams | |
: serialize(body); | |
final nullableHeaderParams = headerParams.isEmpty ? null : headerParams; | |
switch(method) { | |
case 'POST': return await _client.post(url, headers: nullableHeaderParams, body: msgBody,); | |
case 'PUT': return await _client.put(url, headers: nullableHeaderParams, body: msgBody,); | |
case 'DELETE': return await _client.delete(url, headers: nullableHeaderParams,); | |
case 'PATCH': return await _client.patch(url, headers: nullableHeaderParams, body: msgBody,); | |
case 'HEAD': return await _client.head(url, headers: nullableHeaderParams,); | |
case 'GET': return await _client.get(url, headers: nullableHeaderParams,); | |
} | |
} on SocketException catch (e, trace) { | |
throw ApiException.withInner(HttpStatus.badRequest, 'Socket operation failed: $method $path', e, trace,); | |
} on TlsException catch (e, trace) { | |
throw ApiException.withInner(HttpStatus.badRequest, 'TLS/SSL communication failed: $method $path', e, trace,); | |
} on IOException catch (e, trace) { | |
throw ApiException.withInner(HttpStatus.badRequest, 'I/O operation failed: $method $path', e, trace,); | |
} on ClientException catch (e, trace) { | |
throw ApiException.withInner(HttpStatus.badRequest, 'HTTP connection failed: $method $path', e, trace,); | |
} on Exception catch (e, trace) { | |
throw ApiException.withInner(HttpStatus.badRequest, 'Exception occurred: $method $path', e, trace,); | |
} | |
throw ApiException(HttpStatus.badRequest, 'Invalid HTTP operation: $method $path',); | |
} | |
dynamic _deserialize(dynamic value, String targetType, {bool growable}) { | |
try { | |
switch (targetType) { | |
case 'String': | |
return '$value'; | |
case 'int': | |
return value is int ? value : int.parse('$value'); | |
case 'bool': | |
if (value is bool) { | |
return value; | |
} | |
final valueString = '$value'.toLowerCase(); | |
return valueString == 'true' || valueString == '1'; | |
break; | |
case 'double': | |
return value is double ? value : double.parse('$value'); | |
{{#models}} | |
{{#model}} | |
case '{{{classname}}}': | |
{{#isEnum}} | |
return {{{classname}}}TypeTransformer().decode(value); | |
{{/isEnum}} | |
{{^isEnum}} | |
return {{{classname}}}.fromJson(value as Map<String, dynamic>); | |
{{/isEnum}} | |
{{/model}} | |
{{/models}} | |
default: | |
Match match; | |
if (value is List && (match = _regList.firstMatch(targetType)) != null) { | |
final newTargetType = match[1]; | |
return value | |
.map((v) => _deserialize(v, newTargetType, growable: growable)) | |
.toList(growable: true == growable); | |
} | |
if (value is Map && (match = _regMap.firstMatch(targetType)) != null) { | |
final newTargetType = match[1]; | |
return Map.fromIterables( | |
value.keys, | |
value.values.map((v) => _deserialize(v, newTargetType, growable: growable)), | |
); | |
} | |
break; | |
} | |
} on Exception catch (e, stack) { | |
throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', e, stack,); | |
} | |
throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization',); | |
} | |
/// Update query and header parameters based on authentication settings. | |
/// @param authNames The authentications to apply | |
void _updateParamsForAuth( | |
List<String> authNames, | |
List<QueryParam> queryParams, | |
Map<String, String> headerParams, | |
) { | |
for(final authName in authNames) { | |
final auth = _authentications[authName]; | |
if (auth == null) { | |
throw ArgumentError('Authentication undefined: $authName'); | |
} | |
auth.applyToParams(queryParams, headerParams); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class {{{classname}}} { | |
/// Returns a new [{{{classname}}}] instance. | |
{{{classname}}}({ | |
{{#vars}} | |
{{#required}}{{^defaultValue}}@required {{/defaultValue}}{{/required}}this.{{{name}}}{{^isNullable}}{{#defaultValue}} = {{#isEnum}}{{^isContainer}}const {{{classname}}}{{{enumName}}}._({{/isContainer}}{{/isEnum}}{{{defaultValue}}}{{#isEnum}}{{^isContainer}}){{/isContainer}}{{/isEnum}}{{/defaultValue}}{{/isNullable}}, | |
{{/vars}} | |
}); | |
{{#vars}} | |
{{#description}} | |
/// {{{description}}} | |
{{/description}} | |
{{^isEnum}} | |
{{#minimum}} | |
// minimum: {{{minimum}}} | |
{{/minimum}} | |
{{#maximum}} | |
// maximum: {{{maximum}}} | |
{{/maximum}} | |
{{/isEnum}} | |
final {{{datatypeWithEnum}}} {{{name}}}; | |
{{/vars}} | |
@override | |
bool operator ==(Object other) => identical(this, other) || other is {{{classname}}} && | |
{{#vars}} | |
other.{{{name}}} == {{{name}}}{{^-last}} &&{{/-last}}{{#-last}};{{/-last}} | |
{{/vars}} | |
@override | |
int get hashCode => | |
{{#vars}} | |
({{{name}}} == null ? 0 : {{{name}}}.hashCode){{^-last}} +{{/-last}}{{#-last}};{{/-last}} | |
{{/vars}} | |
@override | |
String toString() => '{{{classname}}}[{{#vars}}{{{name}}}=${{{name}}}{{^-last}}, {{/-last}}{{/vars}}]'; | |
Map<String, dynamic> toJson() { | |
final json = <String, dynamic>{}; | |
{{#vars}} | |
if ({{{name}}} != null) { | |
{{#isDateTime}} | |
{{#pattern}} | |
json[r'{{{baseName}}}'] = _dateEpochMarker == '{{{pattern}}}' | |
? {{{name}}}.millisecondsSinceEpoch | |
: {{{name}}}.toUtc().toIso8601String(); | |
{{/pattern}} | |
{{^pattern}} | |
json[r'{{{baseName}}}'] = {{{name}}}.toUtc().toIso8601String(); | |
{{/pattern}} | |
{{/isDateTime}} | |
{{#isDate}} | |
{{#pattern}} | |
json[r'{{{baseName}}}'] = _dateEpochMarker == '{{{pattern}}}' | |
? {{{name}}}.millisecondsSinceEpoch | |
: _dateFormatter.format({{{name}}}.toUtc()); | |
{{/pattern}} | |
{{^pattern}} | |
json[r'{{{baseName}}}'] = _dateFormatter.format({{{name}}}.toUtc()); | |
{{/pattern}} | |
{{/isDate}} | |
{{^isDateTime}} | |
{{^isDate}} | |
json[r'{{{baseName}}}'] = {{{name}}}; | |
{{/isDate}} | |
{{/isDateTime}} | |
} | |
{{/vars}} | |
return json; | |
} | |
/// Returns a new [{{{classname}}}] instance and imports its values from | |
/// [json] if it's non-null, null if [json] is null. | |
static {{{classname}}} fromJson(Map<String, dynamic> json) => json == null | |
? null | |
: {{{classname}}}( | |
{{#vars}} | |
{{#isDateTime}} | |
{{{name}}}: json[r'{{{baseName}}}'] == null | |
? null | |
{{#pattern}} | |
: _dateEpochMarker == '{{{pattern}}}' | |
? DateTime.fromMillisecondsSinceEpoch(json[r'{{{baseName}}}'] as int, isUtc: true) | |
: DateTime.parse(json[r'{{{baseName}}}'] as String), | |
{{/pattern}} | |
{{^pattern}} | |
: DateTime.parse(json[r'{{{baseName}}}'] as String), | |
{{/pattern}} | |
{{/isDateTime}} | |
{{#isDate}} | |
{{{name}}}: json[r'{{{baseName}}}'] == null | |
? null | |
{{#pattern}} | |
: _dateEpochMarker == '{{{pattern}}}' | |
? DateTime.fromMillisecondsSinceEpoch(json[r'{{{baseName}}}'] as int, isUtc: true) | |
: DateTime.parse(json[r'{{{baseName}}}'] as String), | |
{{/pattern}} | |
{{^pattern}} | |
: DateTime.parse(json[r'{{{baseName}}}'] as String), | |
{{/pattern}} | |
{{/isDate}} | |
{{^isDateTime}} | |
{{^isDate}} | |
{{#complexType}} | |
{{#isArray}} | |
{{#items.isArray}} | |
{{{name}}}: json[r'{{{baseName}}}'] == null | |
? null | |
: (json[r'{{{baseName}}}'] as List).map( | |
{{#items.complexType}} | |
{{items.complexType}}.listFromJson(json[r'{{{baseName}}}'] as List<dynamic>) | |
{{/items.complexType}} | |
{{^items.complexType}} | |
(e) => e == null ? null : (e as List).cast<{{items.items.dataType}}>() | |
{{/items.complexType}} | |
).toList(growable: false), | |
{{/items.isArray}} | |
{{^items.isArray}} | |
{{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}'] as List<dynamic>), | |
{{/items.isArray}} | |
{{/isArray}} | |
{{^isArray}} | |
{{#isMap}} | |
{{#items.isArray}} | |
{{{name}}}: json[r'{{{baseName}}}'] == null | |
? null | |
{{#items.complexType}} | |
: {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']), | |
{{/items.complexType}} | |
{{^items.complexType}} | |
: (json[r'{{{baseName}}}'] as Map).cast<String, List>(), | |
{{/items.complexType}} | |
{{/items.isArray}} | |
{{^items.isArray}} | |
{{{name}}}: json[r'{{{baseName}}}'] as Map<String, Map<String, String>>, | |
{{/items.isArray}} | |
{{/isMap}} | |
{{^isMap}} | |
{{#isBinary}} | |
{{{name}}}: null, // No support for decoding binary content from JSON | |
{{/isBinary}} | |
{{^isBinary}} | |
{{{name}}}: {{{complexType}}}.fromJson(json[r'{{{baseName}}}'] as Map<String, dynamic>), | |
{{/isBinary}} | |
{{/isMap}} | |
{{/isArray}} | |
{{/complexType}} | |
{{^complexType}} | |
{{#isArray}} | |
{{#isEnum}} | |
{{{name}}}: {{{classname}}}{{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']), | |
{{/isEnum}} | |
{{^isEnum}} | |
{{{name}}}: json[r'{{{baseName}}}'] == null | |
? null | |
: (json[r'{{{baseName}}}'] as List).cast<{{{items.datatype}}}>(), | |
{{/isEnum}} | |
{{/isArray}} | |
{{^isArray}} | |
{{#isMap}} | |
{{{name}}}: json[r'{{{baseName}}}'] == null ? | |
null : | |
(json[r'{{{baseName}}}'] as Map).cast<String, {{{items.datatype}}}>(), | |
{{/isMap}} | |
{{^isMap}} | |
{{#isNumber}} | |
{{{name}}}: json[r'{{{baseName}}}'] == null ? | |
null : | |
(json[r'{{{baseName}}}'] as num).toDouble(), | |
{{/isNumber}} | |
{{^isNumber}} | |
{{^isEnum}} | |
{{{name}}}: json[r'{{{baseName}}}'] as String, | |
{{/isEnum}} | |
{{#isEnum}} | |
{{{name}}}: {{{datatypeWithEnum}}}.fromJson(json[r'{{{baseName}}}'] as Map<String, dynamic>), | |
{{/isEnum}} | |
{{/isNumber}} | |
{{/isMap}} | |
{{/isArray}} | |
{{/complexType}} | |
{{/isDate}} | |
{{/isDateTime}} | |
{{/vars}} | |
); | |
static List<{{{classname}}}> listFromJson(List<dynamic> json, {bool emptyIsNull, bool growable,}) => | |
json?.isEmpty == true | |
? true == emptyIsNull ? null : <{{{classname}}}>[] | |
: (json as List<Map<String, dynamic>>).map({{{classname}}}.fromJson).toList(growable: true == growable); | |
static Map<String, {{{classname}}}> mapFromJson(Map<String, dynamic> json) { | |
final map = <String, {{{classname}}}>{}; | |
if (json?.isNotEmpty == true) { | |
json.forEach((key, value) => map[key] = {{{classname}}}.fromJson(value as Map<String, dynamic>)); | |
} | |
return map; | |
} | |
// maps a json object with a list of {{{classname}}}-objects as value to a dart map | |
static Map<String, List<{{{classname}}}>> mapListFromJson(Map<String, dynamic> json, {bool emptyIsNull, bool growable,}) { | |
final map = <String, List<{{{classname}}}>>{}; | |
if (json?.isNotEmpty == true) { | |
json.forEach((key, value) { | |
map[key] = {{{classname}}}.listFromJson(value as List<dynamic>, emptyIsNull: emptyIsNull, growable: growable,); | |
}); | |
} | |
return map; | |
} | |
} | |
{{#vars}} | |
{{#isEnum}} | |
{{^isContainer}} | |
{{>enum_inline}} | |
{{/isContainer}} | |
{{#isContainer}} | |
{{#mostInnerItems}} | |
{{>enum_inline}} | |
{{/mostInnerItems}} | |
{{/isContainer}} | |
{{/isEnum}} | |
{{/vars}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{{#description}}/// {{{description}}}{{/description}} | |
class {{{classname}}} { | |
/// Instantiate a new enum with the provided [value]. | |
const {{{classname}}}._(this.value); | |
/// The underlying value of this enum member. | |
final {{{dataType}}} value; | |
@override | |
bool operator ==(Object other) => identical(this, other) || | |
other is {{{classname}}} && other.value == value; | |
@override | |
int get hashCode => toString().hashCode; | |
@override | |
String toString() => value{{^isString}}.toString(){{/isString}}; | |
{{{dataType}}} toJson() => value; | |
{{#allowableValues}} | |
{{#enumVars}} | |
static const {{{name}}} = {{{classname}}}._({{#isString}}r{{/isString}}{{{value}}}); | |
{{/enumVars}} | |
{{/allowableValues}} | |
/// List of all possible values in this [enum][{{{classname}}}]. | |
static const values = <{{{classname}}}>[ | |
{{#allowableValues}} | |
{{#enumVars}} | |
{{{name}}}, | |
{{/enumVars}} | |
{{/allowableValues}} | |
]; | |
static {{{classname}}} fromJson(dynamic value) => | |
{{{classname}}}TypeTransformer().decode(value); | |
static List<{{{classname}}}> listFromJson(List<dynamic> json, {bool emptyIsNull, bool growable,}) => | |
json == null || json.isEmpty | |
? true == emptyIsNull ? null : <{{{classname}}}>[] | |
: json | |
.map({{{classname}}}.fromJson) | |
.toList(growable: true == growable); | |
} | |
/// Transformation class that can [encode] an instance of [{{{classname}}}] to {{{dataType}}}, | |
/// and [decode] dynamic data back to [{{{classname}}}]. | |
class {{{classname}}}TypeTransformer { | |
const {{{classname}}}TypeTransformer._(); | |
factory {{{classname}}}TypeTransformer() => _instance ??= {{{classname}}}TypeTransformer._(); | |
{{{dataType}}} encode({{{classname}}} data) => data.value; | |
/// Decodes a [dynamic value][data] to a {{{classname}}}. | |
/// | |
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, | |
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data] | |
/// cannot be decoded successfully, then an [UnimplementedError] is thrown. | |
/// | |
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed, | |
/// and users are still using an old app with the old code. | |
{{{classname}}} decode(dynamic data, {bool allowNull}) { | |
switch (data as String) { | |
{{#allowableValues}} | |
{{#enumVars}} | |
case {{#isString}}r{{/isString}}{{{value}}}: return {{{classname}}}.{{{name}}}; | |
{{/enumVars}} | |
{{/allowableValues}} | |
default: | |
if (allowNull == false) { | |
throw ArgumentError('Unknown enum value to decode: $data'); | |
} | |
} | |
return null; | |
} | |
/// Singleton [{{{classname}}}TypeTransformer] instance. | |
static {{{classname}}}TypeTransformer _instance; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{{#description}}/// {{{description}}}{{/description}} | |
class {{{enumName}}} { | |
/// Instantiate a new enum with the provided [value]. | |
const {{{enumName}}}._(this.value); | |
/// The underlying value of this enum member. | |
final {{{dataType}}} value; | |
@override | |
bool operator ==(Object other) => identical(this, other) || | |
other is {{{enumName}}} && other.value == value; | |
@override | |
int get hashCode => toString().hashCode; | |
@override | |
String toString() => value{{^isString}}.toString(){{/isString}}; | |
{{{dataType}}} toJson() => value; | |
{{#allowableValues}} | |
{{#enumVars}} | |
static const {{{name}}} = {{{enumName}}}._({{#isString}}r{{/isString}}{{{value}}}); | |
{{/enumVars}} | |
{{/allowableValues}} | |
/// List of all possible values in this [enum][{{{enumName}}}]. | |
static const values = <{{{enumName}}}>[ | |
{{#allowableValues}} | |
{{#enumVars}} | |
{{{name}}}, | |
{{/enumVars}} | |
{{/allowableValues}} | |
]; | |
static {{{enumName}}} fromJson(dynamic value) => | |
{{{enumName}}}TypeTransformer().decode(value); | |
static List<{{{enumName}}}> listFromJson(List<dynamic> json, {bool emptyIsNull, bool growable,}) => | |
json == null || json.isEmpty | |
? true == emptyIsNull ? null : <{{{enumName}}}>[] | |
: json | |
.map((value) => {{{enumName}}}.fromJson(value)) | |
.toList(growable: true == growable); | |
} | |
/// Transformation class that can [encode] an instance of [{{{enumName}}}] to {{{dataType}}}, | |
/// and [decode] dynamic data back to [{{{enumName}}}]. | |
class {{{enumName}}}TypeTransformer { | |
const {{{enumName}}}TypeTransformer._(); | |
factory {{{enumName}}}TypeTransformer() => _instance ??= {{{enumName}}}TypeTransformer._(); | |
{{{dataType}}} encode({{{enumName}}} data) => data.value; | |
/// Decodes a [dynamic value][data] to a {{{enumName}}}. | |
/// | |
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, | |
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data] | |
/// cannot be decoded successfully, then an [UnimplementedError] is thrown. | |
/// | |
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed, | |
/// and users are still using an old app with the old code. | |
{{{enumName}}} decode(dynamic data, {bool allowNull}) { | |
switch (data as String) { | |
{{#allowableValues}} | |
{{#enumVars}} | |
case {{#isString}}r{{/isString}}{{{value}}}: return {{{enumName}}}.{{{name}}}; | |
{{/enumVars}} | |
{{/allowableValues}} | |
default: | |
if (allowNull == false) { | |
throw ArgumentError('Unknown enum value to decode: $data'); | |
} | |
} | |
return null; | |
} | |
/// Singleton [{{{enumName}}}TypeTransformer] instance. | |
static {{{enumName}}}TypeTransformer _instance; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment