generate method
Library
generate()
Implementation
Library generate() {
lb.body.add(Directive.part(partFileName));
var myRequireFreezed = false;
void requireFreezed() {
if (myRequireFreezed) {
return;
}
if (freezedPartFileName.isEmpty) {
throw StateError(
'freeze is required, but no freezedPartFileName was given.');
}
lb.body.insert(1, Directive.part(freezedPartFileName));
myRequireFreezed = true;
}
// create class for each schema..
for (final schemaEntry in api.components!.schemas!.entries) {
_schemaReference(schemaEntry.key, schemaEntry.value!);
}
if (api.components!.securitySchemes != null && !ignoreSecuritySchemes) {
for (final securityScheme in api.components!.securitySchemes!.entries) {
_securitySchemeReference(securityScheme.key, securityScheme.value!);
}
}
// Create path configs
final clientInterface = ClassBuilder()
..name = '${baseName}Client'
..implements.add(_openApiClient)
..abstract = true;
final fields = [
MapEntry('baseUri', refer('Uri')),
MapEntry('requestSender', _openApiRequestSender),
];
final providerClosure = Method((mb) => mb
..lambda = true
..requiredParameters.add(Parameter((pb) => pb..name = 'ref'))
..body = refer('StateError')([literalString('must be overwritten')])
.thrown
.code).closure;
final clientProviderName = '${baseName}ClientProvider'.camelCase;
if (generateProvider) {
lb.body.add(declareFinal(clientProviderName)
.assign(_provider.addGenerics(
refer(clientInterface.name!.pascalCase))([providerClosure]))
.statement);
}
final urlResolveClass = ClassBuilder()
..name = '${baseName}UrlResolve'
..mixins.add(_openApiUrlEncodeMixin);
final clientClass = ClassBuilder()
..name = '_${baseName}ClientImpl'
..extend = _openApiClientBase
..implements.add(refer(clientInterface.name!))
..constructors.add(Constructor(
(cb) => cb
..name = '_'
..requiredParameters.addAll(fields.map((f) => Parameter((pb) => pb
..name = f.key
..toThis = true))),
))
..fields.addAll(fields.map((f) => Field((fb) => fb
..name = f.key
..type = f.value
..annotations.add(_override)
..modifier = FieldModifier.final$)));
clientInterface.constructors.add(Constructor((cb) => cb
..factory = true
..requiredParameters.addAll(fields.map((f) => Parameter((pb) => pb
..name = f.key
..type = f.value)))
..body = refer(clientClass.name!)
.newInstanceNamed('_', fields.map((f) => refer(f.key)))
.code));
final c = Class((cb) {
cb.name = baseName;
cb.implements.add(_openApiEndpoint);
// cb.types.add(TypeReference((b) => b
// ..symbol = 'T'
// ..bound = _openApiRequest));
cb.abstract = true;
for (final path in api.paths!.entries) {
for (final operation in path.value!.operations.entries) {
final pathName = path.key
.replaceAll(
// language=RegExp
RegExp(r'[{}]'),
'')
.camelCase;
final operationName = operation.value!.id == null
? '$pathName'
'${operation.key.pascalCase}'
: operation.value!.id!.camelCase;
final responseClass = ClassBuilder();
responseClass
..name = '${operationName.pascalCase}Response'
..sealed = true
..extend = _openApiResponse
..constructors.add(Constructor());
// ..constructors.add(Constructor((cb) => cb
// ..name = '_'
// ..requiredParameters.add(Parameter((pb) => pb
// ..name = 'status'
// ..toThis = true))))
final mapMethod = MethodBuilder()
..name = 'map'
..types.add(const Reference('R'))
..returns = const Reference('R');
final mapCode = <Code>[];
Reference? successResponseBodyType;
Reference? successResponseCodeType;
MapEntry<String, APIResponse?>? successApiResponse;
final clientResponseParse = <String, Expression>{};
for (final response in operation.value!.responses!.entries) {
final statusAsParameter = response.key == 'default';
final codeName = response.key.pascalCase;
final responseCodeClass = ClassBuilder()
..extend = refer(responseClass.name!)
..name = '${responseClass.name}$codeName'
..fields.add(Field((fb) => fb
..name = 'status'
..annotations.add(_override)
..modifier = FieldModifier.final$
..type = refer('int')));
mapMethod.optionalParameters.add(Parameter((pb) => pb
..name = 'on$codeName'
..asRequired(this, true)
..named = true
..type = _responseMapType
.addGenerics(refer(responseCodeClass.name!))
.addGenerics(const Reference('R'))));
if (mapCode.isNotEmpty) {
mapCode.add(const Code(' else '));
}
mapCode.add(const Code('if (this is '));
mapCode.add(refer(responseCodeClass.name!).code);
mapCode.add(const Code(') {'));
mapCode.add(refer('on$codeName')(
[refer('this').asA(refer(responseCodeClass.name!))])
.returned
.statement);
mapCode.add(const Code('}'));
final clientResponseParseParams = <Expression>[];
final constructor = Constructor((cb) {
cb
..name = 'response$codeName'
..docs.addDartDoc(response.value!.description);
refer(cb.name!);
if (statusAsParameter) {
cb
..requiredParameters.add(
Parameter((pb) => pb
..name = 'status'
..type = refer('int')),
)
..initializers
.add(refer('status').assign(refer('status')).code);
clientResponseParseParams
.add(refer('response').property('status'));
} else {
cb.initializers.add(refer('status')
.assign(literalNum(int.parse(response.key)))
.code);
}
final content =
(response.value!.content ?? {})[mediaTypeJson.contentType];
// OpenApiContentType responseContentType;
Code? responseContentTypeAssignment = _literalNullCode;
Reference? bodyType;
if (content != null) {
const responseContentType = OpenApiContentType.json;
responseContentTypeAssignment = _openApiContentType
.newInstanceNamed('parse',
[literalString(responseContentType.toString())]).code;
final responseSchema = content.schema!;
if (responseSchema.type == APIType.array) {
final bodyItemType = _schemaReference(
'${responseClass.name}Body$codeName',
responseSchema.items!);
bodyType = _referType('List', generics: [bodyItemType]);
cb.requiredParameters.add(Parameter((pb) => pb
..name = 'body'
..type = bodyType
..toThis = true));
// TODO add server side support for arrays.
// cb.initializers.add(refer('bodyJson')
// .assign(refer('body').property('toJson')([]))
// .code);
cb.initializers
.add(refer('bodyJson').assign(literalMap({})).code);
clientResponseParseParams.add(refer('response')
.property('responseBodyJsonDynamic')([])
.awaited
.asA(_referType('List', generics: [refer('dynamic')]))
.property('map')([
Method((mb) {
mb.requiredParameters
.add(Parameter((pb) => pb..name = 'item'));
mb.lambda = true;
mb.body = bodyItemType.newInstanceNamed('fromJson', [
refer('item').asA(_referType('Map',
generics: [refer('String'), refer('dynamic')]))
]).code;
}).closure
])
.property('toList')([]));
} else {
bodyType = _schemaReference(
'${responseClass.name}Body$codeName', content.schema!);
cb.requiredParameters.add(Parameter((pb) => pb
..name = 'body'
..type = bodyType
..toThis = true));
cb.initializers.add(refer('bodyJson')
.assign(refer('body').property('toJson')([]))
.code);
clientResponseParseParams.add(bodyType.newInstanceNamed(
'fromJson', [
refer('response').property('responseBodyJson')([]).awaited
]));
}
responseCodeClass.fields.add(Field((fb) => fb
..name = 'body'
..type = bodyType
..modifier = FieldModifier.final$));
responseCodeClass.fields.add(Field((fb) => fb
..name = 'bodyJson'
..annotations.add(_override)
..type = _referType('Map',
generics: [_typeString, refer('dynamic')])
..modifier = FieldModifier.final$));
responseCodeClass.implements.add(_openApiResponseBodyJson);
} else {
if (response.value!.content?.length == 1) {
final responseContent =
response.value!.content!.entries.first;
final responseContentType =
OpenApiContentType.parse(responseContent.key);
responseContentTypeAssignment = _openApiContentType
.newInstanceNamed('parse',
[literalString(responseContentType.toString())]).code;
bodyType = _toDartType('${responseCodeClass}Content',
responseContent.value!.schema!);
checkState(
responseContent.value!.schema!.type == APIType.string,
message: 'schema type not supported for content type '
'${responseContent.key}: '
'${responseContent.value!.schema!.type}');
responseCodeClass.fields.add(Field((fb) => fb
..name = 'body'
..type = bodyType
..annotations.add(_override)
..modifier = FieldModifier.final$));
if (responseContent.key.contains('*')) {
responseContentTypeAssignment = null;
cb.requiredParameters.add(Parameter((pb) => pb
..type = _openApiContentType
..name = 'contentType'
..toThis = true));
clientResponseParseParams.add(
refer('response').property('responseContentType')([]));
}
cb.requiredParameters.add(Parameter((pb) => pb
..name = 'body'
..type = bodyType
..toThis = true));
if (_typeString == bodyType) {
responseCodeClass.implements
.add(_openApiResponseBodyString);
clientResponseParseParams.add(refer('response')
.property('responseBodyString')([])
.awaited);
} else if (_uint8List == bodyType) {
responseCodeClass.implements
.add(_openApiResponseBodyBinary);
clientResponseParseParams.add(refer('response')
.property('responseBodyBytes')([])
.awaited);
} else {
throw StateError(
'Unsupported bodyType $bodyType for responses.');
}
}
}
if (response.key.startsWith('2') ||
(response.key == 'default' &&
successResponseBodyType == null)) {
successResponseCodeType = refer(responseCodeClass.name!);
successResponseBodyType = bodyType;
successApiResponse = response;
}
responseCodeClass.fields.add(Field(
(fb) => fb
..name = 'contentType'
..type = responseContentTypeAssignment == _literalNullCode
? _openApiContentTypeNullable
: _openApiContentType
..annotations.add(_override)
..modifier = FieldModifier.final$
..assignment = responseContentTypeAssignment,
));
});
responseCodeClass.constructors.add((constructor.toBuilder()
..requiredParameters.map((pb) => (pb.toBuilder()
..type = pb.toThis ? null : pb.type)
.build()))
.build());
responseCodeClass.methods.add(Method((mb) => mb
..name = 'propertiesToString'
..annotations.add(_override)
..returns = _referType('Map',
generics: [_typeString, refer('Object').asNullable(true)])
..lambda = true
..body = literalMap(
Map.fromEntries(responseCodeClass.fields.build().map(
(f) => MapEntry(literalString(f.name), refer(f.name)))),
).code));
responseClass.constructors.add((constructor.toBuilder()
..factory = true
..initializers.clear()
..requiredParameters
.map((pb) => (pb.toBuilder()..toThis = false).build())
..body = refer(responseCodeClass.name!)
.newInstanceNamed(
constructor.name!,
constructor.requiredParameters
.map((e) => refer(e.name))
.toList())
.code)
.build());
clientResponseParse[response.key] = Method((mb) => mb
..modifier = MethodModifier.async
..requiredParameters.add(Parameter((pb) => pb
..name = 'response'
..type = _openApiClientResponse))
..body = refer(responseCodeClass.name!)
.newInstanceNamed(
constructor.name!, clientResponseParseParams)
.code).closure;
lb.body.add(responseCodeClass.build());
}
if (mapCode.isNotEmpty) {
mapMethod.optionalParameters.add(Parameter((pb) => pb
..name = 'onElse'
// ..asRequired(this, true)
..named = true
..type = _responseMapType
.addGenerics(refer(responseClass.name!))
.addGenerics(const Reference('R'))
.asNullable(true)));
mapCode.add(const Code('else '));
mapCode.add(ifStatement(
refer('onElse').notEqualTo(literalNull),
refer('onElse')([refer('this')]).returned.statement,
elseCode: refer('StateError')
.newInstance(
[literalString(r'Invalid instance of type $this')])
.thrown
.statement,
));
// mapCode.add(const Code(r'''
// if (onElse != null) {
// return onElse();
// } else {
// throw StateError('Invalid instance type $this');
// }'''));
// mapCode.add(const Code('}'));
}
mapMethod.body = Block.of(mapCode);
responseClass.methods.add(mapMethod.build());
if (successApiResponse != null) {
ArgumentError.checkNotNull(
successResponseCodeType, 'successResponseCodeType');
responseClass.implements.add(_hasSuccessResponse
.addGenerics(successResponseBodyType ?? _void));
responseClass.methods.add(
Method((mb) => mb
..name = 'requireSuccess'
..addDartDoc(successApiResponse!.value!.description,
prefix: 'status ${successApiResponse!.key}: ')
..annotations.add(_override)
..returns = successResponseBodyType ?? _void
..body = Block.of([
const Code('if (this is '),
successResponseCodeType!.code,
const Code(') {'),
successResponseBodyType == null
// success, but no body.
? const Code('return;')
: refer('this')
.asA(successResponseCodeType!)
.property('body')
.returned
.statement,
const Code('} else {'),
const Code(
r'''throw StateError('Expected success response, but got $this');'''),
const Code('}'),
])),
);
}
lb.body.add(responseClass.build());
final clientDataClass = ClassBuilder()
..name = operationName.pascalCase
..mixins.add(refer('_\$${operationName.pascalCase}'))
..annotations.add(_freezed);
final clientDataConstructor = ConstructorBuilder()
..factory = true
..redirect = refer('_${operationName.pascalCase}');
final clientMethod = MethodBuilder()
..name = operationName
..addDartDoc(operation.value!.summary)
..addDartDoc(operation.value!.description)
..docs.add('/// ${operation.key}: ${path.key}')
..docs.add('///')
..returns =
_referType('Future', generics: [refer(responseClass.name!)])
..modifier = MethodModifier.async;
final clientCode = <Code>[
declareFinal('request')
.assign(_openApiClientRequest.newInstance([
literalString(operation.key),
literalString(path.key),
_operationSecurityRequirements(
operation.value!.security ?? api.security),
]))
.statement,
];
final clientCodeRequest = refer('request');
cb.methods.add(Method((mb) {
mb
..name = operationName
..addDartDoc(operation.value!.summary)
..addDartDoc(operation.value!.description)
..docs.add('/// ${operation.key}: ${path.key}')
..returns =
_referType('Future', generics: [refer(responseClass.name!)]);
final routerParams = <Expression>[];
final routerParamsNamed = <String, Expression>{};
if (apiMethodsWithRequest) {
mb.requiredParameters.add(Parameter((pb) => pb
..name = 'request'
..type = _openApiRequest));
routerParams.add(refer('request'));
}
// ignore: avoid_function_literals_in_foreach_calls
final allParameters = [
...?path.value!.parameters,
...?operation.value!.parameters
];
for (final param in allParameters) {
final paramType = _toDartType(operationName, param!.schema!);
final paramNameCamelCase = param.name!.camelCase;
if (param.description != null) {
clientMethod.docs
.add('/// * [$paramNameCamelCase]: ${param.description}');
}
final p = Parameter((pb) => pb
..name = paramNameCamelCase
..type = paramType.asNullable(!param.isRequired)
..asRequired(this, param.isRequired)
..named = true);
mb.optionalParameters.add(p);
clientMethod.optionalParameters.add(p);
clientDataConstructor.optionalParameters.add(p.rebuild((pb) => pb
// ..toThis = true
));
// clientDataClass.fields.add(Field((fb) => fb
// ..name = paramNameCamelCase
// ..modifier = FieldModifier.final$
// ..type = paramType.asNullable(!param.isRequired)));
Expression decodeParameterFrom(
APIParameter param, Expression expression) {
final schemaType = ArgumentError.checkNotNull(
param.schema?.type, 'param.schema.type');
switch (schemaType) {
case APIType.string:
final asString = refer('paramToString')([expression]);
if (param.schema!.format == 'uuid') {
assert(paramType == _apiUuid);
return _apiUuid.newInstanceNamed('parse', [asString]);
} else if (param.schema?.enumerated?.isNotEmpty == true) {
final paramEnumType = paramType;
return refer('${paramEnumType.symbol!}Ext')
.property('fromName')([asString]);
} else if (paramType == _typeDateTime) {
return _typeDateTime.property('parse')([asString]);
} else if (paramType != _typeString) {
throw StateError(
'Unsupported paramType for string $paramType');
}
return asString;
case APIType.number:
return refer('paramToNum')([expression]);
case APIType.integer:
return refer('paramToInt')([expression]);
case APIType.boolean:
return refer('paramToBool')([expression]);
case APIType.array:
checkState(param.schema!.items!.type == APIType.string);
if (param.schema!.items!.enumerated != null &&
param.schema!.items!.enumerated!.isNotEmpty) {
final paramEnumType =
(paramType as TypeReference).types.first;
return expression
.property('map')([
Method(
(mb) => mb
..lambda = true
..requiredParameters
.add(Parameter((pb) => pb..name = 'e'))
..body = refer('${paramEnumType.symbol!}Ext')
.property('fromName')([refer('e')])
.code,
).closure
])
.property('toList')([]);
}
return expression;
case APIType.object:
return expression;
default:
throw StateError('Invalid schema type $schemaType');
}
}
Expression decodeParameter(Expression? expression) {
return refer(param.isRequired ? 'paramRequired' : 'paramOpt')(
[],
{
'name': literalString(param.name!),
'value': expression!,
'decode': Method((mb) => mb
..lambda = true
..requiredParameters
.add(Parameter((pb) => pb..name = 'value'))
..body =
decodeParameterFrom(param, refer('value')).code)
.closure,
});
}
Expression encodeParameter(Expression expression) {
final schemaType = ArgumentError.checkNotNull(
param.schema?.type, 'param.schema.type');
switch (schemaType) {
case APIType.string:
if (param.schema!.format == 'uuid') {
assert(paramType == _apiUuid);
if (param.isRequired) {
expression = expression.property('encodeToString')([]);
} else {
expression =
expression.nullSafeProperty('encodeToString')([]);
}
} else if (param.schema?.enumerated?.isNotEmpty == true) {
if (param.isRequired) {
expression = expression.property('name');
} else {
expression = expression.nullSafeProperty('name');
}
} else if (paramType == _typeDateTime) {
if (param.isRequired) {
expression = expression.property('toIso8601String')([]);
} else {
expression =
expression.nullSafeProperty('toIso8601String')([]);
}
} else if (paramType != _typeString) {
// TODO not sure if this makes sense, maybe we should just
// use `toString`?
throw StateError(
'encodeParameter: Unsupported paramType for string $paramType');
}
return refer('encodeString')([expression]);
case APIType.number:
return refer('encodeNum')([expression]);
case APIType.integer:
return refer('encodeInt')([expression]);
case APIType.boolean:
return refer('encodeBool')([expression]);
case APIType.array:
checkState(param.schema!.items!.type == APIType.string);
if (param.schema!.items!.enumerated != null &&
param.schema!.items!.enumerated!.isNotEmpty) {
return expression.property('map')([
Method(
(mb) => mb
..lambda = true
..requiredParameters
.add(Parameter((pb) => pb..name = 'e'))
..body = refer('e').property('name').code,
).closure
]);
}
return expression;
case APIType.object:
return expression;
}
}
final paramLocation = ArgumentError.checkNotNull(param.location);
final paramName = ArgumentError.checkNotNull(param.name);
routerParamsNamed[paramNameCamelCase] =
decodeParameter(_readFromRequest(paramLocation, paramName));
clientCode.add(_writeToRequest(
clientCodeRequest,
paramLocation,
paramName,
encodeParameter(refer(paramNameCamelCase)),
).statement);
}
final urlResolverMethod = clientMethod.build().toBuilder()
..returns = _openApiClientRequest
..modifier = null
..body =
Block.of(clientCode + [clientCodeRequest.returned.statement]);
urlResolveClass.methods.add(urlResolverMethod.build());
final body = operation.value!.requestBody;
if (body != null && body.content!.isNotEmpty) {
final entry = body.content!.entries.first;
if (body.content!.length > 1) {
_logger.warning('Right now we only support one request body, '
'but found: ${body.content!.keys}, only using $entry');
}
Map.fromEntries([entry]).forEach((key, reqBody) {
final contentType = OpenApiContentType.parse(key);
// final ct = OpenApiContentType.allKnown
// .firstWhere((element) => element.matches(contentType));
_createRequestBody(
contentType,
reqBody!,
operationName,
mb,
routerParams,
clientMethod,
clientCode,
);
});
}
_routerConfig(
path.key,
operation.key,
refer('impl').property('invoke')([
refer('request'),
Method((m) => m
..requiredParameters.add(Parameter((pb) => pb
..type = refer(baseName)
..name = 'impl'))
..lambda = true
..body = refer('impl')
.property(operationName)(routerParams, routerParamsNamed)
// .returned
.code
..modifier = MethodModifier.async).closure
]),
operation.value!.security ?? api.security,
); //.property(operationName)(parameters));
}));
clientCode.add(refer('sendRequest')(
[refer('request'), literalMap(clientResponseParse)])
.awaited
.returned
.statement);
clientMethod.body = Block.of(clientCode);
clientClass.methods
.add((clientMethod..annotations.add(_override)).build());
clientInterface.methods.add((clientMethod.build().toBuilder()
..annotations.clear()
..body = null)
.build());
if (generateProvider && operation.key.toLowerCase() == 'get') {
final params = clientDataConstructor.build().optionalParameters;
clientDataClass.constructors.add(clientDataConstructor.build());
// clientDataClass.build().fields.
if (params.length > 1) {
lb.body.add(clientDataClass.build());
requireFreezed();
}
// final baseBaseIdGet = _i1.StreamProvider.family<BaseBaseIdGetResponse, BaseBaseIdGet>((ref, arg) {
// final client = ref.watch(mywarmApiClientProvider);
// return Stream.fromFuture(client.baseBaseIdGet(baseId: arg.baseId));
// });
final m = Method((mb) {
mb.requiredParameters.add(Parameter((pb) => pb..name = 'ref'));
if (params.isNotEmpty) {
mb.requiredParameters.add(Parameter((pb) => pb..name = 'arg'));
}
mb.body = Block.of([
declareFinal('client')
.assign(refer('ref')
.property('watch')([refer(clientProviderName)]))
.statement,
refer('Stream')
.property('fromFuture')(
[
refer('client').property(operationName)(
[],
params.isEmpty
? {}
: params.length == 1
? {
params.first.name: refer('arg'),
}
: Map.fromEntries(params.map((f) =>
MapEntry(f.name,
refer('arg').property(f.name))))),
],
)
.returned
.statement,
]);
});
final providerName =
'${providerNamePrefix.isEmpty ? operationName.camelCase : '$providerNamePrefix${operationName.pascalCase}'}Provider';
final Expression createProvider;
if (params.isEmpty) {
createProvider = _streamProvider.property('autoDispose')(
[m.closure], {}, [refer(responseClass.name!)]);
} else {
createProvider = _streamProvider
.property('autoDispose')
.property('family')
.call([
m.closure
], {}, [
refer(responseClass.name!),
params.length > 1
? refer(clientDataClass.name!)
: params.first.type!
]);
}
lb.body.add(
declareFinal(providerName).assign(createProvider).statement);
}
}
}
});
lb.body.add(c);
lb.body.add(clientInterface.build());
lb.body.add(clientClass.build());
lb.body.add(urlResolveClass.build());
lb.body.add(Class((cb) {
cb.name = '${baseName}Router';
cb.constructors.add(Constructor((cb) => cb
..requiredParameters.add(Parameter((pb) => pb
..name = 'impl'
..toThis = true))));
cb.extend = refer(
'OpenApiServerRouterBase', 'package:openapi_base/openapi_base.dart');
cb.fields.add(Field((fb) => fb
..name = 'impl'
..type = _endpointProvider.addGenerics(refer(c.name))
..modifier = FieldModifier.final$));
cb.methods.add(Method((mb) => mb
..name = 'configure'
..annotations.add(_override)
..returns = refer('void')
..body = Block.of(routerConfig.map((e) => e!.statement))));
}));
lb.body.add(securitySchemesClass.build());
// api.paths.map((key, value) => MapEntry(key, refer('ApiPathConfig').newInstance([value.])))
return lb.build();
}