diff --git a/packages/dynamite/dynamite/lib/dynamite.dart b/packages/dynamite/dynamite/lib/dynamite.dart index 190cddf9..f8fd102e 100644 --- a/packages/dynamite/dynamite/lib/dynamite.dart +++ b/packages/dynamite/dynamite/lib/dynamite.dart @@ -1,16 +1,4 @@ +// ignore: unnecessary_library_directive library dynamite; -import 'dart:convert'; - -import 'package:build/build.dart'; -import 'package:code_builder/code_builder.dart'; -import 'package:collection/collection.dart'; -import 'package:dart_style/dart_style.dart'; -import 'package:dynamite/src/models/open_api.dart'; -import 'package:dynamite/src/models/parameter.dart' as spec_parameter; -import 'package:dynamite/src/models/path_item.dart'; -import 'package:dynamite/src/models/schema.dart'; -import 'package:dynamite/src/type_result/type_result.dart'; -import 'package:path/path.dart' as p; - -part 'src/openapi_builder.dart'; +export 'src/openapi_builder.dart'; diff --git a/packages/dynamite/dynamite/lib/src/builder/client.dart b/packages/dynamite/dynamite/lib/src/builder/client.dart new file mode 100644 index 00000000..cfac493f --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/client.dart @@ -0,0 +1,653 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:collection/collection.dart'; +import 'package:dynamite/src/builder/resolve_object.dart'; +import 'package:dynamite/src/builder/resolve_type.dart'; +import 'package:dynamite/src/builder/state.dart'; +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/helpers/dynamite.dart'; +import 'package:dynamite/src/helpers/type_result.dart'; +import 'package:dynamite/src/models/open_api.dart'; +import 'package:dynamite/src/models/path_item.dart'; +import 'package:dynamite/src/models/schema.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +List generateDynamiteOverrides(final State state) => [ + Class( + (final b) => b + ..name = '${state.classPrefix}Response' + ..types.addAll([ + refer('T'), + refer('U'), + ]) + ..extend = refer('DynamiteResponse') + ..constructors.add( + Constructor( + (final b) => b + ..requiredParameters.addAll( + ['data', 'headers'].map( + (final name) => Parameter( + (final b) => b + ..name = name + ..toSuper = true, + ), + ), + ), + ), + ) + ..methods.add( + Method( + (final b) => b + ..name = 'toString' + ..returns = refer('String') + ..annotations.add(refer('override')) + ..lambda = true + ..body = Code( + "'${state.classPrefix}Response(data: \$data, headers: \$headers)'", + ), + ), + ), + ), + Class( + (final b) => b + ..name = '${state.classPrefix}ApiException' + ..extend = refer('DynamiteApiException') + ..constructors.add( + Constructor( + (final b) => b + ..requiredParameters.addAll( + ['statusCode', 'headers', 'body'].map( + (final name) => Parameter( + (final b) => b + ..name = name + ..toSuper = true, + ), + ), + ), + ), + ) + ..methods.addAll([ + Method( + (final b) => b + ..name = 'fromResponse' + ..returns = refer('Future<${state.classPrefix}ApiException>') + ..static = true + ..modifier = MethodModifier.async + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'response' + ..type = refer('HttpClientResponse'), + ), + ) + ..body = Code(''' +String body; +try { + body = await response.body; +} on FormatException { + body = 'binary'; +} + +return ${state.classPrefix}ApiException( + response.statusCode, + response.responseHeaders, + body, +); +'''), + ), + Method( + (final b) => b + ..name = 'toString' + ..returns = refer('String') + ..annotations.add(refer('override')) + ..lambda = true + ..body = Code( + "'${state.classPrefix}ApiException(statusCode: \$statusCode, headers: \$headers, body: \$body)'", + ), + ), + ]), + ), + ]; + +Iterable generateClients( + final OpenAPI spec, + final State state, +) sync* { + yield* generateDynamiteOverrides(state); + + final tags = generateTags(spec); + yield buildRootClient(spec, state, tags); + + for (final tag in tags) { + yield buildClient(spec, state, tags, tag); + } +} + +Class buildRootClient( + final OpenAPI spec, + final State state, + final List tags, +) => + Class( + (final b) { + b + ..extend = refer('DynamiteClient') + ..name = '${state.classPrefix}Client' + ..docs.addAll(spec.formattedTagsFor(null)) + ..constructors.addAll([ + Constructor( + (final b) => b + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'baseURL' + ..toSuper = true, + ), + ) + ..optionalParameters.addAll([ + Parameter( + (final b) => b + ..name = 'baseHeaders' + ..toSuper = true + ..named = true, + ), + Parameter( + (final b) => b + ..name = 'userAgent' + ..toSuper = true + ..named = true, + ), + Parameter( + (final b) => b + ..name = 'httpClient' + ..toSuper = true + ..named = true, + ), + Parameter( + (final b) => b + ..name = 'cookieJar' + ..toSuper = true + ..named = true, + ), + if (spec.hasAnySecurity) ...[ + Parameter( + (final b) => b + ..name = 'authentications' + ..toSuper = true + ..named = true, + ), + ], + ]), + ), + Constructor( + (final b) => b + ..name = 'fromClient' + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'client' + ..type = refer('DynamiteClient'), + ), + ) + ..initializers.add( + const Code(''' +super( + client.baseURL, + baseHeaders: client.baseHeaders, + httpClient: client.httpClient, + cookieJar: client.cookieJar, + authentications: client.authentications, +) +'''), + ), + ), + ]); + + for (final tag in tags.where((final t) => !t.contains('/'))) { + final client = '${state.classPrefix}${clientName(tag)}'; + + b.methods.add( + Method( + (final b) => b + ..name = toDartName(tag) + ..lambda = true + ..type = MethodType.getter + ..returns = refer(client) + ..body = Code('$client(this)'), + ), + ); + } + + b.methods.addAll(buildTags(spec, state, tags, null)); + }, + ); + +Class buildClient( + final OpenAPI spec, + final State state, + final List tags, + final String tag, +) => + Class( + (final b) { + b + ..name = '${state.classPrefix}${clientName(tag)}' + ..docs.addAll(spec.formattedTagsFor(tag)) + ..constructors.add( + Constructor( + (final b) => b.requiredParameters.add( + Parameter( + (final b) => b + ..name = '_rootClient' + ..toThis = true, + ), + ), + ), + ) + ..fields.add( + Field( + (final b) => b + ..name = '_rootClient' + ..type = refer('${state.classPrefix}Client') + ..modifier = FieldModifier.final$, + ), + ); + + for (final t in tags.where((final t) => t.startsWith('$tag/'))) { + b.methods.add( + Method( + (final b) => b + ..name = toDartName(t.substring('$tag/'.length)) + ..lambda = true + ..type = MethodType.getter + ..returns = refer('${state.classPrefix}${clientName(t)}') + ..body = Code('${state.classPrefix}${clientName(t)}(_rootClient)'), + ), + ); + } + + b.methods.addAll(buildTags(spec, state, tags, tag)); + }, + ); + +Iterable buildTags( + final OpenAPI spec, + final State state, + final List tags, + final String? tag, +) sync* { + final isRootClient = tag == null; + final paths = generatePaths(spec, tag); + + for (final pathEntry in paths.entries) { + for (final operationEntry in pathEntry.value.operations.entries) { + yield Method( + (final b) { + final httpMethod = operationEntry.key; + final operation = operationEntry.value; + final operationId = operation.operationId ?? toDartName('$httpMethod-${pathEntry.key}'); + final parameters = [ + ...?pathEntry.value.parameters, + ...?operation.parameters, + ]..sort(sortRequiredParameters); + b + ..name = toDartName(filterMethodName(operationId, tag ?? '')) + ..modifier = MethodModifier.async + ..docs.addAll(operation.formattedDescription); + if (operation.deprecated ?? false) { + b.annotations.add(refer('Deprecated').call([refer("''")])); + } + + final acceptHeader = operation.responses?.values + .map((final response) => response.content?.keys) + .whereNotNull() + .expand((final element) => element) + .toSet() + .join(',') ?? + ''; + final code = StringBuffer(''' +var _path = '${pathEntry.key}'; +final _queryParameters = {}; +final _headers = {${acceptHeader.isNotEmpty ? "'Accept': '$acceptHeader'," : ''}}; +Uint8List? _body; +'''); + + final security = operation.security ?? spec.security ?? []; + final securityRequirements = security.where((final requirement) => requirement.isNotEmpty); + final isOptionalSecurity = securityRequirements.length != security.length; + code.write(' // coverage:ignore-start\n'); + for (final requirement in securityRequirements) { + final securityScheme = spec.components!.securitySchemes![requirement.keys.single]!; + code.write(''' +if (${isRootClient ? 'this' : '_rootClient'}.authentications.where((final a) => a.type == '${securityScheme.type}' && a.scheme == '${securityScheme.scheme}').isNotEmpty) { + _headers.addAll(${isRootClient ? 'this' : '_rootClient'}.authentications.singleWhere((final a) => a.type == '${securityScheme.type}' && a.scheme == '${securityScheme.scheme}').headers); +} +'''); + if (securityRequirements.last != requirement) { + code.write('else '); + } + } + if (securityRequirements.isNotEmpty && !isOptionalSecurity) { + code.write(''' +else { + throw Exception('Missing authentication for ${securityRequirements.map((final r) => r.keys.single).join(' or ')}'); +} +'''); + } + code.write(' // coverage:ignore-end\n'); + + for (final parameter in parameters) { + final dartParameterNullable = isDartParameterNullable( + parameter.required, + parameter.schema, + ); + + final result = resolveType( + spec, + state, + toDartName( + '$operationId-${parameter.name}', + uppercaseFirstCharacter: true, + ), + parameter.schema!, + nullable: dartParameterNullable, + ).dartType; + + if (result.name == 'String') { + if (parameter.schema?.pattern != null) { + code.write(''' +if (!RegExp(r'${parameter.schema!.pattern!}').hasMatch(${toDartName(parameter.name)})) { + throw Exception('Invalid value "\$${toDartName(parameter.name)}" for parameter "${toDartName(parameter.name)}" with pattern "\${r'${parameter.schema!.pattern!}'}"'); // coverage:ignore-line +} +'''); + } + if (parameter.schema?.minLength != null) { + code.write(''' +if (${toDartName(parameter.name)}.length < ${parameter.schema!.minLength!}) { + throw Exception('Parameter "${toDartName(parameter.name)}" has to be at least ${parameter.schema!.minLength!} characters long'); // coverage:ignore-line +} +'''); + } + if (parameter.schema?.maxLength != null) { + code.write(''' +if (${toDartName(parameter.name)}.length > ${parameter.schema!.maxLength!}) { + throw Exception('Parameter "${toDartName(parameter.name)}" has to be at most ${parameter.schema!.maxLength!} characters long'); // coverage:ignore-line +} +'''); + } + } + + final defaultValueCode = parameter.schema?.default_ != null + ? valueToEscapedValue(result, parameter.schema!.default_.toString()) + : null; + + b.optionalParameters.add( + Parameter( + (final b) { + b + ..named = true + ..name = toDartName(parameter.name) + ..required = parameter.isDartRequired; + if (parameter.schema != null) { + b.type = refer(result.nullableName); + } + if (defaultValueCode != null) { + b.defaultTo = Code(defaultValueCode); + } + }, + ), + ); + + if (dartParameterNullable) { + code.write('if (${toDartName(parameter.name)} != null) {'); + } + final value = result.encode( + toDartName(parameter.name), + onlyChildren: result is TypeResultList && parameter.in_ == 'query', + ); + if (defaultValueCode != null && parameter.in_ == 'query') { + code.write('if (${toDartName(parameter.name)} != $defaultValueCode) {'); + } + switch (parameter.in_) { + case 'path': + code.write( + "_path = _path.replaceAll('{${parameter.name}}', Uri.encodeQueryComponent($value));", + ); + case 'query': + code.write( + "_queryParameters['${parameter.name}'] = $value;", + ); + case 'header': + code.write( + "_headers['${parameter.name}'] = $value;", + ); + default: + throw Exception('Can not work with parameter in "${parameter.in_}"'); + } + if (defaultValueCode != null && parameter.in_ == 'query') { + code.write('}'); + } + if (dartParameterNullable) { + code.write('}'); + } + } + + if (operation.requestBody != null) { + if (operation.requestBody!.content!.length > 1) { + throw Exception('Can not work with multiple mime types right now'); + } + for (final content in operation.requestBody!.content!.entries) { + final mimeType = content.key; + final mediaType = content.value; + + code.write("_headers['Content-Type'] = '$mimeType';"); + + final dartParameterNullable = isDartParameterNullable( + operation.requestBody!.required, + mediaType.schema, + ); + + final result = resolveType( + spec, + state, + toDartName('$operationId-request-$mimeType', uppercaseFirstCharacter: true), + mediaType.schema!, + nullable: dartParameterNullable, + ); + final parameterName = toDartName(result.name.replaceFirst(state.classPrefix, '')); + switch (mimeType) { + case 'application/json': + case 'application/x-www-form-urlencoded': + final dartParameterRequired = isRequired( + operation.requestBody!.required, + mediaType.schema?.default_, + ); + b.optionalParameters.add( + Parameter( + (final b) => b + ..name = parameterName + ..type = refer(result.nullableName) + ..named = true + ..required = dartParameterRequired, + ), + ); + + if (dartParameterNullable) { + code.write('if ($parameterName != null) {'); + } + code.write( + '_body = utf8.encode(${result.encode(parameterName, mimeType: mimeType)}) as Uint8List;', + ); + if (dartParameterNullable) { + code.write('}'); + } + default: + throw Exception('Can not parse mime type "$mimeType"'); + } + } + } + + code.write( + ''' +final _response = await ${isRootClient ? 'this' : '_rootClient'}.doRequest( + '$httpMethod', + Uri(path: _path, queryParameters: _queryParameters.isNotEmpty ? _queryParameters : null), + _headers, + _body, +); +''', + ); + + if (operation.responses != null) { + if (operation.responses!.length > 1) { + throw Exception('Can not work with multiple status codes right now'); + } + for (final responseEntry in operation.responses!.entries) { + final statusCode = responseEntry.key; + final response = responseEntry.value; + code.write('if (_response.statusCode == $statusCode) {'); + + String? headersType; + String? headersValue; + if (response.headers != null) { + final identifier = + '${tag != null ? toDartName(tag, uppercaseFirstCharacter: true) : null}${toDartName(operationId, uppercaseFirstCharacter: true)}Headers'; + final result = resolveObject( + spec, + state, + identifier, + Schema( + properties: response.headers!.map( + (final headerName, final value) => MapEntry( + headerName.toLowerCase(), + value.schema!, + ), + ), + ), + isHeader: true, + ); + headersType = result.name; + headersValue = result.deserialize('_response.responseHeaders'); + } + + String? dataType; + String? dataValue; + bool? dataNeedsAwait; + if (response.content != null) { + if (response.content!.length > 1) { + throw Exception('Can not work with multiple mime types right now'); + } + for (final content in response.content!.entries) { + final mimeType = content.key; + final mediaType = content.value; + + final result = resolveType( + spec, + state, + toDartName( + '$operationId-response-$statusCode-$mimeType', + uppercaseFirstCharacter: true, + ), + mediaType.schema!, + ); + + if (mimeType == '*/*' || mimeType == 'application/octet-stream' || mimeType.startsWith('image/')) { + dataType = 'Uint8List'; + dataValue = '_response.bodyBytes'; + dataNeedsAwait = true; + } else if (mimeType.startsWith('text/') || mimeType == 'application/javascript') { + dataType = 'String'; + dataValue = '_response.body'; + dataNeedsAwait = true; + } else if (mimeType == 'application/json') { + dataType = result.name; + if (result.name == 'dynamic') { + dataValue = ''; + } else if (result.name == 'String') { + dataValue = '_response.body'; + dataNeedsAwait = true; + } else if (result is TypeResultEnum || result is TypeResultBase) { + dataValue = result.deserialize(result.decode('await _response.body')); + dataNeedsAwait = false; + } else { + dataValue = result.deserialize('await _response.jsonBody'); + dataNeedsAwait = false; + } + } else { + throw Exception('Can not parse mime type "$mimeType"'); + } + } + } + + if (headersType != null && dataType != null) { + b.returns = refer('Future<${state.classPrefix}Response<$dataType, $headersType>>'); + code.write( + 'return ${state.classPrefix}Response<$dataType, $headersType>(${dataNeedsAwait ?? false ? 'await ' : ''}$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 { + b.returns = refer('Future'); + code.write('return;'); + } + + code.write('}'); + } + code.write( + 'throw await ${state.classPrefix}ApiException.fromResponse(_response); // coverage:ignore-line\n', + ); + } else { + b.returns = refer('Future'); + } + b.body = Code(code.toString()); + }, + ); + } + } +} + +Map generatePaths(final OpenAPI spec, final String? tag) { + final paths = {}; + + if (spec.paths != null) { + for (final path in spec.paths!.entries) { + for (final operationEntry in path.value.operations.entries) { + final operation = operationEntry.value; + if ((operation.tags != null && operation.tags!.contains(tag)) || + (tag == null && (operation.tags == null || operation.tags!.isEmpty))) { + paths[path.key] ??= PathItem( + description: path.value.description, + parameters: path.value.parameters, + ); + paths[path.key] = paths[path.key]!.copyWithOperations({operationEntry.key: operation}); + } + } + } + } + + return paths; +} + +List generateTags(final OpenAPI spec) { + final tags = []; + + if (spec.paths != null) { + for (final pathItem in spec.paths!.values) { + for (final operation in pathItem.operations.values) { + if (operation.tags != null) { + for (final tag in operation.tags!) { + final tagPart = tag.split('/').first; + if (!tags.contains(tagPart)) { + tags.add(tagPart); + } + } + } + } + } + } + + return tags..sort((final a, final b) => a.compareTo(b)); +} diff --git a/packages/dynamite/dynamite/lib/src/builder/header_serializer.dart b/packages/dynamite/dynamite/lib/src/builder/header_serializer.dart new file mode 100644 index 00000000..0a6f519b --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/header_serializer.dart @@ -0,0 +1,135 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:dynamite/src/builder/resolve_type.dart'; +import 'package:dynamite/src/builder/state.dart'; +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/helpers/dynamite.dart'; +import 'package:dynamite/src/models/open_api.dart'; +import 'package:dynamite/src/models/schema.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +Spec buildHeaderSerializer(final State state, final String identifier, final OpenAPI spec, final Schema schema) => + Class( + (final b) => b + ..name = '_\$${state.classPrefix}${identifier}Serializer' + ..implements.add(refer('StructuredSerializer<${state.classPrefix}$identifier>')) + ..fields.addAll([ + Field( + (final b) => b + ..name = 'types' + ..modifier = FieldModifier.final$ + ..type = refer('Iterable') + ..annotations.add(refer('override')) + ..assignment = Code('const [${state.classPrefix}$identifier, _\$${state.classPrefix}$identifier]'), + ), + Field( + (final b) => b + ..name = 'wireName' + ..modifier = FieldModifier.final$ + ..type = refer('String') + ..annotations.add(refer('override')) + ..assignment = Code("r'${state.classPrefix}$identifier'"), + ), + ]) + ..methods.addAll([ + Method((final b) { + b + ..name = 'serialize' + ..returns = refer('Iterable') + ..annotations.add(refer('override')) + ..requiredParameters.addAll([ + Parameter( + (final b) => b + ..name = 'serializers' + ..type = refer('Serializers'), + ), + Parameter( + (final b) => b + ..name = 'object' + ..type = refer('${state.classPrefix}$identifier'), + ), + ]) + ..optionalParameters.add( + Parameter( + (final b) => b + ..name = 'specifiedType' + ..type = refer('FullType') + ..named = true + ..defaultTo = const Code('FullType.unspecified'), + ), + ) + ..body = const Code('throw UnimplementedError();'); + }), + Method((final b) { + b + ..name = 'deserialize' + ..returns = refer('${state.classPrefix}$identifier') + ..annotations.add(refer('override')) + ..requiredParameters.addAll([ + Parameter( + (final b) => b + ..name = 'serializers' + ..type = refer('Serializers'), + ), + Parameter( + (final b) => b + ..name = 'serialized' + ..type = refer('Iterable'), + ), + ]) + ..optionalParameters.add( + Parameter( + (final b) => b + ..name = 'specifiedType' + ..type = refer('FullType') + ..named = true + ..defaultTo = const Code('FullType.unspecified'), + ), + ) + ..body = Code(''' +final result = new ${state.classPrefix}${identifier}Builder(); + +final iterator = serialized.iterator; +while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final value = iterator.current! as String; + switch (key) { + ${deserializeProperty(state, identifier, spec, schema).join('\n')} + } +} + +return result.build(); +'''); + }), + ]), + ); + +Iterable deserializeProperty( + final State state, + final String identifier, + final OpenAPI spec, + final Schema schema, +) sync* { + for (final property in schema.properties!.entries) { + final propertyName = property.key; + final propertySchema = property.value; + final result = resolveType( + spec, + state, + '${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}', + propertySchema, + nullable: isDartParameterNullable(schema.required?.contains(propertyName), propertySchema), + ); + + yield "case '$propertyName':"; + if (result.className != 'String') { + if (result is TypeResultBase || result is TypeResultEnum) { + yield 'result.${toDartName(propertyName)} = ${result.deserialize(result.decode('value!'))};'; + } else { + yield 'result.${toDartName(propertyName)}.replace(${result.deserialize(result.decode('value!'))});'; + } + } else { + yield 'result.${toDartName(propertyName)} = value!;'; + } + } +} diff --git a/packages/dynamite/dynamite/lib/src/builder/imports.dart b/packages/dynamite/dynamite/lib/src/builder/imports.dart new file mode 100644 index 00000000..c847f0d2 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/imports.dart @@ -0,0 +1,24 @@ +import 'package:build/build.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:path/path.dart' as p; + +List generateImports(final AssetId outputId) => [ + const Code('// ignore_for_file: camel_case_types'), + const Code('// ignore_for_file: public_member_api_docs'), + Directive.import('dart:convert'), + Directive.import('dart:typed_data'), + const Code(''), + Directive.import('package:built_collection/built_collection.dart'), + Directive.import('package:built_value/built_value.dart'), + Directive.import('package:built_value/json_object.dart'), + Directive.import('package:built_value/serializer.dart'), + Directive.import('package:built_value/standard_json_plugin.dart'), + Directive.import('package:dynamite_runtime/content_string.dart'), + Directive.import('package:dynamite_runtime/http_client.dart'), + Directive.import('package:universal_io/io.dart'), + const Code(''), + Directive.export('package:dynamite_runtime/http_client.dart'), + const Code(''), + Directive.part(p.basename(outputId.changeExtension('.g.dart').path)), + const Code(''), + ]; diff --git a/packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart b/packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart new file mode 100644 index 00000000..1b9d7bc9 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart @@ -0,0 +1,229 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:dynamite/src/builder/resolve_type.dart'; +import 'package:dynamite/src/builder/state.dart'; +import 'package:dynamite/src/helpers/built_value.dart'; +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/models/open_api.dart'; +import 'package:dynamite/src/models/schema.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +TypeResult resolveOfs( + final OpenAPI spec, + final State state, + final String identifier, + final Schema schema, { + final bool nullable = false, +}) { + final result = TypeResultObject( + '${state.classPrefix}$identifier', + nullable: nullable, + ); + + if (state.resolvedTypes.add(result)) { + final results = schema.ofs! + .map( + (final s) => resolveType( + spec, + state, + '$identifier${schema.ofs!.indexOf(s)}', + s, + nullable: !(schema.allOf?.contains(s) ?? false), + ), + ) + .toList(); + + final fields = {}; + for (final result in results) { + final dartName = toDartName(result.name.replaceFirst(state.classPrefix, '')); + fields[result.name] = toFieldName(dartName, result.name.replaceFirst(state.classPrefix, '')); + } + + state.output.addAll([ + buildBuiltClass( + '${state.classPrefix}$identifier', + BuiltList.build((final b) { + b.add( + Method( + (final b) { + b + ..name = 'data' + ..returns = refer('JsonObject') + ..type = MethodType.getter; + }, + ), + ); + + for (final result in results) { + b.add( + Method( + (final b) { + final s = schema.ofs![results.indexOf(result)]; + b + ..name = fields[result.name] + ..returns = refer(result.nullableName) + ..type = MethodType.getter + ..docs.addAll(s.formattedDescription); + }, + ), + ); + } + }), + customSerializer: true, + ), + Class( + (final b) => b + ..name = '_\$${state.classPrefix}${identifier}Serializer' + ..implements.add(refer('PrimitiveSerializer<${state.classPrefix}$identifier>')) + ..fields.addAll([ + Field( + (final b) => b + ..name = 'types' + ..modifier = FieldModifier.final$ + ..type = refer('Iterable') + ..annotations.add(refer('override')) + ..assignment = Code('const [${state.classPrefix}$identifier, _\$${state.classPrefix}$identifier]'), + ), + Field( + (final b) => b + ..name = 'wireName' + ..modifier = FieldModifier.final$ + ..type = refer('String') + ..annotations.add(refer('override')) + ..assignment = Code("r'${state.classPrefix}$identifier'"), + ), + ]) + ..methods.addAll([ + Method((final b) { + b + ..name = 'serialize' + ..returns = refer('Object') + ..annotations.add(refer('override')) + ..requiredParameters.addAll([ + Parameter( + (final b) => b + ..name = 'serializers' + ..type = refer('Serializers'), + ), + Parameter( + (final b) => b + ..name = 'object' + ..type = refer('${state.classPrefix}$identifier'), + ), + ]) + ..optionalParameters.add( + Parameter( + (final b) => b + ..name = 'specifiedType' + ..type = refer('FullType') + ..named = true + ..defaultTo = const Code('FullType.unspecified'), + ), + ) + ..body = const Code('return object.data.value;'); + }), + Method((final b) { + b + ..name = 'deserialize' + ..returns = refer('${state.classPrefix}$identifier') + ..annotations.add(refer('override')) + ..requiredParameters.addAll([ + Parameter( + (final b) => b + ..name = 'serializers' + ..type = refer('Serializers'), + ), + Parameter( + (final b) => b + ..name = 'data' + ..type = refer('Object'), + ), + ]) + ..optionalParameters.add( + Parameter( + (final b) => b + ..name = 'specifiedType' + ..type = refer('FullType') + ..named = true + ..defaultTo = const Code('FullType.unspecified'), + ), + ) + ..body = Code( + [ + 'final result = new ${state.classPrefix}${identifier}Builder()', + '..data = JsonObject(data);', + if (schema.allOf != null) ...[ + for (final result in results) ...[ + if (result is TypeResultBase || result is TypeResultEnum) ...[ + 'result.${fields[result.name]!} = ${result.deserialize('data')};', + ] else ...[ + 'result.${fields[result.name]!}.replace(${result.deserialize('data')});', + ], + ], + ] else ...[ + if (schema.discriminator != null) ...[ + 'if (data is! Iterable) {', + r"throw StateError('Expected an Iterable but got ${data.runtimeType}');", + '}', + '', + 'String? discriminator;', + '', + 'final iterator = data.iterator;', + 'while (iterator.moveNext()) {', + 'final key = iterator.current! as String;', + 'iterator.moveNext();', + 'final Object? value = iterator.current;', + "if (key == '${schema.discriminator!.propertyName}') {", + 'discriminator = value! as String;', + 'break;', + '}', + '}', + ], + for (final result in results) ...[ + if (schema.discriminator != null) ...[ + "if (discriminator == '${result.name.replaceFirst(state.classPrefix, '')}'", + if (schema.discriminator!.mapping != null && schema.discriminator!.mapping!.isNotEmpty) ...[ + for (final key in schema.discriminator!.mapping!.entries + .where( + (final entry) => + entry.value.endsWith('/${result.name.replaceFirst(state.classPrefix, '')}'), + ) + .map((final entry) => entry.key)) ...[ + " || discriminator == '$key'", + ], + ') {', + ], + ], + 'try {', + if (result is TypeResultBase || result is TypeResultEnum) ...[ + 'result._${fields[result.name]!} = ${result.deserialize('data')};', + ] else ...[ + 'result._${fields[result.name]!} = ${result.deserialize('data')}.toBuilder();', + ], + '} catch (_) {', + if (schema.discriminator != null) ...[ + 'rethrow;', + ], + '}', + if (schema.discriminator != null) ...[ + '}', + ], + ], + if (schema.oneOf != null) ...[ + "assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need oneOf for \${result._data}');", + ], + if (schema.anyOf != null) ...[ + "assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need anyOf for \${result._data}');", + ], + ], + 'return result.build();', + ].join(), + ); + }), + ]), + ), + ]); + } + + return result; +} diff --git a/packages/dynamite/dynamite/lib/src/builder/resolve_enum.dart b/packages/dynamite/dynamite/lib/src/builder/resolve_enum.dart new file mode 100644 index 00000000..533c7f03 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/resolve_enum.dart @@ -0,0 +1,112 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:dynamite/src/builder/resolve_type.dart'; +import 'package:dynamite/src/builder/state.dart'; +import 'package:dynamite/src/helpers/built_value.dart'; +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/helpers/type_result.dart'; +import 'package:dynamite/src/models/open_api.dart'; +import 'package:dynamite/src/models/schema.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +TypeResult resolveEnum( + final OpenAPI spec, + final State state, + final String identifier, + final Schema schema, + final TypeResult subResult, { + final bool nullable = false, +}) { + if (state.resolvedTypes.add(TypeResultEnum('${state.classPrefix}$identifier', subResult))) { + state.output.add( + Class( + (final b) => b + ..name = '${state.classPrefix}$identifier' + ..extend = refer('EnumClass') + ..constructors.add( + Constructor( + (final b) => b + ..name = '_' + ..constant = true + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'name' + ..toSuper = true, + ), + ), + ), + ) + ..fields.addAll( + schema.enum_!.map( + (final value) => Field( + (final b) { + final result = resolveType( + spec, + state, + '$identifier${toDartName(value.toString(), uppercaseFirstCharacter: true)}', + schema, + ignoreEnum: true, + ); + b + ..name = toDartName(value.toString()) + ..static = true + ..modifier = FieldModifier.constant + ..type = refer('${state.classPrefix}$identifier') + ..assignment = Code( + '_\$${toCamelCase('${state.classPrefix}$identifier')}${toDartName(value.toString(), uppercaseFirstCharacter: true)}', + ); + + if (toDartName(value.toString()) != value.toString()) { + if (result.name != 'String' && result.name != 'int') { + throw Exception( + 'Sorry enum values are a bit broken. ' + 'See https://github.com/google/json_serializable.dart/issues/616. ' + 'Please remove the enum values on ${state.classPrefix}$identifier.', + ); + } + b.annotations.add( + refer('BuiltValueEnumConst').call([], { + 'wireName': refer(valueToEscapedValue(result, value.toString())), + }), + ); + } + }, + ), + ), + ) + ..methods.addAll([ + Method( + (final b) => b + ..name = 'values' + ..returns = refer('BuiltSet<${state.classPrefix}$identifier>') + ..lambda = true + ..static = true + ..body = Code('_\$${toCamelCase('${state.classPrefix}$identifier')}Values') + ..type = MethodType.getter, + ), + Method( + (final b) => b + ..name = 'valueOf' + ..returns = refer('${state.classPrefix}$identifier') + ..lambda = true + ..static = true + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'name' + ..type = refer(subResult.name), + ), + ) + ..body = Code('_\$valueOf${state.classPrefix}$identifier(name)'), + ), + buildSerializer('${state.classPrefix}$identifier'), + ]), + ), + ); + } + return TypeResultEnum( + '${state.classPrefix}$identifier', + subResult, + nullable: nullable, + ); +} diff --git a/packages/dynamite/dynamite/lib/src/builder/resolve_object.dart b/packages/dynamite/dynamite/lib/src/builder/resolve_object.dart new file mode 100644 index 00000000..5bf5ab64 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/resolve_object.dart @@ -0,0 +1,90 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:dynamite/src/builder/header_serializer.dart'; +import 'package:dynamite/src/builder/resolve_type.dart'; +import 'package:dynamite/src/builder/state.dart'; +import 'package:dynamite/src/helpers/built_value.dart'; +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/helpers/dynamite.dart'; +import 'package:dynamite/src/helpers/type_result.dart'; +import 'package:dynamite/src/models/open_api.dart'; +import 'package:dynamite/src/models/schema.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +TypeResult resolveObject( + final OpenAPI spec, + final State state, + final String identifier, + final Schema schema, { + final bool nullable = false, + final bool isHeader = false, +}) { + final result = TypeResultObject( + '${state.classPrefix}$identifier', + nullable: nullable, + ); + if (state.resolvedTypes.add(result)) { + final defaults = []; + for (final property in schema.properties!.entries) { + final propertySchema = property.value; + if (propertySchema.default_ != null) { + final value = propertySchema.default_!.toString(); + final result = resolveType( + spec, + state, + propertySchema.type!, + propertySchema, + ); + defaults.add('..${toDartName(property.key)} = ${valueToEscapedValue(result, value)}'); + } + } + + state.output.add( + buildBuiltClass( + '${state.classPrefix}$identifier', + BuiltList.build((final b) { + for (final property in schema.properties!.entries) { + b.add( + Method( + (final b) { + final propertyName = property.key; + final propertySchema = property.value; + final result = resolveType( + spec, + state, + '${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}', + propertySchema, + nullable: isDartParameterNullable( + schema.required?.contains(propertyName), + propertySchema, + ), + ); + + b + ..name = toDartName(propertyName) + ..returns = refer(result.nullableName) + ..type = MethodType.getter + ..docs.addAll(propertySchema.formattedDescription); + + if (toDartName(propertyName) != propertyName) { + b.annotations.add( + refer('BuiltValueField').call([], { + 'wireName': literalString(propertyName), + }), + ); + } + }, + ), + ); + } + }), + defaults: defaults, + customSerializer: isHeader, + ), + ); + if (isHeader) { + state.output.add(buildHeaderSerializer(state, identifier, spec, schema)); + } + } + return result; +} diff --git a/packages/dynamite/dynamite/lib/src/builder/resolve_type.dart b/packages/dynamite/dynamite/lib/src/builder/resolve_type.dart new file mode 100644 index 00000000..a69d4999 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/resolve_type.dart @@ -0,0 +1,165 @@ +import 'package:dynamite/src/builder/ofs_builder.dart'; +import 'package:dynamite/src/builder/resolve_enum.dart'; +import 'package:dynamite/src/builder/resolve_object.dart'; +import 'package:dynamite/src/builder/state.dart'; +import 'package:dynamite/src/models/open_api.dart'; +import 'package:dynamite/src/models/schema.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +TypeResult resolveType( + final OpenAPI spec, + final State state, + final String identifier, + final Schema schema, { + final bool ignoreEnum = false, + final bool nullable = false, +}) { + TypeResult? result; + if (schema.ref == null && schema.ofs == null && schema.type == null) { + return TypeResultBase( + 'JsonObject', + nullable: nullable, + ); + } + if (schema.ref != null) { + final name = schema.ref!.split('/').last; + result = resolveType( + spec, + state, + name, + spec.components!.schemas![name]!, + nullable: nullable, + ); + } else if (schema.ofs != null) { + result = resolveOfs( + spec, + state, + identifier, + schema, + nullable: nullable, + ); + } else if (schema.isContentString) { + final subResult = resolveType( + spec, + state, + identifier, + schema.contentSchema!, + ); + + result = TypeResultObject( + 'ContentString', + generics: [subResult], + nullable: nullable, + ); + } else { + switch (schema.type) { + case 'boolean': + result = TypeResultBase( + 'bool', + nullable: nullable, + ); + case 'integer': + result = TypeResultBase( + 'int', + nullable: nullable, + ); + case 'number': + result = TypeResultBase( + 'num', + nullable: nullable, + ); + case 'string': + switch (schema.format) { + case 'binary': + result = TypeResultBase( + 'Uint8List', + nullable: nullable, + ); + } + + result = TypeResultBase( + 'String', + nullable: nullable, + ); + case 'array': + if (schema.items != null) { + final subResult = resolveType( + spec, + state, + identifier, + schema.items!, + ); + result = TypeResultList( + 'BuiltList', + subResult, + nullable: nullable, + ); + } else { + result = TypeResultList( + 'BuiltList', + TypeResultBase('JsonObject'), + nullable: nullable, + ); + } + case 'object': + if (schema.properties == null) { + if (schema.additionalProperties == null) { + result = TypeResultBase( + 'JsonObject', + nullable: nullable, + ); + } else if (schema.additionalProperties is EmptySchema) { + result = TypeResultMap( + 'BuiltMap', + TypeResultBase('JsonObject'), + nullable: nullable, + ); + } else { + final subResult = resolveType( + spec, + state, + identifier, + schema.additionalProperties!, + ); + result = TypeResultMap( + 'BuiltMap', + subResult, + nullable: nullable, + ); + } + } else if (schema.properties!.isEmpty) { + result = TypeResultMap( + 'BuiltMap', + TypeResultBase('JsonObject'), + nullable: nullable, + ); + } else { + result = resolveObject( + spec, + state, + identifier, + schema, + nullable: nullable, + ); + } + } + } + + if (result != null) { + if (!ignoreEnum && schema.enum_ != null) { + result = resolveEnum( + spec, + state, + identifier, + schema, + result, + nullable: nullable, + ); + } + + state.resolvedTypes.add(result); + return result; + } + + throw Exception('Can not convert OpenAPI type "${schema.toJson()}" to a Dart type'); +} diff --git a/packages/dynamite/dynamite/lib/src/builder/serializer.dart b/packages/dynamite/dynamite/lib/src/builder/serializer.dart new file mode 100644 index 00000000..bbd4d7ed --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/serializer.dart @@ -0,0 +1,34 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:dynamite/src/builder/state.dart'; + +List buildSerializer(final State state) { + if (state.resolvedTypes.isNotEmpty) { + return [ + const Code('// coverage:ignore-start'), + const Code('final Serializers _serializers = (Serializers().toBuilder()'), + ...state.resolvedTypes + .map((final type) => type.serializers) + .expand((final element) => element) + .toSet() + .map(Code.new), + const Code(').build();'), + const Code(''), + Code('Serializers get ${state.variablePrefix}Serializers => _serializers;'), + const Code(''), + const Code( + 'final Serializers _jsonSerializers = (_serializers.toBuilder()..addPlugin(StandardJsonPlugin())..addPlugin(const ContentStringPlugin())).build();', + ), + const Code(''), + Code( + 'T deserialize${state.classPrefix}(final Object data) => _serializers.deserialize(data, specifiedType: FullType(T))! as T;', + ), + const Code(''), + Code( + 'Object? serialize${state.classPrefix}(final T data) => _serializers.serialize(data, specifiedType: FullType(T));', + ), + const Code('// coverage:ignore-end'), + ]; + } + + return []; +} diff --git a/packages/dynamite/dynamite/lib/src/builder/state.dart b/packages/dynamite/dynamite/lib/src/builder/state.dart new file mode 100644 index 00000000..75c9abe6 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/state.dart @@ -0,0 +1,15 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +class State { + State(final String prefix) + : classPrefix = toDartName(prefix, uppercaseFirstCharacter: true), + variablePrefix = toDartName(prefix); + + final String classPrefix; + final String variablePrefix; + + final output = []; + final resolvedTypes = {}; +} diff --git a/packages/dynamite/dynamite/lib/src/helpers/built_value.dart b/packages/dynamite/dynamite/lib/src/helpers/built_value.dart new file mode 100644 index 00000000..7ac0f247 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/helpers/built_value.dart @@ -0,0 +1,120 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:dynamite/src/helpers/dart_helpers.dart'; + +Spec buildBuiltClass( + final String className, + final BuiltList methods, { + final Iterable? defaults, + final bool customSerializer = false, +}) => + Class( + (final b) { + b + ..name = className + ..abstract = true + ..implements.add( + refer( + 'Built<$className, ${className}Builder>', + ), + ) + ..constructors.addAll([ + builtValueConstructor(className), + hiddenConstructor, + fromJsonConstructor, + ]) + ..methods.addAll([ + toJsonMethod, + ...methods, + buildSerializer(className, isCustom: customSerializer), + ]); + + if (defaults != null && defaults.isNotEmpty) { + b.methods.add( + Method( + (final b) => b + ..name = '_defaults' + ..returns = refer('void') + ..static = true + ..lambda = true + ..annotations.add( + refer('BuiltValueHook').call([], { + 'initializeBuilder': literalTrue, + }), + ) + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'b' + ..type = refer('${className}Builder'), + ), + ) + ..body = Code( + [ + 'b', + ...defaults, + ].join(), + ), + ), + ); + } + }, + ); + +Method get toJsonMethod => Method( + (final b) => b + ..name = 'toJson' + ..returns = refer('Map') + ..lambda = true + ..body = const Code('_jsonSerializers.serializeWith(serializer, this)! as Map'), + ); + +Method buildSerializer(final String className, {final bool isCustom = false}) => Method((final b) { + b + ..name = 'serializer' + ..returns = refer('Serializer<$className>') + ..lambda = true + ..static = true + ..body = Code( + isCustom ? '_\$${className}Serializer()' : '_\$${toCamelCase(className)}Serializer', + ) + ..type = MethodType.getter; + if (isCustom) { + b.annotations.add(refer('BuiltValueSerializer').call([], {'custom': literalTrue})); + } + }); + +Constructor builtValueConstructor(final String className) => Constructor( + (final b) => b + ..factory = true + ..lambda = true + ..optionalParameters.add( + Parameter( + (final b) => b + ..name = 'b' + ..type = refer('void Function(${className}Builder)?'), + ), + ) + ..redirect = refer('_\$$className'), + ); + +Constructor get hiddenConstructor => Constructor( + (final b) => b + ..name = '_' + ..constant = true, + ); + +Constructor get fromJsonConstructor => Constructor( + (final b) => b + ..factory = true + ..name = 'fromJson' + ..lambda = true + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'json' + ..type = refer('Map'), + ), + ) + ..body = const Code('_jsonSerializers.deserializeWith(serializer, json)!'), + ); diff --git a/packages/dynamite/dynamite/lib/src/helpers/dart_helpers.dart b/packages/dynamite/dynamite/lib/src/helpers/dart_helpers.dart new file mode 100644 index 00000000..54e40aa3 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/helpers/dart_helpers.dart @@ -0,0 +1,107 @@ +String toDartName( + final String name, { + final bool uppercaseFirstCharacter = false, +}) { + var result = ''; + var upperCase = uppercaseFirstCharacter; + var firstCharacter = !uppercaseFirstCharacter; + for (final char in name.split('')) { + if (_isNonAlphaNumericString(char)) { + upperCase = true; + } else { + result += firstCharacter ? char.toLowerCase() : (upperCase ? char.toUpperCase() : char); + upperCase = false; + firstCharacter = false; + } + } + + if (_dartKeywords.contains(result) || RegExp(r'^[0-9]+$', multiLine: true).hasMatch(result)) { + return '\$$result'; + } + + return result; +} + +final _dartKeywords = [ + 'assert', + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'default', + 'do', + 'else', + 'enum', + 'extends', + 'false', + 'final', + 'finally', + 'for', + 'if', + 'in', + 'is', + 'new', + 'null', + 'rethrow', + 'return', + 'super', + 'switch', + 'this', + 'throw', + 'true', + 'try', + 'var', + 'void', + 'while', + 'with', + 'async', + 'hide', + 'on', + 'show', + 'sync', + 'abstract', + 'as', + 'covariant', + 'deferred', + 'dynamic', + 'export', + 'extension', + 'external', + 'factory', + 'function', + 'get', + 'implements', + 'import', + 'interface', + 'library', + 'mixin', + 'operator', + 'part', + 'set', + 'static', + 'typedef', +]; + +bool _isNonAlphaNumericString(final String input) => !RegExp(r'^[a-zA-Z0-9]$').hasMatch(input); + +String toFieldName(final String dartName, final String type) => dartName == type ? '\$$dartName' : dartName; + +String toCamelCase(final String name) { + var result = ''; + var upperCase = false; + var firstCharacter = true; + for (final char in name.split('')) { + if (char == '_') { + upperCase = true; + } else if (char == r'$') { + result += r'$'; + } else { + result += firstCharacter ? char.toLowerCase() : (upperCase ? char.toUpperCase() : char); + upperCase = false; + firstCharacter = false; + } + } + return result; +} diff --git a/packages/dynamite/dynamite/lib/src/helpers/docs.dart b/packages/dynamite/dynamite/lib/src/helpers/docs.dart new file mode 100644 index 00000000..87b2501b --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/helpers/docs.dart @@ -0,0 +1,9 @@ +const docsSeparator = '///'; + +Iterable descriptionToDocs(final String? description) sync* { + if (description != null && description.isNotEmpty) { + for (final line in description.split('\n')) { + yield '$docsSeparator $line'; + } + } +} diff --git a/packages/dynamite/dynamite/lib/src/helpers/dynamite.dart b/packages/dynamite/dynamite/lib/src/helpers/dynamite.dart new file mode 100644 index 00000000..3063d6a8 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/helpers/dynamite.dart @@ -0,0 +1,43 @@ +// ignore_for_file: avoid_positional_boolean_parameters + +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/models/parameter.dart'; +import 'package:dynamite/src/models/schema.dart'; + +String filterMethodName(final String operationId, final String tag) { + final expandedTag = tag.split('/').toList(); + final parts = operationId.split('-'); + final output = []; + for (var i = 0; i < parts.length; i++) { + if (expandedTag.length <= i || expandedTag[i] != parts[i]) { + output.add(parts[i]); + } + } + return output.join('-'); +} + +String clientName(final String tag) => '${toDartName(tag, uppercaseFirstCharacter: true)}Client'; + +bool isDartParameterNullable( + final bool? required, + final Schema? schema, +) => + (!(required ?? false) && schema?.default_ == null) || (schema?.nullable ?? false); + +bool isRequired( + final bool? required, + final dynamic default_, +) => + (required ?? false) && default_ == null; + +int sortRequiredParameters(final Parameter a, final Parameter b) { + if (a.isDartRequired != b.isDartRequired) { + if (a.isDartRequired && !b.isDartRequired) { + return -1; + } else { + return 1; + } + } + + return 0; +} diff --git a/packages/dynamite/dynamite/lib/src/helpers/type_result.dart b/packages/dynamite/dynamite/lib/src/helpers/type_result.dart new file mode 100644 index 00000000..ca1727e0 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/helpers/type_result.dart @@ -0,0 +1,15 @@ +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +String valueToEscapedValue(final TypeResult result, final String value) { + if (result is TypeResultBase && result.name == 'String') { + return "'$value'"; + } + if (result is TypeResultList) { + return 'const $value'; + } + if (result is TypeResultEnum) { + return '${result.name}.${toDartName(value)}'; + } + return value; +} diff --git a/packages/dynamite/dynamite/lib/src/models/components.dart b/packages/dynamite/dynamite/lib/src/models/components.dart index e995236a..ab168eb1 100644 --- a/packages/dynamite/dynamite/lib/src/models/components.dart +++ b/packages/dynamite/dynamite/lib/src/models/components.dart @@ -1,12 +1,14 @@ import 'package:dynamite/src/models/schema.dart'; import 'package:dynamite/src/models/security_scheme.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'components.g.dart'; @JsonSerializable() +@immutable class Components { - Components({ + const Components({ this.securitySchemes, this.schemas, }); diff --git a/packages/dynamite/dynamite/lib/src/models/discriminator.dart b/packages/dynamite/dynamite/lib/src/models/discriminator.dart index 5968aeb6..f1bfe774 100644 --- a/packages/dynamite/dynamite/lib/src/models/discriminator.dart +++ b/packages/dynamite/dynamite/lib/src/models/discriminator.dart @@ -1,10 +1,12 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'discriminator.g.dart'; @JsonSerializable() +@immutable class Discriminator { - Discriminator({ + const Discriminator({ required this.propertyName, this.mapping, }); diff --git a/packages/dynamite/dynamite/lib/src/models/header.dart b/packages/dynamite/dynamite/lib/src/models/header.dart index 9bd47efa..9e8a21e8 100644 --- a/packages/dynamite/dynamite/lib/src/models/header.dart +++ b/packages/dynamite/dynamite/lib/src/models/header.dart @@ -1,11 +1,13 @@ import 'package:dynamite/src/models/schema.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'header.g.dart'; @JsonSerializable() +@immutable class Header { - Header({ + const Header({ this.description, this.required, this.schema, diff --git a/packages/dynamite/dynamite/lib/src/models/info.dart b/packages/dynamite/dynamite/lib/src/models/info.dart index 59d2f97a..b648d6ba 100644 --- a/packages/dynamite/dynamite/lib/src/models/info.dart +++ b/packages/dynamite/dynamite/lib/src/models/info.dart @@ -1,11 +1,13 @@ import 'package:dynamite/src/models/license.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'info.g.dart'; @JsonSerializable() +@immutable class Info { - Info({ + const Info({ required this.title, required this.version, required this.license, diff --git a/packages/dynamite/dynamite/lib/src/models/license.dart b/packages/dynamite/dynamite/lib/src/models/license.dart index 3bf19718..f1a158a3 100644 --- a/packages/dynamite/dynamite/lib/src/models/license.dart +++ b/packages/dynamite/dynamite/lib/src/models/license.dart @@ -1,10 +1,12 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'license.g.dart'; @JsonSerializable() +@immutable class License { - License({ + const License({ required this.name, this.identifier, this.url, diff --git a/packages/dynamite/dynamite/lib/src/models/media_type.dart b/packages/dynamite/dynamite/lib/src/models/media_type.dart index 4172cbae..feebad58 100644 --- a/packages/dynamite/dynamite/lib/src/models/media_type.dart +++ b/packages/dynamite/dynamite/lib/src/models/media_type.dart @@ -1,11 +1,13 @@ import 'package:dynamite/src/models/schema.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'media_type.g.dart'; @JsonSerializable() +@immutable class MediaType { - MediaType({ + const MediaType({ this.schema, }); diff --git a/packages/dynamite/dynamite/lib/src/models/open_api.dart b/packages/dynamite/dynamite/lib/src/models/open_api.dart index 45101099..18203261 100644 --- a/packages/dynamite/dynamite/lib/src/models/open_api.dart +++ b/packages/dynamite/dynamite/lib/src/models/open_api.dart @@ -6,12 +6,14 @@ import 'package:dynamite/src/models/security_requirement.dart'; import 'package:dynamite/src/models/server.dart'; import 'package:dynamite/src/models/tag.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'open_api.g.dart'; @JsonSerializable() +@immutable class OpenAPI { - OpenAPI({ + const OpenAPI({ required this.version, required this.info, this.servers, @@ -38,4 +40,14 @@ class OpenAPI { final Components? components; final Paths? paths; + + bool get hasAnySecurity => components?.securitySchemes?.isNotEmpty ?? false; + + Iterable formattedTagsFor(final String? tag) sync* { + final matchedTags = tags?.where((final t) => t.name == tag); + + if (matchedTags != null && matchedTags.isNotEmpty) { + yield* matchedTags.single.formattedDescription; + } + } } diff --git a/packages/dynamite/dynamite/lib/src/models/operation.dart b/packages/dynamite/dynamite/lib/src/models/operation.dart index 765deb01..e7fcec64 100644 --- a/packages/dynamite/dynamite/lib/src/models/operation.dart +++ b/packages/dynamite/dynamite/lib/src/models/operation.dart @@ -1,15 +1,18 @@ +import 'package:dynamite/src/helpers/docs.dart'; import 'package:dynamite/src/models/parameter.dart'; import 'package:dynamite/src/models/request_body.dart'; import 'package:dynamite/src/models/response.dart'; import 'package:dynamite/src/models/responses.dart'; import 'package:dynamite/src/models/security_requirement.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'operation.g.dart'; @JsonSerializable() +@immutable class Operation { - Operation({ + const Operation({ this.operationId, this.summary, this.description, @@ -41,4 +44,14 @@ class Operation { final Responses? responses; final List? security; + + Iterable get formattedDescription sync* { + yield* descriptionToDocs(summary); + + if (summary != null && description != null) { + yield docsSeparator; + } + + yield* descriptionToDocs(description); + } } diff --git a/packages/dynamite/dynamite/lib/src/models/parameter.dart b/packages/dynamite/dynamite/lib/src/models/parameter.dart index 86dc7340..f2ba7ded 100644 --- a/packages/dynamite/dynamite/lib/src/models/parameter.dart +++ b/packages/dynamite/dynamite/lib/src/models/parameter.dart @@ -1,11 +1,14 @@ +import 'package:dynamite/src/helpers/dynamite.dart'; import 'package:dynamite/src/models/schema.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'parameter.g.dart'; @JsonSerializable() +@immutable class Parameter { - Parameter({ + const Parameter({ required this.name, required this.in_, this.description, @@ -26,4 +29,6 @@ class Parameter { final bool? required; final Schema? schema; + + bool get isDartRequired => isRequired(required, schema?.default_); } diff --git a/packages/dynamite/dynamite/lib/src/models/path_item.dart b/packages/dynamite/dynamite/lib/src/models/path_item.dart index 5d75e819..2d7d3fea 100644 --- a/packages/dynamite/dynamite/lib/src/models/path_item.dart +++ b/packages/dynamite/dynamite/lib/src/models/path_item.dart @@ -1,12 +1,14 @@ import 'package:dynamite/src/models/operation.dart'; import 'package:dynamite/src/models/parameter.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'path_item.g.dart'; @JsonSerializable() +@immutable class PathItem { - PathItem({ + const PathItem({ this.description, this.parameters, this.get, diff --git a/packages/dynamite/dynamite/lib/src/models/request_body.dart b/packages/dynamite/dynamite/lib/src/models/request_body.dart index 1fbe60eb..2aa631df 100644 --- a/packages/dynamite/dynamite/lib/src/models/request_body.dart +++ b/packages/dynamite/dynamite/lib/src/models/request_body.dart @@ -1,11 +1,13 @@ import 'package:dynamite/src/models/media_type.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'request_body.g.dart'; @JsonSerializable() +@immutable class RequestBody { - RequestBody({ + const RequestBody({ this.description, this.content, this.required, diff --git a/packages/dynamite/dynamite/lib/src/models/response.dart b/packages/dynamite/dynamite/lib/src/models/response.dart index 693e35c9..c4e3764d 100644 --- a/packages/dynamite/dynamite/lib/src/models/response.dart +++ b/packages/dynamite/dynamite/lib/src/models/response.dart @@ -1,12 +1,14 @@ import 'package:dynamite/src/models/header.dart'; import 'package:dynamite/src/models/media_type.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'response.g.dart'; @JsonSerializable() +@immutable class Response { - Response({ + const Response({ required this.description, this.content, this.headers, diff --git a/packages/dynamite/dynamite/lib/src/models/schema.dart b/packages/dynamite/dynamite/lib/src/models/schema.dart index 1db56028..74e191fa 100644 --- a/packages/dynamite/dynamite/lib/src/models/schema.dart +++ b/packages/dynamite/dynamite/lib/src/models/schema.dart @@ -1,11 +1,14 @@ +import 'package:dynamite/src/helpers/docs.dart'; import 'package:dynamite/src/models/discriminator.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'schema.g.dart'; @JsonSerializable() +@immutable class Schema { - Schema({ + const Schema({ this.ref, this.oneOf, this.anyOf, @@ -83,9 +86,13 @@ class Schema { final bool? nullable; bool get isContentString => type == 'string' && (contentMediaType?.isNotEmpty ?? false) && contentSchema != null; + + Iterable get formattedDescription => descriptionToDocs(description); } -class EmptySchema extends Schema {} +class EmptySchema extends Schema { + const EmptySchema(); +} Schema? _parseAdditionalProperties(final dynamic data) { if (data == null) { @@ -93,7 +100,7 @@ Schema? _parseAdditionalProperties(final dynamic data) { } if (data is bool) { - return data ? EmptySchema() : null; + return data ? const EmptySchema() : null; } if (data is Map) { diff --git a/packages/dynamite/dynamite/lib/src/models/security_scheme.dart b/packages/dynamite/dynamite/lib/src/models/security_scheme.dart index df8b4417..90936fc9 100644 --- a/packages/dynamite/dynamite/lib/src/models/security_scheme.dart +++ b/packages/dynamite/dynamite/lib/src/models/security_scheme.dart @@ -1,10 +1,12 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'security_scheme.g.dart'; @JsonSerializable() +@immutable class SecurityScheme { - SecurityScheme({ + const SecurityScheme({ required this.type, this.description, this.scheme, diff --git a/packages/dynamite/dynamite/lib/src/models/server.dart b/packages/dynamite/dynamite/lib/src/models/server.dart index 7feeac49..0a3f7270 100644 --- a/packages/dynamite/dynamite/lib/src/models/server.dart +++ b/packages/dynamite/dynamite/lib/src/models/server.dart @@ -1,11 +1,13 @@ import 'package:dynamite/src/models/server_variable.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'server.g.dart'; @JsonSerializable() +@immutable class Server { - Server({ + const Server({ required this.url, this.variables, }); diff --git a/packages/dynamite/dynamite/lib/src/models/server_variable.dart b/packages/dynamite/dynamite/lib/src/models/server_variable.dart index 52788812..3be2f869 100644 --- a/packages/dynamite/dynamite/lib/src/models/server_variable.dart +++ b/packages/dynamite/dynamite/lib/src/models/server_variable.dart @@ -1,10 +1,12 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'server_variable.g.dart'; @JsonSerializable() +@immutable class ServerVariable { - ServerVariable({ + const ServerVariable({ required this.default_, this.enum_, this.description, diff --git a/packages/dynamite/dynamite/lib/src/models/tag.dart b/packages/dynamite/dynamite/lib/src/models/tag.dart index 3ade793a..f6bd0b0a 100644 --- a/packages/dynamite/dynamite/lib/src/models/tag.dart +++ b/packages/dynamite/dynamite/lib/src/models/tag.dart @@ -1,10 +1,13 @@ +import 'package:dynamite/src/helpers/docs.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; part 'tag.g.dart'; @JsonSerializable() +@immutable class Tag { - Tag({ + const Tag({ required this.name, this.description, }); @@ -15,4 +18,6 @@ class Tag { final String name; final String? description; + + Iterable get formattedDescription => descriptionToDocs(description); } diff --git a/packages/dynamite/dynamite/lib/src/openapi_builder.dart b/packages/dynamite/dynamite/lib/src/openapi_builder.dart index d4648b2c..49804951 100644 --- a/packages/dynamite/dynamite/lib/src/openapi_builder.dart +++ b/packages/dynamite/dynamite/lib/src/openapi_builder.dart @@ -1,4 +1,17 @@ -part of '../dynamite.dart'; +import 'dart:convert'; + +import 'package:build/build.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:dart_style/dart_style.dart'; +import 'package:dynamite/src/builder/client.dart'; +import 'package:dynamite/src/builder/imports.dart'; +import 'package:dynamite/src/builder/resolve_type.dart'; +import 'package:dynamite/src/builder/serializer.dart'; +import 'package:dynamite/src/builder/state.dart'; +import 'package:dynamite/src/helpers/dart_helpers.dart'; +import 'package:dynamite/src/models/open_api.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; class OpenAPIBuilder implements Builder { @override @@ -22,705 +35,52 @@ class OpenAPIBuilder implements Builder { await buildStep.readAsString(inputId), ) as Map, ); - final classPrefix = _toDartName(spec.info.title, uppercaseFirstCharacter: true); - final variablePrefix = _toDartName(spec.info.title); + final supportedVersions = ['3.0.3', '3.1.0']; if (!supportedVersions.contains(spec.version)) { throw Exception('Only OpenAPI ${supportedVersions.join(', ')} are supported'); } - var tags = [ - null, - if (spec.paths != null) ...{ - for (final pathItem in spec.paths!.values) ...{ - for (final operation in pathItem.operations.values) ...{ - ...?operation.tags, - }, - }, - }, - ]; - for (final tag in tags.toList()) { - final tagPart = tag?.split('/').first; - if (!tags.contains(tagPart)) { - tags.add(tagPart); - } - } - tags = tags - ..sort( - (final a, final b) => a == null - ? -1 - : b == null - ? 1 - : a.compareTo(b), - ); + final state = State(spec.info.title); - final hasAnySecurity = spec.components?.securitySchemes?.isNotEmpty ?? false; + final output = ListBuilder() + ..addAll(generateImports(outputId)) + ..addAll(generateClients(spec, state)); - final state = State(classPrefix); - final output = [ - '// ignore_for_file: camel_case_types', - '// ignore_for_file: public_member_api_docs', - "import 'dart:convert';", - "import 'dart:typed_data';", - '', - "import 'package:built_collection/built_collection.dart';", - "import 'package:built_value/built_value.dart';", - "import 'package:built_value/json_object.dart';", - "import 'package:built_value/serializer.dart';", - "import 'package:built_value/standard_json_plugin.dart';", - "import 'package:dynamite_runtime/content_string.dart';", - "import 'package:dynamite_runtime/http_client.dart';", - "import 'package:universal_io/io.dart';", - '', - "export 'package:dynamite_runtime/http_client.dart';", - '', - "part '${p.basename(outputId.changeExtension('.g.dart').path)}';", - '', - Class( - (final b) => b - ..name = '${classPrefix}Response' - ..types.addAll([ - refer('T'), - refer('U'), - ]) - ..extend = refer('DynamiteResponse') - ..constructors.add( - Constructor( - (final b) => b - ..requiredParameters.addAll( - ['data', 'headers'].map( - (final name) => Parameter( - (final b) => b - ..name = name - ..toSuper = true, - ), - ), - ), - ), - ) - ..methods.add( - Method( - (final b) => b - ..name = 'toString' - ..returns = refer('String') - ..annotations.add(refer('override')) - ..lambda = true - ..body = Code( - "'${classPrefix}Response(data: \$data, headers: \$headers)'", - ), - ), - ), - ).accept(emitter).toString(), - Class( - (final b) => b - ..name = '${classPrefix}ApiException' - ..extend = refer('DynamiteApiException') - ..constructors.add( - Constructor( - (final b) => b - ..requiredParameters.addAll( - ['statusCode', 'headers', 'body'].map( - (final name) => Parameter( - (final b) => b - ..name = name - ..toSuper = true, - ), - ), - ), - ), - ) - ..methods.addAll([ - Method( - (final b) => b - ..name = 'fromResponse' - ..returns = refer('Future<${classPrefix}ApiException>') - ..static = true - ..modifier = MethodModifier.async - ..requiredParameters.add( - Parameter( - (final b) => b - ..name = 'response' - ..type = refer('HttpClientResponse'), - ), - ) - ..body = Block.of([ - const Code('String body;'), - const Code('try {'), - const Code('body = await response.body;'), - const Code('} on FormatException {'), - const Code("body = 'binary';"), - const Code('}'), - const Code(''), - Code('return ${classPrefix}ApiException('), - const Code('response.statusCode,'), - const Code('response.responseHeaders,'), - const Code('body,'), - const Code(');'), - ]), - ), - Method( + if (spec.components?.schemas != null) { + for (final schema in spec.components!.schemas!.entries) { + final identifier = toDartName(schema.key, uppercaseFirstCharacter: true); + if (schema.value.type == null && schema.value.ref == null && schema.value.ofs == null) { + output.add( + TypeDef( (final b) => b - ..name = 'toString' - ..returns = refer('String') - ..annotations.add(refer('override')) - ..lambda = true - ..body = Code( - "'${classPrefix}ApiException(statusCode: \$statusCode, headers: \$headers, body: \$body)'", - ), + ..name = identifier + ..definition = refer('dynamic'), ), - ]), - ).accept(emitter).toString(), - ]; - - for (final tag in tags) { - final isRootClient = tag == null; - final paths = {}; - - if (spec.paths != null) { - for (final path in spec.paths!.keys) { - final pathItem = spec.paths![path]!; - for (final method in pathItem.operations.keys) { - final operation = pathItem.operations[method]!; - if ((tag != null && operation.tags != null && operation.tags!.contains(tag)) || - (tag == null && (operation.tags == null || operation.tags!.isEmpty))) { - if (paths[path] == null) { - paths[path] = PathItem( - description: pathItem.description, - parameters: pathItem.parameters, - ); - } - paths[path] = paths[path]!.copyWithOperations({method: operation}); - } - } - } - } - - output.add( - Class( - (final b) { - if (isRootClient) { - b - ..extend = refer('DynamiteClient') - ..constructors.addAll([ - Constructor( - (final b) => b - ..requiredParameters.add( - Parameter( - (final b) => b - ..name = 'baseURL' - ..toSuper = true, - ), - ) - ..optionalParameters.addAll([ - Parameter( - (final b) => b - ..name = 'baseHeaders' - ..toSuper = true - ..named = true, - ), - Parameter( - (final b) => b - ..name = 'userAgent' - ..toSuper = true - ..named = true, - ), - Parameter( - (final b) => b - ..name = 'httpClient' - ..toSuper = true - ..named = true, - ), - Parameter( - (final b) => b - ..name = 'cookieJar' - ..toSuper = true - ..named = true, - ), - if (hasAnySecurity) ...[ - Parameter( - (final b) => b - ..name = 'authentications' - ..toSuper = true - ..named = true, - ), - ], - ]), - ), - Constructor( - (final b) => b - ..name = 'fromClient' - ..requiredParameters.add( - Parameter( - (final b) => b - ..name = 'client' - ..type = refer('DynamiteClient'), - ), - ) - ..initializers.add( - const Code(''' - super( - client.baseURL, - baseHeaders: client.baseHeaders, - httpClient: client.httpClient, - cookieJar: client.cookieJar, - authentications: client.authentications, - ) - '''), - ), - ), - ]); - } else { - b - ..fields.add( - Field( - (final b) => b - ..name = '_rootClient' - ..type = refer('${classPrefix}Client') - ..modifier = FieldModifier.final$, - ), - ) - ..constructors.add( - Constructor( - (final b) => b.requiredParameters.add( - Parameter( - (final b) => b - ..name = '_rootClient' - ..toThis = true, - ), - ), - ), - ); - } - final matchedTags = spec.tags?.where((final t) => t.name == tag).toList(); - b - ..name = '$classPrefix${isRootClient ? 'Client' : _clientName(tag)}' - ..docs.addAll( - _descriptionToDocs( - matchedTags != null && matchedTags.isNotEmpty ? matchedTags.single.description : null, - ), - ) - ..methods.addAll( - [ - for (final t in tags - .whereType() - .where( - (final t) => (tag != null && (t.startsWith('$tag/'))) || (tag == null && !t.contains('/')), - ) - .toList()) ...[ - Method( - (final b) => b - ..name = _toDartName(tag == null ? t : t.substring('$tag/'.length)) - ..lambda = true - ..type = MethodType.getter - ..returns = refer('$classPrefix${_clientName(t)}') - ..body = Code('$classPrefix${_clientName(t)}(${isRootClient ? 'this' : '_rootClient'})'), - ), - ], - for (final path in paths.keys) ...[ - for (final httpMethod in paths[path]!.operations.keys) ...[ - Method( - (final b) { - final operation = paths[path]!.operations[httpMethod]!; - final operationId = operation.operationId ?? _toDartName('$httpMethod-$path'); - final pathParameters = [ - if (paths[path]!.parameters != null) ...paths[path]!.parameters!, - ]; - final parameters = [ - ...pathParameters, - if (operation.parameters != null) ...operation.parameters!, - ]..sort( - (final a, final b) => sortRequiredElements( - _isDartParameterRequired( - a.required, - a.schema?.default_, - ), - _isDartParameterRequired( - b.required, - b.schema?.default_, - ), - ), - ); - b - ..name = _toDartName(_filterMethodName(operationId, tag ?? '')) - ..modifier = MethodModifier.async - ..docs.addAll([ - ..._descriptionToDocs(operation.summary), - if (operation.summary != null && operation.description != null) ...[ - '///', - ], - ..._descriptionToDocs(operation.description), - ]); - if (operation.deprecated ?? false) { - b.annotations.add(refer('Deprecated').call([refer("''")])); - } - - final acceptHeader = (operation.responses?.values - .map((final response) => response.content?.keys) - .whereNotNull() - .expand((final element) => element) - .toSet() ?? - {}) - .join(','); - final code = StringBuffer(''' - var _path = '$path'; - final _queryParameters = {}; - final _headers = {${acceptHeader.isNotEmpty ? "'Accept': '$acceptHeader'," : ''}}; - Uint8List? _body; - '''); - - final security = operation.security ?? spec.security ?? []; - final securityRequirements = security.where((final requirement) => requirement.isNotEmpty); - final isOptionalSecurity = securityRequirements.length != security.length; - code.write(' // coverage:ignore-start\n'); - for (final requirement in securityRequirements) { - final securityScheme = spec.components!.securitySchemes![requirement.keys.single]!; - code.write(''' - if (${isRootClient ? 'this' : '_rootClient'}.authentications.where((final a) => a.type == '${securityScheme.type}' && a.scheme == '${securityScheme.scheme}').isNotEmpty) { - _headers.addAll(${isRootClient ? 'this' : '_rootClient'}.authentications.singleWhere((final a) => a.type == '${securityScheme.type}' && a.scheme == '${securityScheme.scheme}').headers); - } - '''); - if (securityRequirements.last != requirement) { - code.write('else'); - } - } - if (securityRequirements.isNotEmpty && !isOptionalSecurity) { - code.write(''' - else { - throw Exception('Missing authentication for ${securityRequirements.map((final r) => r.keys.single).join(' or ')}'); - } - '''); - } - code.write(' // coverage:ignore-end\n'); - - for (final parameter in parameters) { - final dartParameterNullable = _isDartParameterNullable( - parameter.required, - parameter.schema?.nullable, - parameter.schema?.default_, - ); - final dartParameterRequired = _isDartParameterRequired( - parameter.required, - parameter.schema?.default_, - ); - - final result = resolveType( - spec, - variablePrefix, - state, - _toDartName( - '$operationId-${parameter.name}', - uppercaseFirstCharacter: true, - ), - parameter.schema!, - nullable: dartParameterNullable, - ).dartType; - - if (result.name == 'String') { - if (parameter.schema?.pattern != null) { - code.write(''' - if (!RegExp(r'${parameter.schema!.pattern!}').hasMatch(${_toDartName(parameter.name)})) { - throw Exception('Invalid value "\$${_toDartName(parameter.name)}" for parameter "${_toDartName(parameter.name)}" with pattern "\${r'${parameter.schema!.pattern!}'}"'); // coverage:ignore-line - } - '''); - } - if (parameter.schema?.minLength != null) { - code.write(''' - if (${_toDartName(parameter.name)}.length < ${parameter.schema!.minLength!}) { - throw Exception('Parameter "${_toDartName(parameter.name)}" has to be at least ${parameter.schema!.minLength!} characters long'); // coverage:ignore-line - } - '''); - } - if (parameter.schema?.maxLength != null) { - code.write(''' - if (${_toDartName(parameter.name)}.length > ${parameter.schema!.maxLength!}) { - throw Exception('Parameter "${_toDartName(parameter.name)}" has to be at most ${parameter.schema!.maxLength!} characters long'); // coverage:ignore-line - } - '''); - } - } - - final defaultValueCode = parameter.schema?.default_ != null - ? _valueToEscapedValue(result, parameter.schema!.default_!.toString()) - : null; - - b.optionalParameters.add( - Parameter( - (final b) { - b - ..named = true - ..name = _toDartName(parameter.name) - ..required = dartParameterRequired; - if (parameter.schema != null) { - b.type = refer(result.nullableName); - } - if (defaultValueCode != null) { - b.defaultTo = Code(defaultValueCode); - } - }, - ), - ); - - if (dartParameterNullable) { - code.write('if (${_toDartName(parameter.name)} != null) {'); - } - final value = result.encode( - _toDartName(parameter.name), - onlyChildren: result is TypeResultList && parameter.in_ == 'query', - ); - if (defaultValueCode != null && parameter.in_ == 'query') { - code.write('if (${_toDartName(parameter.name)} != $defaultValueCode) {'); - } - switch (parameter.in_) { - case 'path': - code.write( - "_path = _path.replaceAll('{${parameter.name}}', Uri.encodeQueryComponent($value));", - ); - case 'query': - code.write( - "_queryParameters['${parameter.name}'] = $value;", - ); - case 'header': - code.write( - "_headers['${parameter.name}'] = $value;", - ); - default: - throw Exception('Can not work with parameter in "${parameter.in_}"'); - } - if (defaultValueCode != null && parameter.in_ == 'query') { - code.write('}'); - } - if (dartParameterNullable) { - code.write('}'); - } - } - - if (operation.requestBody != null) { - if (operation.requestBody!.content!.length > 1) { - throw Exception('Can not work with multiple mime types right now'); - } - for (final mimeType in operation.requestBody!.content!.keys) { - final mediaType = operation.requestBody!.content![mimeType]!; - - code.write("_headers['Content-Type'] = '$mimeType';"); - - final dartParameterNullable = _isDartParameterNullable( - operation.requestBody!.required, - mediaType.schema?.nullable, - mediaType.schema?.default_, - ); - - final result = resolveType( - spec, - variablePrefix, - state, - _toDartName('$operationId-request-$mimeType', uppercaseFirstCharacter: true), - mediaType.schema!, - nullable: dartParameterNullable, - ); - final parameterName = _toDartName(result.name.replaceFirst(classPrefix, '')); - switch (mimeType) { - case 'application/json': - case 'application/x-www-form-urlencoded': - final dartParameterRequired = _isDartParameterRequired( - operation.requestBody!.required, - mediaType.schema?.default_, - ); - b.optionalParameters.add( - Parameter( - (final b) => b - ..name = parameterName - ..type = refer(result.nullableName) - ..named = true - ..required = dartParameterRequired, - ), - ); - - if (dartParameterNullable) { - code.write('if ($parameterName != null) {'); - } - code.write( - '_body = utf8.encode(${result.encode(parameterName, mimeType: mimeType)}) as Uint8List;', - ); - if (dartParameterNullable) { - code.write('}'); - } - default: - throw Exception('Can not parse mime type "$mimeType"'); - } - } - } - - code.write( - ''' - final _response = await ${isRootClient ? 'this' : '_rootClient'}.doRequest( - '$httpMethod', - Uri(path: _path, queryParameters: _queryParameters.isNotEmpty ? _queryParameters : null), - _headers, - _body, - ); - ''', - ); - - if (operation.responses != null) { - if (operation.responses!.length > 1) { - throw Exception('Can not work with multiple status codes right now'); - } - for (final statusCode in operation.responses!.keys) { - final response = operation.responses![statusCode]!; - code.write('if (_response.statusCode == $statusCode) {'); - - String? headersType; - String? headersValue; - if (response.headers != null) { - final identifier = - '${tag != null ? _toDartName(tag, uppercaseFirstCharacter: true) : null}${_toDartName(operationId, uppercaseFirstCharacter: true)}Headers'; - final result = resolveObject( - spec, - variablePrefix, - state, - identifier, - Schema( - properties: response.headers!.map( - (final headerName, final value) => MapEntry( - headerName.toLowerCase(), - value.schema!, - ), - ), - ), - isHeader: true, - ); - headersType = result.name; - headersValue = result.deserialize('_response.responseHeaders'); - } - - String? dataType; - String? dataValue; - bool? dataNeedsAwait; - if (response.content != null) { - if (response.content!.length > 1) { - throw Exception('Can not work with multiple mime types right now'); - } - for (final mimeType in response.content!.keys) { - final mediaType = response.content![mimeType]!; - - final result = resolveType( - spec, - variablePrefix, - state, - _toDartName( - '$operationId-response-$statusCode-$mimeType', - uppercaseFirstCharacter: true, - ), - mediaType.schema!, - ); - - if (mimeType == '*/*' || - mimeType == 'application/octet-stream' || - mimeType.startsWith('image/')) { - dataType = 'Uint8List'; - dataValue = '_response.bodyBytes'; - dataNeedsAwait = true; - } else if (mimeType.startsWith('text/') || mimeType == 'application/javascript') { - dataType = 'String'; - dataValue = '_response.body'; - dataNeedsAwait = true; - } else if (mimeType == 'application/json') { - dataType = result.name; - if (result.name == 'dynamic') { - dataValue = ''; - } else if (result.name == 'String') { - dataValue = '_response.body'; - dataNeedsAwait = true; - } else if (result is TypeResultEnum || result is TypeResultBase) { - dataValue = result.deserialize(result.decode('await _response.body')); - dataNeedsAwait = false; - } else { - dataValue = result.deserialize('await _response.jsonBody'); - dataNeedsAwait = false; - } - } else { - throw Exception('Can not parse mime type "$mimeType"'); - } - } - } - - if (headersType != null && dataType != null) { - b.returns = refer('Future<${classPrefix}Response<$dataType, $headersType>>'); - code.write( - 'return ${classPrefix}Response<$dataType, $headersType>(${dataNeedsAwait ?? false ? 'await ' : ''}$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 { - b.returns = refer('Future'); - code.write('return;'); - } - - code.write('}'); - } - code.write( - 'throw await ${classPrefix}ApiException.fromResponse(_response); // coverage:ignore-line\n', - ); - } else { - b.returns = refer('Future'); - } - b.body = Code(code.toString()); - }, - ), - ], - ], - ], - ); - }, - ).accept(emitter).toString(), - ); - } - - if (spec.components?.schemas != null) { - for (final name in spec.components!.schemas!.keys) { - final schema = spec.components!.schemas![name]!; - - final identifier = _toDartName(name, uppercaseFirstCharacter: true); - if (schema.type == null && schema.ref == null && schema.ofs == null) { - output.add('typedef $identifier = dynamic;'); + ); } else { final result = resolveType( spec, - variablePrefix, state, identifier, - schema, + schema.value, ); if (result is TypeResultBase) { - output.add('typedef $identifier = ${result.name};'); + output.add( + TypeDef( + (final b) => b + ..name = identifier + ..definition = refer(result.name), + ), + ); } } } } - output.addAll(state.output.map((final e) => e.accept(emitter).toString())); - - if (state.resolvedTypes.isNotEmpty) { - output.addAll([ - '// coverage:ignore-start', - 'final Serializers _serializers = (Serializers().toBuilder()', - ...state.resolvedTypes.map((final type) => type.serializers).expand((final element) => element).toSet(), - ').build();', - '', - 'Serializers get ${variablePrefix}Serializers => _serializers;', - '', - 'final Serializers _jsonSerializers = (_serializers.toBuilder()..addPlugin(StandardJsonPlugin())..addPlugin(const ContentStringPlugin())).build();', - '', - 'T deserialize$classPrefix(final Object data) => _serializers.deserialize(data, specifiedType: FullType(T))! as T;', - '', - 'Object? serialize$classPrefix(final T data) => _serializers.serialize(data, specifiedType: FullType(T));', - '// coverage:ignore-end', - ]); - } + output + ..addAll(state.output) + ..addAll(buildSerializer(state)); final patterns = [ RegExp( @@ -736,7 +96,8 @@ class OpenAPIBuilder implements Builder { r'static BuiltSet<.*> get values => _\$.*Values;', ), ]; - var outputString = output.join('\n'); + + var outputString = output.build().map((final e) => e.accept(emitter)).join('\n'); for (final pattern in patterns) { outputString = outputString.replaceAllMapped( pattern, @@ -755,1009 +116,3 @@ class OpenAPIBuilder implements Builder { } } } - -String _clientName(final String tag) => '${_toDartName(tag, uppercaseFirstCharacter: true)}Client'; - -String _toDartName( - final String name, { - final bool uppercaseFirstCharacter = false, -}) { - var result = ''; - var upperCase = uppercaseFirstCharacter; - var firstCharacter = !uppercaseFirstCharacter; - for (final char in name.split('')) { - if (_isNonAlphaNumericString(char)) { - upperCase = true; - } else { - result += firstCharacter ? char.toLowerCase() : (upperCase ? char.toUpperCase() : char); - upperCase = false; - firstCharacter = false; - } - } - - if (_dartKeywords.contains(result) || RegExp(r'^[0-9]+$', multiLine: true).hasMatch(result)) { - return '\$$result'; - } - - return result; -} - -final _dartKeywords = [ - 'assert', - 'break', - 'case', - 'catch', - 'class', - 'const', - 'continue', - 'default', - 'do', - 'else', - 'enum', - 'extends', - 'false', - 'final', - 'finally', - 'for', - 'if', - 'in', - 'is', - 'new', - 'null', - 'rethrow', - 'return', - 'super', - 'switch', - 'this', - 'throw', - 'true', - 'try', - 'var', - 'void', - 'while', - 'with', - 'async', - 'hide', - 'on', - 'show', - 'sync', - 'abstract', - 'as', - 'covariant', - 'deferred', - 'dynamic', - 'export', - 'extension', - 'external', - 'factory', - 'function', - 'get', - 'implements', - 'import', - 'interface', - 'library', - 'mixin', - 'operator', - 'part', - 'set', - 'static', - 'typedef', -]; - -bool _isNonAlphaNumericString(final String input) => !RegExp(r'^[a-zA-Z0-9]$').hasMatch(input); - -String _toFieldName(final String dartName, final String type) => dartName == type ? '\$$dartName' : dartName; - -bool _isDartParameterNullable( - final bool? required, - final bool? nullable, - final dynamic default_, -) => - (!(required ?? false) && default_ == null) || (nullable ?? false); - -bool _isDartParameterRequired( - final bool? required, - final dynamic default_, -) => - (required ?? false) && default_ == null; - -String _valueToEscapedValue(final TypeResult result, final dynamic value) { - if (result is TypeResultBase && result.name == 'String') { - return "'$value'"; - } - if (result is TypeResultList) { - return 'const $value'; - } - if (result is TypeResultEnum) { - return '${result.name}.${_toDartName(value.toString())}'; - } - return value.toString(); -} - -String _toCamelCase(final String name) { - var result = ''; - var upperCase = false; - var firstCharacter = true; - for (final char in name.split('')) { - if (char == '_') { - upperCase = true; - } else if (char == r'$') { - result += r'$'; - } else { - result += firstCharacter ? char.toLowerCase() : (upperCase ? char.toUpperCase() : char); - upperCase = false; - firstCharacter = false; - } - } - return result; -} - -List _descriptionToDocs(final String? description) => [ - if (description != null && description.isNotEmpty) ...[ - for (final line in description.split('\n')) ...[ - '/// $line', - ], - ], - ]; - -String _filterMethodName(final String operationId, final String tag) { - final expandedTag = tag.split('/').toList(); - final parts = operationId.split('-'); - final output = []; - for (var i = 0; i < parts.length; i++) { - if (expandedTag.length <= i || expandedTag[i] != parts[i]) { - output.add(parts[i]); - } - } - return output.join('-'); -} - -class State { - State(this.prefix); - - final String prefix; - final output = []; - final resolvedTypes = {}; -} - -TypeResult resolveObject( - final OpenAPI spec, - final String variablePrefix, - final State state, - final String identifier, - final Schema schema, { - final bool nullable = false, - final bool isHeader = false, -}) { - final result = TypeResultObject( - '${state.prefix}$identifier', - nullable: nullable, - ); - if (state.resolvedTypes.add(result)) { - state.output.add( - Class( - (final b) { - b - ..name = '${state.prefix}$identifier' - ..docs.addAll(_descriptionToDocs(schema.description)) - ..abstract = true - ..implements.add( - refer( - 'Built<${state.prefix}$identifier, ${state.prefix}${identifier}Builder>', - ), - ) - ..constructors.addAll([ - Constructor( - (final b) => b - ..factory = true - ..lambda = true - ..optionalParameters.add( - Parameter( - (final b) => b - ..name = 'b' - ..type = refer('void Function(${state.prefix}${identifier}Builder)?'), - ), - ) - ..redirect = refer('_\$${state.prefix}$identifier'), - ), - Constructor( - (final b) => b - ..name = '_' - ..constant = true, - ), - Constructor( - (final b) => b - ..factory = true - ..name = 'fromJson' - ..lambda = true - ..requiredParameters.add( - Parameter( - (final b) => b - ..name = 'json' - ..type = refer('Map'), - ), - ) - ..body = const Code('_jsonSerializers.deserializeWith(serializer, json)!'), - ), - ]) - ..methods.addAll([ - Method( - (final b) => b - ..name = 'toJson' - ..returns = refer('Map') - ..lambda = true - ..body = const Code('_jsonSerializers.serializeWith(serializer, this)! as Map'), - ), - for (final propertyName in schema.properties!.keys) ...[ - Method( - (final b) { - final propertySchema = schema.properties![propertyName]!; - final result = resolveType( - spec, - variablePrefix, - state, - '${identifier}_${_toDartName(propertyName, uppercaseFirstCharacter: true)}', - propertySchema, - nullable: _isDartParameterNullable( - schema.required?.contains(propertyName), - propertySchema.nullable, - propertySchema.default_, - ), - ); - - b - ..name = _toDartName(propertyName) - ..returns = refer(result.nullableName) - ..type = MethodType.getter - ..docs.addAll(_descriptionToDocs(propertySchema.description)); - - if (_toDartName(propertyName) != propertyName) { - b.annotations.add( - refer('BuiltValueField').call([], { - 'wireName': literalString(propertyName), - }), - ); - } - }, - ), - ], - Method((final b) { - b - ..name = 'serializer' - ..returns = refer('Serializer<${state.prefix}$identifier>') - ..lambda = true - ..static = true - ..body = Code( - isHeader - ? '_\$${state.prefix}${identifier}Serializer()' - : "_\$${_toCamelCase('${state.prefix}$identifier')}Serializer", - ) - ..type = MethodType.getter; - if (isHeader) { - b.annotations.add(refer('BuiltValueSerializer').call([], {'custom': refer('true')})); - } - }), - ]); - - final defaults = []; - for (final propertyName in schema.properties!.keys) { - final propertySchema = schema.properties![propertyName]!; - if (propertySchema.default_ != null) { - final value = propertySchema.default_!.toString(); - final result = resolveType( - spec, - variablePrefix, - state, - propertySchema.type!, - propertySchema, - ); - defaults.add('..${_toDartName(propertyName)} = ${_valueToEscapedValue(result, value)}'); - } - } - if (defaults.isNotEmpty) { - b.methods.add( - Method( - (final b) => b - ..name = '_defaults' - ..returns = refer('void') - ..static = true - ..lambda = true - ..annotations.add( - refer('BuiltValueHook').call( - [], - { - 'initializeBuilder': refer('true'), - }, - ), - ) - ..requiredParameters.add( - Parameter( - (final b) => b - ..name = 'b' - ..type = refer('${state.prefix}${identifier}Builder'), - ), - ) - ..body = Code( - [ - 'b', - ...defaults, - ].join(), - ), - ), - ); - } - }, - ), - ); - if (isHeader) { - state.output.add( - Class( - (final b) => b - ..name = '_\$${state.prefix}${identifier}Serializer' - ..implements.add(refer('StructuredSerializer<${state.prefix}$identifier>')) - ..fields.addAll([ - Field( - (final b) => b - ..name = 'types' - ..modifier = FieldModifier.final$ - ..type = refer('Iterable') - ..annotations.add(refer('override')) - ..assignment = Code('const [${state.prefix}$identifier, _\$${state.prefix}$identifier]'), - ), - Field( - (final b) => b - ..name = 'wireName' - ..modifier = FieldModifier.final$ - ..type = refer('String') - ..annotations.add(refer('override')) - ..assignment = Code("r'${state.prefix}$identifier'"), - ), - ]) - ..methods.addAll([ - Method((final b) { - b - ..name = 'serialize' - ..returns = refer('Iterable') - ..annotations.add(refer('override')) - ..requiredParameters.addAll([ - Parameter( - (final b) => b - ..name = 'serializers' - ..type = refer('Serializers'), - ), - Parameter( - (final b) => b - ..name = 'object' - ..type = refer('${state.prefix}$identifier'), - ), - ]) - ..optionalParameters.add( - Parameter( - (final b) => b - ..name = 'specifiedType' - ..type = refer('FullType') - ..named = true - ..defaultTo = const Code('FullType.unspecified'), - ), - ) - ..body = const Code('throw UnimplementedError();'); - }), - Method((final b) { - b - ..name = 'deserialize' - ..returns = refer('${state.prefix}$identifier') - ..annotations.add(refer('override')) - ..requiredParameters.addAll([ - Parameter( - (final b) => b - ..name = 'serializers' - ..type = refer('Serializers'), - ), - Parameter( - (final b) => b - ..name = 'serialized' - ..type = refer('Iterable'), - ), - ]) - ..optionalParameters.add( - Parameter( - (final b) => b - ..name = 'specifiedType' - ..type = refer('FullType') - ..named = true - ..defaultTo = const Code('FullType.unspecified'), - ), - ); - List deserializeProperty(final String propertyName) { - final propertySchema = schema.properties![propertyName]!; - final result = resolveType( - spec, - variablePrefix, - state, - '${identifier}_${_toDartName(propertyName, uppercaseFirstCharacter: true)}', - propertySchema, - nullable: _isDartParameterNullable( - schema.required?.contains(propertyName), - propertySchema.nullable, - propertySchema.default_, - ), - ); - - return [ - Code("case '$propertyName':"), - if (result.className != 'String') ...[ - if (result is TypeResultBase || result is TypeResultEnum) ...[ - Code( - 'result.${_toDartName(propertyName)} = ${result.deserialize(result.decode('value!'))};', - ), - ] else ...[ - Code( - 'result.${_toDartName(propertyName)}.replace(${result.deserialize(result.decode('value!'))});', - ), - ], - ] else ...[ - Code( - 'result.${_toDartName(propertyName)} = value!;', - ), - ], - ]; - } - - b.body = Block.of([ - Code('final result = new ${state.prefix}${identifier}Builder();'), - const Code(''), - const Code('final iterator = serialized.iterator;'), - const Code('while (iterator.moveNext()) {'), - const Code('final key = iterator.current! as String;'), - const Code('iterator.moveNext();'), - const Code('final value = iterator.current! as String;'), - const Code('switch (key) {'), - for (final propertyName in schema.properties!.keys) ...[ - ...deserializeProperty(propertyName), - ], - const Code('}'), - const Code('}'), - const Code(''), - const Code('return result.build();'), - ]); - }), - ]), - ), - ); - } - } - return result; -} - -TypeResult resolveType( - final OpenAPI spec, - final String variablePrefix, - final State state, - final String identifier, - final Schema schema, { - final bool ignoreEnum = false, - final bool nullable = false, -}) { - TypeResult? result; - if (schema.ref == null && schema.ofs == null && schema.type == null) { - return TypeResultBase( - 'JsonObject', - nullable: nullable, - ); - } - if (schema.ref != null) { - final name = schema.ref!.split('/').last; - result = resolveType( - spec, - variablePrefix, - state, - name, - spec.components!.schemas![name]!, - nullable: nullable, - ); - } else if (schema.ofs != null) { - result = TypeResultObject( - '${state.prefix}$identifier', - nullable: nullable, - ); - if (state.resolvedTypes.add(result)) { - final results = schema.ofs! - .map( - (final s) => resolveType( - spec, - variablePrefix, - state, - '$identifier${schema.ofs!.indexOf(s)}', - s, - nullable: !(schema.allOf?.contains(s) ?? false), - ), - ) - .toList(); - - final fields = {}; - for (final result in results) { - final dartName = _toDartName(result.name.replaceFirst(state.prefix, '')); - fields[result.name] = _toFieldName(dartName, result.name.replaceFirst(state.prefix, '')); - } - - state.output.addAll([ - Class( - (final b) { - b - ..name = '${state.prefix}$identifier' - ..abstract = true - ..implements.add( - refer( - 'Built<${state.prefix}$identifier, ${state.prefix}${identifier}Builder>', - ), - ) - ..constructors.addAll([ - Constructor( - (final b) => b - ..name = '_' - ..constant = true, - ), - Constructor( - (final b) => b - ..factory = true - ..lambda = true - ..optionalParameters.add( - Parameter( - (final b) => b - ..name = 'b' - ..type = refer('void Function(${state.prefix}${identifier}Builder)?'), - ), - ) - ..redirect = refer('_\$${state.prefix}$identifier'), - ), - ]) - ..methods.addAll([ - Method( - (final b) { - b - ..name = 'data' - ..returns = refer('JsonObject') - ..type = MethodType.getter; - }, - ), - for (final result in results) ...[ - Method( - (final b) { - final s = schema.ofs![results.indexOf(result)]; - b - ..name = fields[result.name] - ..returns = refer(result.nullableName) - ..type = MethodType.getter - ..docs.addAll(_descriptionToDocs(s.description)); - }, - ), - ], - Method( - (final b) => b - ..static = true - ..name = 'fromJson' - ..lambda = true - ..returns = refer('${state.prefix}$identifier') - ..requiredParameters.add( - Parameter( - (final b) => b - ..name = 'json' - ..type = refer('Object'), - ), - ) - ..body = const Code('_jsonSerializers.deserializeWith(serializer, json)!'), - ), - Method( - (final b) => b - ..name = 'toJson' - ..returns = refer('Map') - ..lambda = true - ..body = const Code('_jsonSerializers.serializeWith(serializer, this)! as Map'), - ), - Method( - (final b) => b - ..name = 'serializer' - ..returns = refer('Serializer<${state.prefix}$identifier>') - ..lambda = true - ..static = true - ..annotations.add(refer('BuiltValueSerializer').call([], {'custom': refer('true')})) - ..body = Code('_\$${state.prefix}${identifier}Serializer()') - ..type = MethodType.getter, - ), - ]); - }, - ), - Class( - (final b) => b - ..name = '_\$${state.prefix}${identifier}Serializer' - ..implements.add(refer('PrimitiveSerializer<${state.prefix}$identifier>')) - ..fields.addAll([ - Field( - (final b) => b - ..name = 'types' - ..modifier = FieldModifier.final$ - ..type = refer('Iterable') - ..annotations.add(refer('override')) - ..assignment = Code('const [${state.prefix}$identifier, _\$${state.prefix}$identifier]'), - ), - Field( - (final b) => b - ..name = 'wireName' - ..modifier = FieldModifier.final$ - ..type = refer('String') - ..annotations.add(refer('override')) - ..assignment = Code("r'${state.prefix}$identifier'"), - ), - ]) - ..methods.addAll([ - Method((final b) { - b - ..name = 'serialize' - ..returns = refer('Object') - ..annotations.add(refer('override')) - ..requiredParameters.addAll([ - Parameter( - (final b) => b - ..name = 'serializers' - ..type = refer('Serializers'), - ), - Parameter( - (final b) => b - ..name = 'object' - ..type = refer('${state.prefix}$identifier'), - ), - ]) - ..optionalParameters.add( - Parameter( - (final b) => b - ..name = 'specifiedType' - ..type = refer('FullType') - ..named = true - ..defaultTo = const Code('FullType.unspecified'), - ), - ) - ..body = const Code('return object.data.value;'); - }), - Method((final b) { - b - ..name = 'deserialize' - ..returns = refer('${state.prefix}$identifier') - ..annotations.add(refer('override')) - ..requiredParameters.addAll([ - Parameter( - (final b) => b - ..name = 'serializers' - ..type = refer('Serializers'), - ), - Parameter( - (final b) => b - ..name = 'data' - ..type = refer('Object'), - ), - ]) - ..optionalParameters.add( - Parameter( - (final b) => b - ..name = 'specifiedType' - ..type = refer('FullType') - ..named = true - ..defaultTo = const Code('FullType.unspecified'), - ), - ) - ..body = Code( - [ - 'final result = new ${state.prefix}${identifier}Builder()', - '..data = JsonObject(data);', - if (schema.allOf != null) ...[ - for (final result in results) ...[ - if (result is TypeResultBase || result is TypeResultEnum) ...[ - 'result.${fields[result.name]!} = ${result.deserialize('data')};', - ] else ...[ - 'result.${fields[result.name]!}.replace(${result.deserialize('data')});', - ], - ], - ] else ...[ - if (schema.discriminator != null) ...[ - 'if (data is! Iterable) {', - r"throw StateError('Expected an Iterable but got ${data.runtimeType}');", - '}', - '', - 'String? discriminator;', - '', - 'final iterator = data.iterator;', - 'while (iterator.moveNext()) {', - 'final key = iterator.current! as String;', - 'iterator.moveNext();', - 'final Object? value = iterator.current;', - "if (key == '${schema.discriminator!.propertyName}') {", - 'discriminator = value! as String;', - 'break;', - '}', - '}', - ], - for (final result in results) ...[ - if (schema.discriminator != null) ...[ - "if (discriminator == '${result.name.replaceFirst(state.prefix, '')}'", - if (schema.discriminator!.mapping != null && schema.discriminator!.mapping!.isNotEmpty) ...[ - for (final key in schema.discriminator!.mapping!.entries - .where( - (final entry) => - entry.value.endsWith('/${result.name.replaceFirst(state.prefix, '')}'), - ) - .map((final entry) => entry.key)) ...[ - " || discriminator == '$key'", - ], - ') {', - ], - ], - 'try {', - if (result is TypeResultBase || result is TypeResultEnum) ...[ - 'result._${fields[result.name]!} = ${result.deserialize('data')};', - ] else ...[ - 'result._${fields[result.name]!} = ${result.deserialize('data')}.toBuilder();', - ], - '} catch (_) {', - if (schema.discriminator != null) ...[ - 'rethrow;', - ], - '}', - if (schema.discriminator != null) ...[ - '}', - ], - ], - if (schema.oneOf != null) ...[ - "assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need oneOf for \${result._data}');", - ], - if (schema.anyOf != null) ...[ - "assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need anyOf for \${result._data}');", - ], - ], - 'return result.build();', - ].join(), - ); - }), - ]), - ), - ]); - } - } else if (schema.isContentString) { - final subResult = resolveType( - spec, - variablePrefix, - state, - identifier, - schema.contentSchema!, - ); - - result = TypeResultObject( - 'ContentString', - generics: [subResult], - nullable: nullable, - ); - } else { - switch (schema.type) { - case 'boolean': - result = TypeResultBase( - 'bool', - nullable: nullable, - ); - case 'integer': - result = TypeResultBase( - 'int', - nullable: nullable, - ); - case 'number': - result = TypeResultBase( - 'num', - nullable: nullable, - ); - case 'string': - switch (schema.format) { - case 'binary': - result = TypeResultBase( - 'Uint8List', - nullable: nullable, - ); - } - - result = TypeResultBase( - 'String', - nullable: nullable, - ); - case 'array': - if (schema.items != null) { - final subResult = resolveType( - spec, - variablePrefix, - state, - identifier, - schema.items!, - ); - result = TypeResultList( - 'BuiltList', - subResult, - nullable: nullable, - ); - } else { - result = TypeResultList( - 'BuiltList', - TypeResultBase('JsonObject'), - nullable: nullable, - ); - } - case 'object': - if (schema.properties == null) { - if (schema.additionalProperties != null) { - if (schema.additionalProperties is EmptySchema) { - result = TypeResultMap( - 'BuiltMap', - TypeResultBase('JsonObject'), - nullable: nullable, - ); - } else { - final subResult = resolveType( - spec, - variablePrefix, - state, - identifier, - schema.additionalProperties!, - ); - result = TypeResultMap( - 'BuiltMap', - subResult, - nullable: nullable, - ); - } - break; - } - result = TypeResultBase( - 'JsonObject', - nullable: nullable, - ); - break; - } - if (schema.properties!.isEmpty) { - result = TypeResultMap( - 'BuiltMap', - TypeResultBase('JsonObject'), - nullable: nullable, - ); - break; - } - - result = resolveObject( - spec, - variablePrefix, - state, - identifier, - schema, - nullable: nullable, - ); - } - } - - if (result != null) { - if (!ignoreEnum && schema.enum_ != null) { - if (state.resolvedTypes.add(TypeResultEnum('${state.prefix}$identifier', result))) { - state.output.add( - Class( - (final b) => b - ..name = '${state.prefix}$identifier' - ..extend = refer('EnumClass') - ..constructors.add( - Constructor( - (final b) => b - ..name = '_' - ..constant = true - ..requiredParameters.add( - Parameter( - (final b) => b - ..name = 'name' - ..toSuper = true, - ), - ), - ), - ) - ..fields.addAll( - schema.enum_!.map( - (final value) => Field( - (final b) { - final result = resolveType( - spec, - variablePrefix, - state, - '$identifier${_toDartName(value.toString(), uppercaseFirstCharacter: true)}', - schema, - ignoreEnum: true, - ); - b - ..name = _toDartName(value.toString()) - ..static = true - ..modifier = FieldModifier.constant - ..type = refer('${state.prefix}$identifier') - ..assignment = Code( - '_\$${_toCamelCase('${state.prefix}$identifier')}${_toDartName(value.toString(), uppercaseFirstCharacter: true)}', - ); - - if (_toDartName(value.toString()) != value.toString()) { - if (result.name != 'String' && result.name != 'int') { - throw Exception( - 'Sorry enum values are a bit broken. ' - 'See https://github.com/google/json_serializable.dart/issues/616. ' - 'Please remove the enum values on ${state.prefix}$identifier.', - ); - } - b.annotations.add( - refer('BuiltValueEnumConst').call([], { - 'wireName': refer(_valueToEscapedValue(result, value.toString())), - }), - ); - } - }, - ), - ), - ) - ..methods.addAll([ - Method( - (final b) => b - ..name = 'values' - ..returns = refer('BuiltSet<${state.prefix}$identifier>') - ..lambda = true - ..static = true - ..body = Code('_\$${_toCamelCase('${state.prefix}$identifier')}Values') - ..type = MethodType.getter, - ), - Method( - (final b) => b - ..name = 'valueOf' - ..returns = refer('${state.prefix}$identifier') - ..lambda = true - ..static = true - ..requiredParameters.add( - Parameter( - (final b) => b - ..name = 'name' - ..type = refer(result!.name), - ), - ) - ..body = Code('_\$valueOf${state.prefix}$identifier(name)'), - ), - Method( - (final b) => b - ..name = 'serializer' - ..returns = refer('Serializer<${state.prefix}$identifier>') - ..lambda = true - ..static = true - ..body = Code("_\$${_toCamelCase('${state.prefix}$identifier')}Serializer") - ..type = MethodType.getter, - ), - ]), - ), - ); - } - result = TypeResultEnum( - '${state.prefix}$identifier', - result, - nullable: nullable, - ); - } - - state.resolvedTypes.add(result); - return result; - } - - throw Exception('Can not convert OpenAPI type "${schema.toJson()}" to a Dart type'); -} - -// ignore: avoid_positional_boolean_parameters -int sortRequiredElements(final bool a, final bool b) { - if (a != b) { - if (a && !b) { - return -1; - } else { - return 1; - } - } - - return 0; -} diff --git a/packages/dynamite/dynamite/pubspec.yaml b/packages/dynamite/dynamite/pubspec.yaml index 09c75580..69406723 100644 --- a/packages/dynamite/dynamite/pubspec.yaml +++ b/packages/dynamite/dynamite/pubspec.yaml @@ -6,6 +6,7 @@ environment: dependencies: build: ^2.4.1 + built_collection: ^5.1.1 built_value: ^8.6.2 code_builder: ^4.6.0 collection: ^1.17.2 @@ -17,7 +18,6 @@ dependencies: dev_dependencies: build_runner: ^2.4.6 - built_collection: ^5.1.1 built_value_generator: ^8.6.2 json_serializable: ^6.7.1 neon_lints: diff --git a/packages/nextcloud/lib/src/api/core.openapi.dart b/packages/nextcloud/lib/src/api/core.openapi.dart index 6e742e0f..b991668a 100644 --- a/packages/nextcloud/lib/src/api/core.openapi.dart +++ b/packages/nextcloud/lib/src/api/core.openapi.dart @@ -3124,20 +3124,24 @@ abstract class CoreHoverCardGetUserResponse200ApplicationJson } abstract class CoreNavigationEntry_Order implements Built { - // coverage:ignore-end - factory CoreNavigationEntry_Order([final void Function(CoreNavigationEntry_OrderBuilder)? b]) = _$CoreNavigationEntry_Order; + // coverage:ignore-start const CoreNavigationEntry_Order._(); + // coverage:ignore-end + + // coverage:ignore-start + factory CoreNavigationEntry_Order.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - int? get $int; - String? get string; - static CoreNavigationEntry_Order fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + int? get $int; + String? get string; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$CoreNavigationEntry_OrderSerializer(); } @@ -4447,14 +4451,24 @@ abstract class CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_Capabil implements Built { - // coverage:ignore-end - factory CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_Capabilities([ final void Function(CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_CapabilitiesBuilder)? b, ]) = _$CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_Capabilities; + // coverage:ignore-start const CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_Capabilities._(); + // coverage:ignore-end + + // coverage:ignore-start + factory CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_Capabilities.fromJson( + final Map json, + ) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start + Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; + // coverage:ignore-end JsonObject get data; CoreCommentsCapabilities? get commentsCapabilities; CoreDavCapabilities? get davCapabilities; @@ -4469,11 +4483,6 @@ abstract class CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_Capabil CoreThemingPublicCapabilities? get themingPublicCapabilities; CoreUserStatusCapabilities? get userStatusCapabilities; CoreWeatherStatusCapabilities? get weatherStatusCapabilities; - static CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_Capabilities fromJson(final Object json) => - _jsonSerializers.deserializeWith(serializer, json)!; - // coverage:ignore-start - Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; - // coverage:ignore-end @BuiltValueSerializer(custom: true) static Serializer get serializer => _$CoreOcsGetCapabilitiesResponse200ApplicationJson_Ocs_Data_CapabilitiesSerializer(); @@ -4847,22 +4856,27 @@ abstract class CoreReferenceApiResolveResponse200ApplicationJson_Ocs_Data_Refere implements Built { - // coverage:ignore-end - factory CoreReferenceApiResolveResponse200ApplicationJson_Ocs_Data_References([ final void Function(CoreReferenceApiResolveResponse200ApplicationJson_Ocs_Data_ReferencesBuilder)? b, ]) = _$CoreReferenceApiResolveResponse200ApplicationJson_Ocs_Data_References; + // coverage:ignore-start const CoreReferenceApiResolveResponse200ApplicationJson_Ocs_Data_References._(); + // coverage:ignore-end - JsonObject get data; - CoreReference? get reference; - JsonObject? get jsonObject; - static CoreReferenceApiResolveResponse200ApplicationJson_Ocs_Data_References fromJson(final Object json) => + // coverage:ignore-start + factory CoreReferenceApiResolveResponse200ApplicationJson_Ocs_Data_References.fromJson( + final Map json, + ) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + CoreReference? get reference; + JsonObject? get jsonObject; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$CoreReferenceApiResolveResponse200ApplicationJson_Ocs_Data_ReferencesSerializer(); @@ -4991,22 +5005,27 @@ abstract class CoreReferenceApiExtractResponse200ApplicationJson_Ocs_Data_Refere implements Built { - // coverage:ignore-end - factory CoreReferenceApiExtractResponse200ApplicationJson_Ocs_Data_References([ final void Function(CoreReferenceApiExtractResponse200ApplicationJson_Ocs_Data_ReferencesBuilder)? b, ]) = _$CoreReferenceApiExtractResponse200ApplicationJson_Ocs_Data_References; + // coverage:ignore-start const CoreReferenceApiExtractResponse200ApplicationJson_Ocs_Data_References._(); + // coverage:ignore-end - JsonObject get data; - CoreReference? get reference; - JsonObject? get jsonObject; - static CoreReferenceApiExtractResponse200ApplicationJson_Ocs_Data_References fromJson(final Object json) => + // coverage:ignore-start + factory CoreReferenceApiExtractResponse200ApplicationJson_Ocs_Data_References.fromJson( + final Map json, + ) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + CoreReference? get reference; + JsonObject? get jsonObject; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$CoreReferenceApiExtractResponse200ApplicationJson_Ocs_Data_ReferencesSerializer(); @@ -5981,21 +6000,24 @@ abstract class CoreUnifiedSearchGetProvidersResponse200ApplicationJson abstract class CoreUnifiedSearchSearchCursor implements Built { - // coverage:ignore-end - factory CoreUnifiedSearchSearchCursor([final void Function(CoreUnifiedSearchSearchCursorBuilder)? b]) = _$CoreUnifiedSearchSearchCursor; + // coverage:ignore-start const CoreUnifiedSearchSearchCursor._(); + // coverage:ignore-end - JsonObject get data; - int? get $int; - String? get string; - static CoreUnifiedSearchSearchCursor fromJson(final Object json) => + // coverage:ignore-start + factory CoreUnifiedSearchSearchCursor.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + int? get $int; + String? get string; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$CoreUnifiedSearchSearchCursorSerializer(); } @@ -6062,21 +6084,24 @@ abstract class CoreUnifiedSearchResultEntry abstract class CoreUnifiedSearchResult_Cursor implements Built { - // coverage:ignore-end - factory CoreUnifiedSearchResult_Cursor([final void Function(CoreUnifiedSearchResult_CursorBuilder)? b]) = _$CoreUnifiedSearchResult_Cursor; + // coverage:ignore-start const CoreUnifiedSearchResult_Cursor._(); + // coverage:ignore-end - JsonObject get data; - int? get $int; - String? get string; - static CoreUnifiedSearchResult_Cursor fromJson(final Object json) => + // coverage:ignore-start + factory CoreUnifiedSearchResult_Cursor.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + int? get $int; + String? get string; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$CoreUnifiedSearchResult_CursorSerializer(); } diff --git a/packages/nextcloud/lib/src/api/files_sharing.openapi.dart b/packages/nextcloud/lib/src/api/files_sharing.openapi.dart index ed6f8a80..cdf1b421 100644 --- a/packages/nextcloud/lib/src/api/files_sharing.openapi.dart +++ b/packages/nextcloud/lib/src/api/files_sharing.openapi.dart @@ -1641,20 +1641,24 @@ abstract class FilesSharingRemoteUnshareResponse200ApplicationJson abstract class FilesSharingShareInfo_Size implements Built { - // coverage:ignore-end - factory FilesSharingShareInfo_Size([final void Function(FilesSharingShareInfo_SizeBuilder)? b]) = _$FilesSharingShareInfo_Size; + // coverage:ignore-start const FilesSharingShareInfo_Size._(); + // coverage:ignore-end + + // coverage:ignore-start + factory FilesSharingShareInfo_Size.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - int? get $int; - num? get $num; - static FilesSharingShareInfo_Size fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + int? get $int; + num? get $num; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareInfo_SizeSerializer(); } @@ -1722,20 +1726,24 @@ abstract class FilesSharingShareInfo implements Built { - // coverage:ignore-end - factory FilesSharingShare_ItemSize([final void Function(FilesSharingShare_ItemSizeBuilder)? b]) = _$FilesSharingShare_ItemSize; + // coverage:ignore-start const FilesSharingShare_ItemSize._(); + // coverage:ignore-end + + // coverage:ignore-start + factory FilesSharingShare_ItemSize.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - num? get $num; - int? get $int; - static FilesSharingShare_ItemSize fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + num? get $num; + int? get $int; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShare_ItemSizeSerializer(); } @@ -2307,22 +2315,25 @@ abstract class FilesSharingShareapiAcceptShareResponse200ApplicationJson abstract class FilesSharingShareesapiSearchShareType implements Built { - // coverage:ignore-end - factory FilesSharingShareesapiSearchShareType([ final void Function(FilesSharingShareesapiSearchShareTypeBuilder)? b, ]) = _$FilesSharingShareesapiSearchShareType; + // coverage:ignore-start const FilesSharingShareesapiSearchShareType._(); + // coverage:ignore-end - JsonObject get data; - int? get $int; - BuiltList? get builtListInt; - static FilesSharingShareesapiSearchShareType fromJson(final Object json) => + // coverage:ignore-start + factory FilesSharingShareesapiSearchShareType.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + int? get $int; + BuiltList? get builtListInt; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareesapiSearchShareTypeSerializer(); @@ -2500,21 +2511,24 @@ abstract class FilesSharingShareeCircle1_Value1 abstract class FilesSharingShareeCircle1_Value implements Built { - // coverage:ignore-end - factory FilesSharingShareeCircle1_Value([final void Function(FilesSharingShareeCircle1_ValueBuilder)? b]) = _$FilesSharingShareeCircle1_Value; + // coverage:ignore-start const FilesSharingShareeCircle1_Value._(); + // coverage:ignore-end - JsonObject get data; - FilesSharingShareeValue get shareeValue; - FilesSharingShareeCircle1_Value1 get shareeCircle1Value1; - static FilesSharingShareeCircle1_Value fromJson(final Object json) => + // coverage:ignore-start + factory FilesSharingShareeCircle1_Value.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingShareeValue get shareeValue; + FilesSharingShareeCircle1_Value1 get shareeCircle1Value1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeCircle1_ValueSerializer(); } @@ -2579,20 +2593,24 @@ abstract class FilesSharingShareeCircle1 implements Built { - // coverage:ignore-end - factory FilesSharingShareeCircle([final void Function(FilesSharingShareeCircleBuilder)? b]) = _$FilesSharingShareeCircle; + // coverage:ignore-start const FilesSharingShareeCircle._(); + // coverage:ignore-end + + // coverage:ignore-start + factory FilesSharingShareeCircle.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - FilesSharingSharee get sharee; - FilesSharingShareeCircle1 get shareeCircle1; - static FilesSharingShareeCircle fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingSharee get sharee; + FilesSharingShareeCircle1 get shareeCircle1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeCircleSerializer(); } @@ -2657,19 +2675,23 @@ abstract class FilesSharingShareeEmail1 implements Built { - // coverage:ignore-end - factory FilesSharingShareeEmail([final void Function(FilesSharingShareeEmailBuilder)? b]) = _$FilesSharingShareeEmail; + // coverage:ignore-start const FilesSharingShareeEmail._(); + // coverage:ignore-end + + // coverage:ignore-start + factory FilesSharingShareeEmail.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - FilesSharingSharee get sharee; - FilesSharingShareeEmail1 get shareeEmail1; - static FilesSharingShareeEmail fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingSharee get sharee; + FilesSharingShareeEmail1 get shareeEmail1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeEmailSerializer(); } @@ -2734,21 +2756,24 @@ abstract class FilesSharingShareeRemoteGroup1_Value1 abstract class FilesSharingShareeRemoteGroup1_Value implements Built { - // coverage:ignore-end - factory FilesSharingShareeRemoteGroup1_Value([final void Function(FilesSharingShareeRemoteGroup1_ValueBuilder)? b]) = _$FilesSharingShareeRemoteGroup1_Value; + // coverage:ignore-start const FilesSharingShareeRemoteGroup1_Value._(); + // coverage:ignore-end - JsonObject get data; - FilesSharingShareeValue get shareeValue; - FilesSharingShareeRemoteGroup1_Value1 get shareeRemoteGroup1Value1; - static FilesSharingShareeRemoteGroup1_Value fromJson(final Object json) => + // coverage:ignore-start + factory FilesSharingShareeRemoteGroup1_Value.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingShareeValue get shareeValue; + FilesSharingShareeRemoteGroup1_Value1 get shareeRemoteGroup1Value1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeRemoteGroup1_ValueSerializer(); @@ -2816,21 +2841,24 @@ abstract class FilesSharingShareeRemoteGroup1 abstract class FilesSharingShareeRemoteGroup implements Built { - // coverage:ignore-end - factory FilesSharingShareeRemoteGroup([final void Function(FilesSharingShareeRemoteGroupBuilder)? b]) = _$FilesSharingShareeRemoteGroup; + // coverage:ignore-start const FilesSharingShareeRemoteGroup._(); + // coverage:ignore-end - JsonObject get data; - FilesSharingSharee get sharee; - FilesSharingShareeRemoteGroup1 get shareeRemoteGroup1; - static FilesSharingShareeRemoteGroup fromJson(final Object json) => + // coverage:ignore-start + factory FilesSharingShareeRemoteGroup.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingSharee get sharee; + FilesSharingShareeRemoteGroup1 get shareeRemoteGroup1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeRemoteGroupSerializer(); } @@ -2893,21 +2921,24 @@ abstract class FilesSharingShareeRemote1_Value1 abstract class FilesSharingShareeRemote1_Value implements Built { - // coverage:ignore-end - factory FilesSharingShareeRemote1_Value([final void Function(FilesSharingShareeRemote1_ValueBuilder)? b]) = _$FilesSharingShareeRemote1_Value; + // coverage:ignore-start const FilesSharingShareeRemote1_Value._(); + // coverage:ignore-end - JsonObject get data; - FilesSharingShareeValue get shareeValue; - FilesSharingShareeRemote1_Value1 get shareeRemote1Value1; - static FilesSharingShareeRemote1_Value fromJson(final Object json) => + // coverage:ignore-start + factory FilesSharingShareeRemote1_Value.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingShareeValue get shareeValue; + FilesSharingShareeRemote1_Value1 get shareeRemote1Value1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeRemote1_ValueSerializer(); } @@ -2974,20 +3005,24 @@ abstract class FilesSharingShareeRemote1 implements Built { - // coverage:ignore-end - factory FilesSharingShareeRemote([final void Function(FilesSharingShareeRemoteBuilder)? b]) = _$FilesSharingShareeRemote; + // coverage:ignore-start const FilesSharingShareeRemote._(); + // coverage:ignore-end + + // coverage:ignore-start + factory FilesSharingShareeRemote.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - FilesSharingSharee get sharee; - FilesSharingShareeRemote1 get shareeRemote1; - static FilesSharingShareeRemote fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingSharee get sharee; + FilesSharingShareeRemote1 get shareeRemote1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeRemoteSerializer(); } @@ -3075,19 +3110,23 @@ abstract class FilesSharingShareeUser1 implements Built { - // coverage:ignore-end - factory FilesSharingShareeUser([final void Function(FilesSharingShareeUserBuilder)? b]) = _$FilesSharingShareeUser; + // coverage:ignore-start const FilesSharingShareeUser._(); + // coverage:ignore-end + + // coverage:ignore-start + factory FilesSharingShareeUser.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - FilesSharingSharee get sharee; - FilesSharingShareeUser1 get shareeUser1; - static FilesSharingShareeUser fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingSharee get sharee; + FilesSharingShareeUser1 get shareeUser1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeUserSerializer(); } @@ -3232,21 +3271,24 @@ abstract class FilesSharingShareeLookup1_Value1 abstract class FilesSharingShareeLookup1_Value implements Built { - // coverage:ignore-end - factory FilesSharingShareeLookup1_Value([final void Function(FilesSharingShareeLookup1_ValueBuilder)? b]) = _$FilesSharingShareeLookup1_Value; + // coverage:ignore-start const FilesSharingShareeLookup1_Value._(); + // coverage:ignore-end - JsonObject get data; - FilesSharingShareeValue get shareeValue; - FilesSharingShareeLookup1_Value1 get shareeLookup1Value1; - static FilesSharingShareeLookup1_Value fromJson(final Object json) => + // coverage:ignore-start + factory FilesSharingShareeLookup1_Value.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingShareeValue get shareeValue; + FilesSharingShareeLookup1_Value1 get shareeLookup1Value1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeLookup1_ValueSerializer(); } @@ -3311,20 +3353,24 @@ abstract class FilesSharingShareeLookup1 implements Built { - // coverage:ignore-end - factory FilesSharingShareeLookup([final void Function(FilesSharingShareeLookupBuilder)? b]) = _$FilesSharingShareeLookup; + // coverage:ignore-start const FilesSharingShareeLookup._(); + // coverage:ignore-end + + // coverage:ignore-start + factory FilesSharingShareeLookup.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - FilesSharingSharee get sharee; - FilesSharingShareeLookup1 get shareeLookup1; - static FilesSharingShareeLookup fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + FilesSharingSharee get sharee; + FilesSharingShareeLookup1 get shareeLookup1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareeLookupSerializer(); } @@ -3449,22 +3495,25 @@ abstract class FilesSharingShareesapiSearchResponse200ApplicationJson abstract class FilesSharingShareesapiFindRecommendedShareType implements Built { - // coverage:ignore-end - factory FilesSharingShareesapiFindRecommendedShareType([ final void Function(FilesSharingShareesapiFindRecommendedShareTypeBuilder)? b, ]) = _$FilesSharingShareesapiFindRecommendedShareType; + // coverage:ignore-start const FilesSharingShareesapiFindRecommendedShareType._(); + // coverage:ignore-end - JsonObject get data; - int? get $int; - BuiltList? get builtListInt; - static FilesSharingShareesapiFindRecommendedShareType fromJson(final Object json) => + // coverage:ignore-start + factory FilesSharingShareesapiFindRecommendedShareType.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + int? get $int; + BuiltList? get builtListInt; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$FilesSharingShareesapiFindRecommendedShareTypeSerializer(); diff --git a/packages/nextcloud/lib/src/api/provisioning_api.openapi.dart b/packages/nextcloud/lib/src/api/provisioning_api.openapi.dart index e1188c13..d6ac8c5d 100644 --- a/packages/nextcloud/lib/src/api/provisioning_api.openapi.dart +++ b/packages/nextcloud/lib/src/api/provisioning_api.openapi.dart @@ -2859,22 +2859,25 @@ abstract class ProvisioningApiGroupsAddGroupResponse200ApplicationJson abstract class ProvisioningApiGroupDetails_Usercount implements Built { - // coverage:ignore-end - factory ProvisioningApiGroupDetails_Usercount([ final void Function(ProvisioningApiGroupDetails_UsercountBuilder)? b, ]) = _$ProvisioningApiGroupDetails_Usercount; + // coverage:ignore-start const ProvisioningApiGroupDetails_Usercount._(); + // coverage:ignore-end - JsonObject get data; - bool? get $bool; - int? get $int; - static ProvisioningApiGroupDetails_Usercount fromJson(final Object json) => + // coverage:ignore-start + factory ProvisioningApiGroupDetails_Usercount.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + bool? get $bool; + int? get $int; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$ProvisioningApiGroupDetails_UsercountSerializer(); @@ -2916,21 +2919,24 @@ class _$ProvisioningApiGroupDetails_UsercountSerializer abstract class ProvisioningApiGroupDetails_Disabled implements Built { - // coverage:ignore-end - factory ProvisioningApiGroupDetails_Disabled([final void Function(ProvisioningApiGroupDetails_DisabledBuilder)? b]) = _$ProvisioningApiGroupDetails_Disabled; + // coverage:ignore-start const ProvisioningApiGroupDetails_Disabled._(); + // coverage:ignore-end - JsonObject get data; - bool? get $bool; - int? get $int; - static ProvisioningApiGroupDetails_Disabled fromJson(final Object json) => + // coverage:ignore-start + factory ProvisioningApiGroupDetails_Disabled.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + bool? get $bool; + int? get $int; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$ProvisioningApiGroupDetails_DisabledSerializer(); @@ -3181,22 +3187,25 @@ abstract class ProvisioningApiUserDetails_BackendCapabilities abstract class ProvisioningApiUserDetailsQuota_Quota implements Built { - // coverage:ignore-end - factory ProvisioningApiUserDetailsQuota_Quota([ final void Function(ProvisioningApiUserDetailsQuota_QuotaBuilder)? b, ]) = _$ProvisioningApiUserDetailsQuota_Quota; + // coverage:ignore-start const ProvisioningApiUserDetailsQuota_Quota._(); + // coverage:ignore-end - JsonObject get data; - num? get $num; - String? get string; - static ProvisioningApiUserDetailsQuota_Quota fromJson(final Object json) => + // coverage:ignore-start + factory ProvisioningApiUserDetailsQuota_Quota.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + num? get $num; + String? get string; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$ProvisioningApiUserDetailsQuota_QuotaSerializer(); @@ -3359,25 +3368,28 @@ abstract class ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJs implements Built { - // coverage:ignore-end - factory ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJson_Ocs_Data_Users([ final void Function(ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJson_Ocs_Data_UsersBuilder)? b, ]) = _$ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJson_Ocs_Data_Users; + // coverage:ignore-start const ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJson_Ocs_Data_Users._(); + // coverage:ignore-end - JsonObject get data; - ProvisioningApiUserDetails? get userDetails; - ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJson_Ocs_Data_Users1? - get groupsGetGroupUsersDetailsResponse200ApplicationJsonOcsDataUsers1; - static ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJson_Ocs_Data_Users fromJson( - final Object json, + // coverage:ignore-start + factory ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJson_Ocs_Data_Users.fromJson( + final Map json, ) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + ProvisioningApiUserDetails? get userDetails; + ProvisioningApiGroupsGetGroupUsersDetailsResponse200ApplicationJson_Ocs_Data_Users1? + get groupsGetGroupUsersDetailsResponse200ApplicationJsonOcsDataUsers1; @BuiltValueSerializer(custom: true) static Serializer get serializer => @@ -4153,23 +4165,28 @@ abstract class ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs implements Built { - // coverage:ignore-end - factory ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_Users([ final void Function(ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_UsersBuilder)? b, ]) = _$ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_Users; + // coverage:ignore-start const ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_Users._(); + // coverage:ignore-end - JsonObject get data; - ProvisioningApiUserDetails? get userDetails; - ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_Users1? - get usersGetUsersDetailsResponse200ApplicationJsonOcsDataUsers1; - static ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_Users fromJson(final Object json) => + // coverage:ignore-start + factory ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_Users.fromJson( + final Map json, + ) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + ProvisioningApiUserDetails? get userDetails; + ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_Users1? + get usersGetUsersDetailsResponse200ApplicationJsonOcsDataUsers1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$ProvisioningApiUsersGetUsersDetailsResponse200ApplicationJson_Ocs_Data_UsersSerializer(); diff --git a/packages/nextcloud/lib/src/api/user_status.openapi.dart b/packages/nextcloud/lib/src/api/user_status.openapi.dart index 64db19dc..99db26b3 100644 --- a/packages/nextcloud/lib/src/api/user_status.openapi.dart +++ b/packages/nextcloud/lib/src/api/user_status.openapi.dart @@ -592,19 +592,23 @@ abstract class UserStatusPrivate1 implements Built { - // coverage:ignore-end - factory UserStatusPrivate([final void Function(UserStatusPrivateBuilder)? b]) = _$UserStatusPrivate; + // coverage:ignore-start const UserStatusPrivate._(); + // coverage:ignore-end + + // coverage:ignore-start + factory UserStatusPrivate.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - UserStatusPublic get public; - UserStatusPrivate1 get private1; - static UserStatusPrivate fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + UserStatusPublic get public; + UserStatusPrivate1 get private1; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$UserStatusPrivateSerializer(); } @@ -722,19 +726,23 @@ class UserStatusClearAtTimeType extends EnumClass { } abstract class UserStatusClearAt_Time implements Built { - // coverage:ignore-end - factory UserStatusClearAt_Time([final void Function(UserStatusClearAt_TimeBuilder)? b]) = _$UserStatusClearAt_Time; + // coverage:ignore-start const UserStatusClearAt_Time._(); + // coverage:ignore-end + + // coverage:ignore-start + factory UserStatusClearAt_Time.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end - JsonObject get data; - int? get $int; - UserStatusClearAtTimeType? get clearAtTimeType; - static UserStatusClearAt_Time fromJson(final Object json) => _jsonSerializers.deserializeWith(serializer, json)!; // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + int? get $int; + UserStatusClearAtTimeType? get clearAtTimeType; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$UserStatusClearAt_TimeSerializer(); } @@ -1239,22 +1247,27 @@ abstract class UserStatusUserStatusRevertStatusResponse200ApplicationJson_Ocs_Da implements Built { - // coverage:ignore-end - factory UserStatusUserStatusRevertStatusResponse200ApplicationJson_Ocs_Data([ final void Function(UserStatusUserStatusRevertStatusResponse200ApplicationJson_Ocs_DataBuilder)? b, ]) = _$UserStatusUserStatusRevertStatusResponse200ApplicationJson_Ocs_Data; + // coverage:ignore-start const UserStatusUserStatusRevertStatusResponse200ApplicationJson_Ocs_Data._(); + // coverage:ignore-end - JsonObject get data; - UserStatusPrivate? get private; - JsonObject? get jsonObject; - static UserStatusUserStatusRevertStatusResponse200ApplicationJson_Ocs_Data fromJson(final Object json) => + // coverage:ignore-start + factory UserStatusUserStatusRevertStatusResponse200ApplicationJson_Ocs_Data.fromJson( + final Map json, + ) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + // coverage:ignore-start Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; // coverage:ignore-end + JsonObject get data; + UserStatusPrivate? get private; + JsonObject? get jsonObject; @BuiltValueSerializer(custom: true) static Serializer get serializer => _$UserStatusUserStatusRevertStatusResponse200ApplicationJson_Ocs_DataSerializer();