Browse Source

Merge pull request #130 from provokateurin/feature/response-headers

Feature/response headers
pull/131/head
Kate 2 years ago committed by GitHub
parent
commit
33c092368f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/dynamite/lib/src/models/components.dart
  2. 22
      packages/dynamite/lib/src/models/header.dart
  3. 34
      packages/dynamite/lib/src/models/header.g.dart
  4. 6
      packages/dynamite/lib/src/models/info.dart
  5. 6
      packages/dynamite/lib/src/models/info.g.dart
  6. 4
      packages/dynamite/lib/src/models/license.dart
  7. 2
      packages/dynamite/lib/src/models/media_type.dart
  8. 10
      packages/dynamite/lib/src/models/open_api.dart
  9. 10
      packages/dynamite/lib/src/models/operation.dart
  10. 6
      packages/dynamite/lib/src/models/parameter.dart
  11. 20
      packages/dynamite/lib/src/models/path_item.dart
  12. 6
      packages/dynamite/lib/src/models/request_body.dart
  13. 6
      packages/dynamite/lib/src/models/response.dart
  14. 6
      packages/dynamite/lib/src/models/response.g.dart
  15. 26
      packages/dynamite/lib/src/models/schema.dart
  16. 4
      packages/dynamite/lib/src/models/security_scheme.dart
  17. 2
      packages/dynamite/lib/src/models/server.dart
  18. 4
      packages/dynamite/lib/src/models/server_variable.dart
  19. 175
      packages/dynamite/lib/src/openapi_builder.dart

4
packages/dynamite/lib/src/models/components.dart

@ -7,8 +7,8 @@ part 'components.g.dart';
@JsonSerializable() @JsonSerializable()
class Components { class Components {
Components({ Components({
required this.securitySchemes, this.securitySchemes,
required this.schemas, this.schemas,
}); });
factory Components.fromJson(final Map<String, dynamic> json) => _$ComponentsFromJson(json); factory Components.fromJson(final Map<String, dynamic> json) => _$ComponentsFromJson(json);

22
packages/dynamite/lib/src/models/header.dart

@ -0,0 +1,22 @@
import 'package:dynamite/src/models/schema.dart';
import 'package:json_annotation/json_annotation.dart';
part 'header.g.dart';
@JsonSerializable()
class Header {
Header({
this.description,
this.required,
this.schema,
});
factory Header.fromJson(final Map<String, dynamic> json) => _$HeaderFromJson(json);
Map<String, dynamic> toJson() => _$HeaderToJson(this);
final String? description;
final bool? required;
final Schema? schema;
}

34
packages/dynamite/lib/src/models/header.g.dart

@ -0,0 +1,34 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'header.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Header _$HeaderFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
allowedKeys: const ['description', 'required', 'schema'],
);
return Header(
description: json['description'] as String?,
required: json['required'] as bool?,
schema: json['schema'] == null ? null : Schema.fromJson(json['schema'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$HeaderToJson(Header instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('description', instance.description);
writeNotNull('required', instance.required);
writeNotNull('schema', instance.schema?.toJson());
return val;
}

6
packages/dynamite/lib/src/models/info.dart

@ -8,8 +8,8 @@ class Info {
Info({ Info({
required this.title, required this.title,
required this.version, required this.version,
required this.description,
required this.license, required this.license,
this.description,
}); });
factory Info.fromJson(final Map<String, dynamic> json) => _$InfoFromJson(json); factory Info.fromJson(final Map<String, dynamic> json) => _$InfoFromJson(json);
@ -19,7 +19,7 @@ class Info {
final String version; final String version;
final String? description;
final License license; final License license;
final String? description;
} }

6
packages/dynamite/lib/src/models/info.g.dart

@ -9,13 +9,13 @@ part of 'info.dart';
Info _$InfoFromJson(Map<String, dynamic> json) { Info _$InfoFromJson(Map<String, dynamic> json) {
$checkKeys( $checkKeys(
json, json,
allowedKeys: const ['title', 'version', 'description', 'license'], allowedKeys: const ['title', 'version', 'license', 'description'],
); );
return Info( return Info(
title: json['title'] as String, title: json['title'] as String,
version: json['version'] as String, version: json['version'] as String,
description: json['description'] as String?,
license: License.fromJson(json['license'] as Map<String, dynamic>), license: License.fromJson(json['license'] as Map<String, dynamic>),
description: json['description'] as String?,
); );
} }
@ -23,6 +23,7 @@ Map<String, dynamic> _$InfoToJson(Info instance) {
final val = <String, dynamic>{ final val = <String, dynamic>{
'title': instance.title, 'title': instance.title,
'version': instance.version, 'version': instance.version,
'license': instance.license.toJson(),
}; };
void writeNotNull(String key, dynamic value) { void writeNotNull(String key, dynamic value) {
@ -32,6 +33,5 @@ Map<String, dynamic> _$InfoToJson(Info instance) {
} }
writeNotNull('description', instance.description); writeNotNull('description', instance.description);
val['license'] = instance.license.toJson();
return val; return val;
} }

4
packages/dynamite/lib/src/models/license.dart

@ -6,8 +6,8 @@ part 'license.g.dart';
class License { class License {
License({ License({
required this.name, required this.name,
required this.identifier, this.identifier,
required this.url, this.url,
}); });
factory License.fromJson(final Map<String, dynamic> json) => _$LicenseFromJson(json); factory License.fromJson(final Map<String, dynamic> json) => _$LicenseFromJson(json);

2
packages/dynamite/lib/src/models/media_type.dart

@ -6,7 +6,7 @@ part 'media_type.g.dart';
@JsonSerializable() @JsonSerializable()
class MediaType { class MediaType {
MediaType({ MediaType({
required this.schema, this.schema,
}); });
factory MediaType.fromJson(final Map<String, dynamic> json) => _$MediaTypeFromJson(json); factory MediaType.fromJson(final Map<String, dynamic> json) => _$MediaTypeFromJson(json);

10
packages/dynamite/lib/src/models/open_api.dart

@ -14,11 +14,11 @@ class OpenAPI {
OpenAPI({ OpenAPI({
required this.version, required this.version,
required this.info, required this.info,
required this.servers, this.servers,
required this.security, this.security,
required this.tags, this.tags,
required this.components, this.components,
required this.paths, this.paths,
}); });
factory OpenAPI.fromJson(final Map<String, dynamic> json) => _$OpenAPIFromJson(json); factory OpenAPI.fromJson(final Map<String, dynamic> json) => _$OpenAPIFromJson(json);

10
packages/dynamite/lib/src/models/operation.dart

@ -9,11 +9,11 @@ part 'operation.g.dart';
@JsonSerializable() @JsonSerializable()
class Operation { class Operation {
Operation({ Operation({
required this.operationId, this.operationId,
required this.tags, this.tags,
required this.parameters, this.parameters,
required this.requestBody, this.requestBody,
required this.responses, this.responses,
}); });
factory Operation.fromJson(final Map<String, dynamic> json) => _$OperationFromJson(json); factory Operation.fromJson(final Map<String, dynamic> json) => _$OperationFromJson(json);

6
packages/dynamite/lib/src/models/parameter.dart

@ -8,9 +8,9 @@ class Parameter {
Parameter({ Parameter({
required this.name, required this.name,
required this.in_, required this.in_,
required this.description, this.description,
required this.required, this.required,
required this.schema, this.schema,
}); });
factory Parameter.fromJson(final Map<String, dynamic> json) => _$ParameterFromJson(json); factory Parameter.fromJson(final Map<String, dynamic> json) => _$ParameterFromJson(json);

20
packages/dynamite/lib/src/models/path_item.dart

@ -7,16 +7,16 @@ part 'path_item.g.dart';
@JsonSerializable() @JsonSerializable()
class PathItem { class PathItem {
PathItem({ PathItem({
required this.description, this.description,
required this.parameters, this.parameters,
required this.get, this.get,
required this.put, this.put,
required this.post, this.post,
required this.delete, this.delete,
required this.options, this.options,
required this.head, this.head,
required this.patch, this.patch,
required this.trace, this.trace,
}); });
factory PathItem.fromJson(final Map<String, dynamic> json) => _$PathItemFromJson(json); factory PathItem.fromJson(final Map<String, dynamic> json) => _$PathItemFromJson(json);

6
packages/dynamite/lib/src/models/request_body.dart

@ -6,9 +6,9 @@ part 'request_body.g.dart';
@JsonSerializable() @JsonSerializable()
class RequestBody { class RequestBody {
RequestBody({ RequestBody({
required this.description, this.description,
required this.content, this.content,
required this.required, this.required,
}); });
factory RequestBody.fromJson(final Map<String, dynamic> json) => _$RequestBodyFromJson(json); factory RequestBody.fromJson(final Map<String, dynamic> json) => _$RequestBodyFromJson(json);

6
packages/dynamite/lib/src/models/response.dart

@ -1,3 +1,4 @@
import 'package:dynamite/src/models/header.dart';
import 'package:dynamite/src/models/media_type.dart'; import 'package:dynamite/src/models/media_type.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
@ -7,7 +8,8 @@ part 'response.g.dart';
class Response { class Response {
Response({ Response({
required this.description, required this.description,
required this.content, this.content,
this.headers,
}); });
factory Response.fromJson(final Map<String, dynamic> json) => _$ResponseFromJson(json); factory Response.fromJson(final Map<String, dynamic> json) => _$ResponseFromJson(json);
@ -16,4 +18,6 @@ class Response {
final String description; final String description;
final Map<String, MediaType>? content; final Map<String, MediaType>? content;
final Map<String, Header>? headers;
} }

6
packages/dynamite/lib/src/models/response.g.dart

@ -9,13 +9,16 @@ part of 'response.dart';
Response _$ResponseFromJson(Map<String, dynamic> json) { Response _$ResponseFromJson(Map<String, dynamic> json) {
$checkKeys( $checkKeys(
json, json,
allowedKeys: const ['description', 'content'], allowedKeys: const ['description', 'content', 'headers'],
); );
return Response( return Response(
description: json['description'] as String, description: json['description'] as String,
content: (json['content'] as Map<String, dynamic>?)?.map( content: (json['content'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, MediaType.fromJson(e as Map<String, dynamic>)), (k, e) => MapEntry(k, MediaType.fromJson(e as Map<String, dynamic>)),
), ),
headers: (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, Header.fromJson(e as Map<String, dynamic>)),
),
); );
} }
@ -31,5 +34,6 @@ Map<String, dynamic> _$ResponseToJson(Response instance) {
} }
writeNotNull('content', instance.content?.map((k, e) => MapEntry(k, e.toJson()))); writeNotNull('content', instance.content?.map((k, e) => MapEntry(k, e.toJson())));
writeNotNull('headers', instance.headers?.map((k, e) => MapEntry(k, e.toJson())));
return val; return val;
} }

26
packages/dynamite/lib/src/models/schema.dart

@ -5,19 +5,19 @@ part 'schema.g.dart';
@JsonSerializable() @JsonSerializable()
class Schema { class Schema {
Schema({ Schema({
required this.ref, this.ref,
required this.oneOf, this.oneOf,
required this.anyOf, this.anyOf,
required this.allOf, this.allOf,
required this.description, this.description,
required this.deprecated, this.deprecated,
required this.type, this.type,
required this.format, this.format,
required this.default_, this.default_,
required this.enum_, this.enum_,
required this.properties, this.properties,
required this.required, this.required,
required this.items, this.items,
}); });
factory Schema.fromJson(final Map<String, dynamic> json) => _$SchemaFromJson(json); factory Schema.fromJson(final Map<String, dynamic> json) => _$SchemaFromJson(json);

4
packages/dynamite/lib/src/models/security_scheme.dart

@ -6,8 +6,8 @@ part 'security_scheme.g.dart';
class SecurityScheme { class SecurityScheme {
SecurityScheme({ SecurityScheme({
required this.type, required this.type,
required this.description, this.description,
required this.scheme, this.scheme,
}); });
factory SecurityScheme.fromJson(final Map<String, dynamic> json) => _$SecuritySchemeFromJson(json); factory SecurityScheme.fromJson(final Map<String, dynamic> json) => _$SecuritySchemeFromJson(json);

2
packages/dynamite/lib/src/models/server.dart

@ -7,7 +7,7 @@ part 'server.g.dart';
class Server { class Server {
Server({ Server({
required this.url, required this.url,
required this.variables, this.variables,
}); });
factory Server.fromJson(final Map<String, dynamic> json) => _$ServerFromJson(json); factory Server.fromJson(final Map<String, dynamic> json) => _$ServerFromJson(json);

4
packages/dynamite/lib/src/models/server_variable.dart

@ -6,8 +6,8 @@ part 'server_variable.g.dart';
class ServerVariable { class ServerVariable {
ServerVariable({ ServerVariable({
required this.default_, required this.default_,
required this.enum_, this.enum_,
required this.description, this.description,
}); });
factory ServerVariable.fromJson(final Map<String, dynamic> json) => _$ServerVariableFromJson(json); factory ServerVariable.fromJson(final Map<String, dynamic> json) => _$ServerVariableFromJson(json);

175
packages/dynamite/lib/src/openapi_builder.dart

@ -76,6 +76,54 @@ class OpenAPIBuilder implements Builder {
Class( Class(
(final b) => b (final b) => b
..name = 'Response' ..name = 'Response'
..types.addAll([
refer('T'),
refer('U'),
])
..fields.addAll([
Field(
(final b) => b
..name = 'data'
..type = refer('T')
..modifier = FieldModifier.final$,
),
Field(
(final b) => b
..name = 'headers'
..type = refer('U')
..modifier = FieldModifier.final$,
),
])
..constructors.add(
Constructor(
(final b) => b
..requiredParameters.addAll(
['data', 'headers'].map(
(final name) => Parameter(
(final b) => b
..name = name
..toThis = true,
),
),
),
),
)
..methods.add(
Method(
(final b) => b
..name = 'toString'
..returns = refer('String')
..annotations.add(refer('override'))
..lambda = true
..body = const Code(
r"'Response(data: $data, headers: $headers)'",
),
),
),
).accept(emitter).toString(),
Class(
(final b) => b
..name = '_Response'
..fields.addAll([ ..fields.addAll([
Field( Field(
(final b) => b (final b) => b
@ -117,15 +165,16 @@ class OpenAPIBuilder implements Builder {
..returns = refer('String') ..returns = refer('String')
..annotations.add(refer('override')) ..annotations.add(refer('override'))
..lambda = true ..lambda = true
..body = ..body = const Code(
const Code(r"'Response(statusCode: $statusCode, headers: $headers, body: ${utf8.decode(body)})'"), r"'_Response(statusCode: $statusCode, headers: $headers, body: ${utf8.decode(body)})'",
),
), ),
), ),
).accept(emitter).toString(), ).accept(emitter).toString(),
Class( Class(
(final b) => b (final b) => b
..name = 'ApiException' ..name = 'ApiException'
..extend = refer('Response') ..extend = refer('_Response')
..implements.add(refer('Exception')) ..implements.add(refer('Exception'))
..constructors.addAll( ..constructors.addAll(
[ [
@ -150,7 +199,7 @@ class OpenAPIBuilder implements Builder {
Parameter( Parameter(
(final b) => b (final b) => b
..name = 'response' ..name = 'response'
..type = refer('Response'), ..type = refer('_Response'),
), ),
) )
..body = const Code('ApiException(response.statusCode, response.headers, response.body,)'), ..body = const Code('ApiException(response.statusCode, response.headers, response.body,)'),
@ -258,6 +307,7 @@ class OpenAPIBuilder implements Builder {
final Schema schema, { final Schema schema, {
final bool ignoreEnum = false, final bool ignoreEnum = false,
final Map<String, String>? extraJsonSerializableValues, final Map<String, String>? extraJsonSerializableValues,
final Map<String, Map<String, String>>? extraJsonKeyValues,
}) { }) {
TypeResolveResult? result; TypeResolveResult? result;
if (schema.ref != null) { if (schema.ref != null) {
@ -557,6 +607,15 @@ class OpenAPIBuilder implements Builder {
[], [],
{ {
'name': refer("'$propertyName'"), 'name': refer("'$propertyName'"),
if (extraJsonKeyValues != null) ...{
for (final p in extraJsonKeyValues.keys) ...{
if (p == propertyName) ...{
for (final key in extraJsonKeyValues[p]!.keys) ...{
key: refer(extraJsonKeyValues[p]![key]!),
},
},
},
},
}, },
), ),
); );
@ -699,14 +758,6 @@ class OpenAPIBuilder implements Builder {
paths[path] = PathItem( paths[path] = PathItem(
description: pathItem.description, description: pathItem.description,
parameters: pathItem.parameters, parameters: pathItem.parameters,
get: null,
put: null,
post: null,
delete: null,
options: null,
head: null,
patch: null,
trace: null,
); );
} }
paths[path] = paths[path]!.copyWithOperations({method: operation}); paths[path] = paths[path]!.copyWithOperations({method: operation});
@ -829,7 +880,7 @@ class OpenAPIBuilder implements Builder {
Method( Method(
(final b) => b (final b) => b
..name = 'doRequest' ..name = 'doRequest'
..returns = refer('Future<Response>') ..returns = refer('Future<_Response>')
..modifier = MethodModifier.async ..modifier = MethodModifier.async
..requiredParameters.addAll([ ..requiredParameters.addAll([
Parameter( Parameter(
@ -859,6 +910,7 @@ class OpenAPIBuilder implements Builder {
for (final header in {...baseHeaders, ...headers}.entries) { for (final header in {...baseHeaders, ...headers}.entries) {
request.headers.add(header.key, header.value); request.headers.add(header.key, header.value);
} }
<<<<<<< Updated upstream
if (body != null) { if (body != null) {
request.add(body.toList()); request.add(body.toList());
} }
@ -875,6 +927,11 @@ class OpenAPIBuilder implements Builder {
responseHeaders[name] = values.last; responseHeaders[name] = values.last;
}); });
return Response( return Response(
=======
final response = await http.Response.fromStream(await request.send());
return _Response(
>>>>>>> Stashed changes
response.statusCode, response.statusCode,
responseHeaders, responseHeaders,
await response.bodyBytes, await response.bodyBytes,
@ -913,6 +970,7 @@ class OpenAPIBuilder implements Builder {
Method( Method(
(final b) { (final b) {
final operation = paths[path]!.operations[httpMethod]!; final operation = paths[path]!.operations[httpMethod]!;
final operationId = operation.operationId ?? _toDartName('$httpMethod-$path');
final pathParameters = <spec_parameter.Parameter>[ final pathParameters = <spec_parameter.Parameter>[
if (paths[path]!.parameters != null) ...paths[path]!.parameters!, if (paths[path]!.parameters != null) ...paths[path]!.parameters!,
]; ];
@ -920,7 +978,7 @@ class OpenAPIBuilder implements Builder {
...pathParameters, ...pathParameters,
if (operation.parameters != null) ...operation.parameters!, if (operation.parameters != null) ...operation.parameters!,
]; ];
final methodName = _toDartName(operation.operationId ?? _toDartName('$httpMethod-$path')); final methodName = _toDartName(operationId);
b b
..name = methodName ..name = methodName
..modifier = MethodModifier.async; ..modifier = MethodModifier.async;
@ -986,7 +1044,7 @@ class OpenAPIBuilder implements Builder {
break; break;
case 'query': case 'query':
code.write( code.write(
"queryParameters['${parameter.name}'] = ${_toDartName(parameter.name)}$enumValueGetter.toString();", "queryParameters['${parameter.name}${result.isList ? '[]' : ''}'] = ${_toDartName(parameter.name)}$enumValueGetter${result.isList ? '.map((final x) => x.toString()).toList()' : '.toString()'};",
); );
break; break;
case 'header': case 'header':
@ -1064,6 +1122,51 @@ class OpenAPIBuilder implements Builder {
for (final statusCode in operation.responses!.keys) { for (final statusCode in operation.responses!.keys) {
final response = operation.responses![statusCode]!; final response = operation.responses![statusCode]!;
code.write('if (response.statusCode == $statusCode) {'); code.write('if (response.statusCode == $statusCode) {');
String? headersType;
String? headersValue;
if (response.headers != null) {
final identifier =
'${tag != null ? _toDartName(tag.name, uppercaseFirstCharacter: true) : null}${_toDartName(operationId, uppercaseFirstCharacter: true)}Headers';
final headerParseFunctions = <String, String>{};
for (final headerName in response.headers!.keys) {
final functionIdentifier = '_${_toDartName('${identifier}Parse$headerName')}';
headerParseFunctions[headerName] = functionIdentifier;
final result = resolveType(
identifier,
response.headers![headerName]!.schema!,
);
output.add(
'${result.typeName} $functionIdentifier(final Map data, final String key) => ${_parseFromStringFunctionForType('data[key]', result)};',
);
}
final result = resolveType(
identifier,
Schema(
type: 'object',
properties: {
for (final headerName in response.headers!.keys) ...{
headerName.toLowerCase(): response.headers![headerName]!.schema!,
},
},
),
extraJsonSerializableValues: {
'disallowUnrecognizedKeys': 'false',
},
extraJsonKeyValues: {
for (final headerName in response.headers!.keys) ...{
headerName.toLowerCase(): {
'readValue': headerParseFunctions[headerName]!,
},
},
},
);
headersType = result.typeName;
headersValue = _deserializeFunctionForType('response.headers', result);
}
String? dataType;
String? dataValue;
if (response.content != null) { if (response.content != null) {
if (response.content!.length > 1) { if (response.content!.length > 1) {
throw Exception('Can not work with multiple mime types right now'); throw Exception('Can not work with multiple mime types right now');
@ -1077,26 +1180,38 @@ class OpenAPIBuilder implements Builder {
); );
switch (mimeType) { switch (mimeType) {
case 'application/json': case 'application/json':
b.returns = refer('Future<${result.typeName}>'); dataType = result.typeName;
code.write('return ${_deserializeFunctionForType( dataValue = _deserializeFunctionForType(
result.isBaseType result.isBaseType
? 'utf8.decode(response.body)' ? 'utf8.decode(response.body)'
: 'json.decode(utf8.decode(response.body))', : 'json.decode(utf8.decode(response.body))',
result, result,
)};'); );
break; break;
case 'image/png': case 'image/png':
b.returns = refer('Future<Uint8List>'); dataType = 'Uint8List';
code.write('return response.body;'); dataValue = 'response.body';
break; break;
default: default:
throw Exception('Can not parse mime type "$mimeType"'); throw Exception('Can not parse mime type "$mimeType"');
} }
} }
}
if (headersType != null && dataType != null) {
b.returns = refer('Future<Response<$dataType, $headersType>>');
code.write('return Response<$dataType, $headersType>($dataValue, $headersValue,);');
} else if (headersType != null) {
b.returns = refer('Future<$headersType>');
code.write('return $headersValue;');
} else if (dataType != null) {
b.returns = refer('Future<$dataType>');
code.write('return $dataValue;');
} else { } else {
code.write('return;');
b.returns = refer('Future'); b.returns = refer('Future');
code.write('return;');
} }
code.write('}'); code.write('}');
} }
code.write('throw ApiException.fromResponse(response); // coverage:ignore-line\n'); code.write('throw ApiException.fromResponse(response); // coverage:ignore-line\n');
@ -1373,6 +1488,24 @@ String _deserializeFunctionForType(final String object, final TypeResolveResult
} }
} }
String _parseFromStringFunctionForType(final String object, final TypeResolveResult result) {
final o = '$object as String';
if (result.isBaseType) {
switch (result.typeName) {
case 'String':
return o;
case 'int':
return 'int.parse($o)';
default:
throw Exception('Can not parse "${result.typeName}" from String');
}
} else if (result.isEnum) {
return _parseFromStringFunctionForType(o, result.subType!);
} else {
return 'json.decode($o)';
}
}
bool _isParameterNullable(final bool? required, final dynamic default_) => !(required ?? false) && default_ == null; bool _isParameterNullable(final bool? required, final dynamic default_) => !(required ?? false) && default_ == null;
String _valueToEscapedValue(final String type, final dynamic value) => type == 'String' ? "'$value'" : value.toString(); String _valueToEscapedValue(final String type, final dynamic value) => type == 'String' ? "'$value'" : value.toString();

Loading…
Cancel
Save