<lambda>null1 @file:OptIn(ExperimentalSerializationApi::class)
2
3 package kotlinx.serialization.protobuf.schema
4
5 import kotlinx.serialization.*
6 import kotlinx.serialization.builtins.*
7 import kotlinx.serialization.descriptors.*
8 import kotlinx.serialization.protobuf.*
9 import kotlinx.serialization.protobuf.internal.*
10
11 /**
12 * Experimental generator of ProtoBuf schema that is compatible with [serializable][Serializable] Kotlin classes
13 * and data encoded and decoded by [ProtoBuf] format.
14 *
15 * The schema is generated based on provided [SerialDescriptor] and is compatible with proto2 schema definition.
16 * An arbitrary Kotlin class represent much wider object domain than the ProtoBuf specification, thus schema generator
17 * has the following list of restrictions:
18 *
19 * * Serial name of the class and all its fields should be a valid Proto [identifier](https://developers.google.com/protocol-buffers/docs/reference/proto2-spec)
20 * * Nullable values are allowed only for Kotlin [nullable][SerialDescriptor.isNullable] types, but not [optional][SerialDescriptor.isElementOptional]
21 * in order to properly distinguish "default" and "absent" values.
22 * * The name of the type without the package directive uniquely identifies the proto message type and two or more fields with the same serial name
23 * are considered to have the same type. Schema generator allows to specify a separate package directive for the pack of classes in order
24 * to mitigate this limitation.
25 * * Nested collections, e.g. `List<List<Int>>` are represented using the artificial wrapper message in order to distinguish
26 * repeated fields boundaries.
27 * * Default Kotlin values are not representable in proto schema. A special commentary is generated for properties with default values.
28 * * Empty nullable collections are not supported by the generated schema and will be prohibited in [ProtoBuf] as well
29 * due to their ambiguous nature.
30 *
31 * Temporary restrictions:
32 * * [Contextual] data is represented as as `bytes` type
33 * * [Polymorphic] data is represented as a artificial `KotlinxSerializationPolymorphic` message.
34 *
35 * Other types are mapped according to their specification: primitives as primitives, lists as 'repeated' fields and
36 * maps as 'repeated' map entries.
37 *
38 * The name of messages and enums is extracted from [SerialDescriptor.serialName] in [SerialDescriptor] without the package directive,
39 * as substring after the last dot character, the `'?'` character is also removed if it is present at the end of the string.
40 */
41 @ExperimentalSerializationApi
42 public object ProtoBufSchemaGenerator {
43
44 /**
45 * Generate text of protocol buffers schema version 2 for the given [rootDescriptor].
46 * The resulting schema will contain all types referred by [rootDescriptor].
47 *
48 * [packageName] define common protobuf package for all messages and enum in the schema, it may contain `'a'`..`'z'`
49 * letters in upper and lower case, decimal digits, `'.'` or `'_'` chars, but must be started only by a letter and
50 * not finished by a dot.
51 *
52 * [options] define values for protobuf options. Option value (map value) is an any string, option name (map key)
53 * should be the same format as [packageName].
54 *
55 * The method throws [IllegalArgumentException] if any of the restrictions imposed by [ProtoBufSchemaGenerator] is violated.
56 */
57 @ExperimentalSerializationApi
58 public fun generateSchemaText(
59 rootDescriptor: SerialDescriptor,
60 packageName: String? = null,
61 options: Map<String, String> = emptyMap()
62 ): String = generateSchemaText(listOf(rootDescriptor), packageName, options)
63
64 /**
65 * Generate text of protocol buffers schema version 2 for the given serializable [descriptors].
66 * [packageName] define common protobuf package for all messages and enum in the schema, it may contain `'a'`..`'z'`
67 * letters in upper and lower case, decimal digits, `'.'` or `'_'` chars, but started only from a letter and
68 * not finished by dot.
69 *
70 * [options] define values for protobuf options. Option value (map value) is an any string, option name (map key)
71 * should be the same format as [packageName].
72 *
73 * The method throws [IllegalArgumentException] if any of the restrictions imposed by [ProtoBufSchemaGenerator] is violated.
74 */
75 @ExperimentalSerializationApi
76 public fun generateSchemaText(
77 descriptors: List<SerialDescriptor>,
78 packageName: String? = null,
79 options: Map<String, String> = emptyMap()
80 ): String {
81 packageName?.let { p -> p.checkIsValidFullIdentifier { "Incorrect protobuf package name '$it'" } }
82 checkDoubles(descriptors)
83 val builder = StringBuilder()
84 builder.generateProto2SchemaText(descriptors, packageName, options)
85 return builder.toString()
86 }
87
88 private fun checkDoubles(descriptors: List<SerialDescriptor>) {
89 val rootTypesNames = mutableSetOf<String>()
90 val duplicates = mutableListOf<String>()
91
92 descriptors.map { it.messageOrEnumName }.forEach {
93 if (!rootTypesNames.add(it)) {
94 duplicates += it
95 }
96 }
97 if (duplicates.isNotEmpty()) {
98 throw IllegalArgumentException("Serial names of the following types are duplicated: $duplicates")
99 }
100 }
101
102 private fun StringBuilder.generateProto2SchemaText(
103 descriptors: List<SerialDescriptor>,
104 packageName: String?,
105 options: Map<String, String>
106 ) {
107 appendLine("""syntax = "proto2";""").appendLine()
108
109 packageName?.let { append("package ").append(it).appendLine(';') }
110
111 for ((optionName, optionValue) in options) {
112 val safeOptionName = removeLineBreaks(optionName)
113 val safeOptionValue = removeLineBreaks(optionValue)
114 safeOptionName.checkIsValidFullIdentifier { "Invalid option name '$it'" }
115 append("option ").append(safeOptionName).append(" = \"").append(safeOptionValue).appendLine("\";")
116 }
117
118 val generatedTypes = mutableSetOf<String>()
119 val queue = ArrayDeque<TypeDefinition>()
120 descriptors.map { TypeDefinition(it) }.forEach { queue.add(it) }
121
122 while (queue.isNotEmpty()) {
123 val type = queue.removeFirst()
124 val descriptor = type.descriptor
125 val name = descriptor.messageOrEnumName
126 if (!generatedTypes.add(name)) {
127 continue
128 }
129
130 appendLine()
131 when {
132 descriptor.isProtobufMessage -> queue.addAll(generateMessage(type))
133 descriptor.isProtobufEnum -> generateEnum(type)
134 else -> throw IllegalStateException(
135 "Unrecognized custom type with serial name "
136 + "'${descriptor.serialName}' and kind '${descriptor.kind}'"
137 )
138 }
139 }
140 }
141
142 private fun StringBuilder.generateMessage(messageType: TypeDefinition): List<TypeDefinition> {
143 val messageDescriptor = messageType.descriptor
144 val messageName: String
145 if (messageType.isSynthetic) {
146 append("// This message was generated to support ").append(messageType.ability)
147 .appendLine(" and does not present in Kotlin.")
148
149 messageName = messageDescriptor.serialName
150 if (messageType.containingMessageName != null) {
151 append("// Containing message '").append(messageType.containingMessageName).append("', field '")
152 .append(messageType.fieldName).appendLine('\'')
153 }
154 } else {
155 messageName = messageDescriptor.messageOrEnumName
156 messageName.checkIsValidIdentifier {
157 "Invalid name for the message in protobuf schema '$messageName'. " +
158 "Serial name of the class '${messageDescriptor.serialName}'"
159 }
160 val safeSerialName = removeLineBreaks(messageDescriptor.serialName)
161 if (safeSerialName != messageName) {
162 append("// serial name '").append(safeSerialName).appendLine('\'')
163 }
164 }
165
166 append("message ").append(messageName).appendLine(" {")
167
168 val usedNumbers: MutableSet<Int> = mutableSetOf()
169 val nestedTypes = mutableListOf<TypeDefinition>()
170 generateMessageField(messageName, messageType, nestedTypes, usedNumbers)
171 appendLine('}')
172
173 return nestedTypes
174 }
175
176 private fun StringBuilder.generateMessageField(
177 messageName: String,
178 parentType: TypeDefinition,
179 nestedTypes: MutableList<TypeDefinition>,
180 usedNumbers: MutableSet<Int>,
181 counts: Int = parentType.descriptor.elementsCount,
182 getAnnotations: (Int) -> List<Annotation> = { parentType.descriptor.getElementAnnotations(it) },
183 getChildType: (Int) -> TypeDefinition = { parentType.descriptor.getElementDescriptor(it).let(::TypeDefinition) },
184 getChildNumber: (Int) -> Int = { parentType.descriptor.getElementAnnotations(it).filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: (it + 1) },
185 getChildName: (Int) -> String = { parentType.descriptor.getElementName(it) },
186 inOneOfStruct: Boolean = false,
187 ) {
188 val messageDescriptor = parentType.descriptor
189 for (index in 0 until counts) {
190 val fieldName = getChildName(index)
191 fieldName.checkIsValidIdentifier {
192 "Invalid name of the field '$fieldName' in ${if (inOneOfStruct) "oneof" else ""} message '$messageName' for class with serial " +
193 "name '${messageDescriptor.serialName}'"
194 }
195
196 val fieldType = getChildType(index)
197 val fieldDescriptor = fieldType.descriptor
198
199 val number = getChildNumber(index)
200 if (messageDescriptor.isChildOneOfMessage(index)) {
201 require(!inOneOfStruct) {
202 "Cannot have nested oneof in oneof struct: ${messageName}.$fieldName"
203 }
204 val subDescriptor = fieldDescriptor.getElementDescriptor(1).elementDescriptors.toList()
205 append(" ").append("oneof").append(' ').append(fieldName).appendLine(" {")
206 subDescriptor.forEach { desc ->
207 require(desc.elementsCount == 1) {
208 "Implementation of oneOf type ${desc.serialName} should contain only 1 element, but get ${desc.elementsCount}"
209 }
210 generateMessageField(
211 messageName = messageName,
212 parentType = TypeDefinition(desc),
213 nestedTypes = nestedTypes,
214 usedNumbers = usedNumbers,
215 counts = desc.elementsCount,
216 getAnnotations = { desc.annotations },
217 getChildType = { desc.elementDescriptors.single().let(::TypeDefinition) },
218 getChildNumber = { desc.getElementAnnotations(0).filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: (it + 1) },
219 getChildName = { desc.getElementName(0) },
220 inOneOfStruct = true,
221 )
222 }
223 appendLine(" }")
224 } else {
225 val annotations = getAnnotations(index)
226
227 val isList = fieldDescriptor.isProtobufRepeated
228
229 nestedTypes += when {
230 fieldDescriptor.isProtobufNamedType -> generateNamedType(
231 fieldDescriptor = messageDescriptor.getElementDescriptor(index),
232 annotations = messageDescriptor.getElementAnnotations(index),
233 isSealedPolymorphic = messageDescriptor.isSealedPolymorphic && index == 1,
234 isOptional = messageDescriptor.isElementOptional(index),
235 inOneOfStruct = inOneOfStruct,
236 indent = if (inOneOfStruct) 2 else 1,
237 )
238 isList -> generateListType(parentType, index)
239 fieldDescriptor.isProtobufMap -> generateMapType(parentType, index)
240 else -> throw IllegalStateException(
241 "Unprocessed message field type with serial name " +
242 "'${fieldDescriptor.serialName}' and kind '${fieldDescriptor.kind}'"
243 )
244 }
245 if (!usedNumbers.add(number)) {
246 throw IllegalArgumentException("Field number $number is repeated in the class with serial name ${messageDescriptor.serialName}")
247 }
248
249 append(' ').append(fieldName).append(" = ").append(number)
250
251 val isPackRequested = annotations.filterIsInstance<ProtoPacked>().singleOrNull() != null
252
253 when {
254 !isPackRequested ||
255 !isList || // ignore as packed only meaningful on repeated types
256 !fieldDescriptor.getElementDescriptor(0).isPackable // Ignore if the type is not allowed to be packed
257 -> appendLine(';')
258
259 else -> appendLine(" [packed=true];")
260 }
261 }
262 }
263 }
264
265 private fun StringBuilder.generateNamedType(
266 fieldDescriptor: SerialDescriptor,
267 annotations: List<Annotation>,
268 isSealedPolymorphic: Boolean,
269 isOptional: Boolean,
270 inOneOfStruct: Boolean = false,
271 indent: Int = 1,
272 ): List<TypeDefinition> {
273 var unwrappedFieldDescriptor = fieldDescriptor
274 while (unwrappedFieldDescriptor.isInline) {
275 unwrappedFieldDescriptor = unwrappedFieldDescriptor.getElementDescriptor(0)
276 }
277
278 val nestedTypes: List<TypeDefinition>
279 val typeName: String = when {
280 isSealedPolymorphic -> {
281 append(" ".repeat(indent * 2)).appendLine("// decoded as message with one of these types:")
282 nestedTypes = unwrappedFieldDescriptor.elementDescriptors.map { TypeDefinition(it) }.toList()
283 nestedTypes.forEachIndexed { _, childType ->
284 append(" ".repeat(indent * 2)).append("// message ").append(childType.descriptor.messageOrEnumName).append(", serial name '")
285 .append(removeLineBreaks(childType.descriptor.serialName)).appendLine('\'')
286 }
287 unwrappedFieldDescriptor.scalarTypeName()
288 }
289 unwrappedFieldDescriptor.isProtobufScalar -> {
290 nestedTypes = emptyList()
291 unwrappedFieldDescriptor.scalarTypeName(annotations)
292 }
293 unwrappedFieldDescriptor.isOpenPolymorphic -> {
294 nestedTypes = listOf(SyntheticPolymorphicType)
295 SyntheticPolymorphicType.descriptor.serialName
296 }
297 else -> {
298 // enum or regular message
299 nestedTypes = listOf(TypeDefinition(unwrappedFieldDescriptor))
300 unwrappedFieldDescriptor.messageOrEnumName
301 }
302 }
303
304 if (isOptional) {
305 append(" ".repeat(indent * 2)).appendLine("// WARNING: a default value decoded when value is missing")
306 }
307 val optional = fieldDescriptor.isNullable || isOptional
308
309 append(" ".repeat(indent * 2)).append(
310 when {
311 inOneOfStruct -> ""
312 optional -> "optional "
313 else -> "required "
314 }
315 ).append(typeName)
316
317 return nestedTypes
318 }
319
320 private fun StringBuilder.generateMapType(messageType: TypeDefinition, index: Int): List<TypeDefinition> {
321 val messageDescriptor = messageType.descriptor
322 val mapDescriptor = messageDescriptor.getElementDescriptor(index)
323 val originalMapValueDescriptor = mapDescriptor.getElementDescriptor(1)
324 val valueType = if (originalMapValueDescriptor.isProtobufCollection) {
325 createNestedCollectionType(messageType, index, originalMapValueDescriptor, "nested collection in map value")
326 } else {
327 TypeDefinition(originalMapValueDescriptor)
328 }
329 val valueDescriptor = valueType.descriptor
330
331 if (originalMapValueDescriptor.isNullable) {
332 appendLine(" // WARNING: nullable map values can not be represented in protobuf")
333 }
334 generateCollectionAbsenceComment(messageDescriptor, mapDescriptor, index)
335
336 val keyTypeName = mapDescriptor.getElementDescriptor(0).scalarTypeName(mapDescriptor.getElementAnnotations(0))
337 val valueTypeName = valueDescriptor.protobufTypeName(mapDescriptor.getElementAnnotations(1))
338 append(" map<").append(keyTypeName).append(", ").append(valueTypeName).append(">")
339
340 return if (valueDescriptor.isProtobufMessageOrEnum) {
341 listOf(valueType)
342 } else {
343 emptyList()
344 }
345 }
346
347 private fun StringBuilder.generateListType(messageType: TypeDefinition, index: Int): List<TypeDefinition> {
348 val messageDescriptor = messageType.descriptor
349 val collectionDescriptor = messageDescriptor.getElementDescriptor(index)
350 val originalElementDescriptor = collectionDescriptor.getElementDescriptor(0)
351 val elementType = if (collectionDescriptor.kind == StructureKind.LIST) {
352 if (originalElementDescriptor.isProtobufCollection) {
353 createNestedCollectionType(messageType, index, originalElementDescriptor, "nested collection in list")
354 } else {
355 TypeDefinition(originalElementDescriptor)
356 }
357 } else {
358 createLegacyMapType(messageType, index, "legacy map")
359 }
360
361 val elementDescriptor = elementType.descriptor
362
363 if (elementDescriptor.isNullable) {
364 appendLine(" // WARNING: nullable elements of collections can not be represented in protobuf")
365 }
366 generateCollectionAbsenceComment(messageDescriptor, collectionDescriptor, index)
367
368 val typeName = elementDescriptor.protobufTypeName(messageDescriptor.getElementAnnotations(index))
369 append(" repeated ").append(typeName)
370
371 return if (elementDescriptor.isProtobufMessageOrEnum) {
372 listOf(elementType)
373 } else {
374 emptyList()
375 }
376 }
377
378 private fun StringBuilder.generateEnum(enumType: TypeDefinition) {
379 val enumDescriptor = enumType.descriptor
380 val enumName = enumDescriptor.messageOrEnumName
381 enumName.checkIsValidIdentifier {
382 "Invalid name for the enum in protobuf schema '$enumName'. Serial name of the enum " +
383 "class '${enumDescriptor.serialName}'"
384 }
385 val safeSerialName = removeLineBreaks(enumDescriptor.serialName)
386 if (safeSerialName != enumName) {
387 append("// serial name '").append(safeSerialName).appendLine('\'')
388 }
389
390 append("enum ").append(enumName).appendLine(" {")
391
392 val usedNumbers: MutableSet<Int> = mutableSetOf()
393 val duplicatedNumbers: MutableSet<Int> = mutableSetOf()
394 enumDescriptor.elementDescriptors.forEachIndexed { index, element ->
395 val elementName = element.protobufEnumElementName
396 elementName.checkIsValidIdentifier {
397 "The enum element name '$elementName' is invalid in the " +
398 "protobuf schema. Serial name of the enum class '${enumDescriptor.serialName}'"
399 }
400
401 val annotations = enumDescriptor.getElementAnnotations(index)
402 val number = annotations.filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: index
403 if (!usedNumbers.add(number)) {
404 duplicatedNumbers.add(number)
405 }
406
407 append(" ").append(elementName).append(" = ").append(number).appendLine(';')
408 }
409 if (duplicatedNumbers.isNotEmpty()) {
410 throw IllegalArgumentException(
411 "The class with serial name ${enumDescriptor.serialName} has duplicate " +
412 "elements with numbers $duplicatedNumbers"
413 )
414 }
415
416 appendLine('}')
417 }
418
419 private val SerialDescriptor.isOpenPolymorphic: Boolean
420 get() = kind == PolymorphicKind.OPEN
421
422 private val SerialDescriptor.isSealedPolymorphic: Boolean
423 get() = kind == PolymorphicKind.SEALED
424
425 private val SerialDescriptor.isProtobufNamedType: Boolean
426 get() = isProtobufMessageOrEnum || isProtobufScalar
427
428 private val SerialDescriptor.isProtobufScalar: Boolean
429 get() = (kind is PrimitiveKind)
430 || (kind is StructureKind.LIST && getElementDescriptor(0).kind === PrimitiveKind.BYTE)
431 || kind == SerialKind.CONTEXTUAL
432
433 private val SerialDescriptor.isProtobufMessageOrEnum: Boolean
434 get() = isProtobufMessage || isProtobufEnum
435
436 private val SerialDescriptor.isProtobufMessage: Boolean
437 get() = kind == StructureKind.CLASS || kind == StructureKind.OBJECT || kind == PolymorphicKind.SEALED || kind == PolymorphicKind.OPEN
438
439 private val SerialDescriptor.isProtobufCollection: Boolean
440 get() = isProtobufRepeated || isProtobufMap
441
442 private val SerialDescriptor.isProtobufRepeated: Boolean
443 get() = (kind == StructureKind.LIST && getElementDescriptor(0).kind != PrimitiveKind.BYTE)
444 || (kind == StructureKind.MAP && !getElementDescriptor(0).isValidMapKey)
445
446 private val SerialDescriptor.isProtobufMap: Boolean
447 get() = kind == StructureKind.MAP && getElementDescriptor(0).isValidMapKey
448
449 private val SerialDescriptor.isProtobufEnum: Boolean
450 get() = kind == SerialKind.ENUM
451
452 private val SerialDescriptor.isValidMapKey: Boolean
453 get() = kind == PrimitiveKind.INT || kind == PrimitiveKind.LONG || kind == PrimitiveKind.BOOLEAN || kind == PrimitiveKind.STRING
454
455
456 private val SerialDescriptor.messageOrEnumName: String
457 get() = (serialName.substringAfterLast('.', serialName)).removeSuffix("?")
458
459 private fun SerialDescriptor.isChildOneOfMessage(index: Int): Boolean =
460 this.getElementDescriptor(index).isSealedPolymorphic && this.getElementAnnotations(index).any { it is ProtoOneOf }
461
462 private fun SerialDescriptor.protobufTypeName(annotations: List<Annotation> = emptyList()): String {
463 return if (isProtobufScalar) {
464 scalarTypeName(annotations)
465 } else {
466 messageOrEnumName
467 }
468 }
469
470 private val SerialDescriptor.protobufEnumElementName: String
471 get() = serialName.substringAfterLast('.', serialName)
472
473 private fun SerialDescriptor.scalarTypeName(annotations: List<Annotation> = emptyList()): String {
474 val integerType = annotations.filterIsInstance<ProtoType>().firstOrNull()?.type ?: ProtoIntegerType.DEFAULT
475
476 if (kind == SerialKind.CONTEXTUAL) {
477 return "bytes"
478 }
479
480 if (kind is StructureKind.LIST && getElementDescriptor(0).kind == PrimitiveKind.BYTE) {
481 return "bytes"
482 }
483
484 return when (kind as PrimitiveKind) {
485 PrimitiveKind.BOOLEAN -> "bool"
486 PrimitiveKind.BYTE, PrimitiveKind.CHAR, PrimitiveKind.SHORT, PrimitiveKind.INT ->
487 when (integerType) {
488 ProtoIntegerType.DEFAULT -> "int32"
489 ProtoIntegerType.SIGNED -> "sint32"
490 ProtoIntegerType.FIXED -> "fixed32"
491 }
492 PrimitiveKind.LONG ->
493 when (integerType) {
494 ProtoIntegerType.DEFAULT -> "int64"
495 ProtoIntegerType.SIGNED -> "sint64"
496 ProtoIntegerType.FIXED -> "fixed64"
497 }
498 PrimitiveKind.FLOAT -> "float"
499 PrimitiveKind.DOUBLE -> "double"
500 PrimitiveKind.STRING -> "string"
501 }
502 }
503
504 @SuppressAnimalSniffer // Boolean.hashCode(boolean) in compiler-generated hashCode implementation
505 private data class TypeDefinition(
506 val descriptor: SerialDescriptor,
507 val isSynthetic: Boolean = false,
508 val ability: String? = null,
509 val containingMessageName: String? = null,
510 val fieldName: String? = null
511 )
512
513 private val SyntheticPolymorphicType = TypeDefinition(
514 buildClassSerialDescriptor("KotlinxSerializationPolymorphic") {
515 element("type", PrimitiveSerialDescriptor("typeDescriptor", PrimitiveKind.STRING))
516 element("value", buildSerialDescriptor("valueDescriptor", StructureKind.LIST) {
517 element("0", Byte.serializer().descriptor)
518 })
519 },
520 true,
521 "polymorphic types"
522 )
523
524 private class NotNullSerialDescriptor(val original: SerialDescriptor) : SerialDescriptor by original {
525 override val isNullable = false
526 }
527
528 private val SerialDescriptor.notNull get() = NotNullSerialDescriptor(this)
529
530 private fun StringBuilder.generateCollectionAbsenceComment(
531 messageDescriptor: SerialDescriptor,
532 collectionDescriptor: SerialDescriptor,
533 index: Int
534 ) {
535 if (!collectionDescriptor.isNullable && messageDescriptor.isElementOptional(index)) {
536 appendLine(" // WARNING: a default value decoded when value is missing")
537 } else if (collectionDescriptor.isNullable && !messageDescriptor.isElementOptional(index)) {
538 appendLine(" // WARNING: an empty collection decoded when a value is missing")
539 } else if (collectionDescriptor.isNullable && messageDescriptor.isElementOptional(index)) {
540 appendLine(" // WARNING: a default value decoded when value is missing")
541 }
542 }
543
544 private fun createLegacyMapType(
545 messageType: TypeDefinition,
546 index: Int,
547 description: String
548 ): TypeDefinition {
549 val messageDescriptor = messageType.descriptor
550 val fieldDescriptor = messageDescriptor.getElementDescriptor(index)
551 val fieldName = messageDescriptor.getElementName(index)
552 val messageName = messageDescriptor.messageOrEnumName
553
554 val wrapperName = "${messageName}_${fieldName}"
555 val wrapperDescriptor = buildClassSerialDescriptor(wrapperName) {
556 element("key", fieldDescriptor.getElementDescriptor(0).notNull)
557 element("value", fieldDescriptor.getElementDescriptor(1).notNull)
558 }
559
560 return TypeDefinition(
561 wrapperDescriptor,
562 true,
563 description,
564 messageType.containingMessageName ?: messageName,
565 messageType.fieldName ?: fieldName
566 )
567 }
568
569 private fun createNestedCollectionType(
570 messageType: TypeDefinition,
571 index: Int,
572 elementDescriptor: SerialDescriptor,
573 description: String
574 ): TypeDefinition {
575 val messageDescriptor = messageType.descriptor
576 val fieldName = messageDescriptor.getElementName(index)
577 val messageName = messageDescriptor.messageOrEnumName
578
579 val wrapperName = "${messageName}_${fieldName}"
580 val wrapperDescriptor = buildClassSerialDescriptor(wrapperName) {
581 element("value", elementDescriptor.notNull)
582 }
583
584 return TypeDefinition(
585 wrapperDescriptor,
586 true,
587 description,
588 messageType.containingMessageName ?: messageName,
589 messageType.fieldName ?: fieldName
590 )
591 }
592
593 private fun removeLineBreaks(text: String): String {
594 return text.replace('\n', ' ').replace('\r', ' ')
595 }
596
597 private val IDENTIFIER_REGEX = Regex("[A-Za-z][A-Za-z0-9_]*")
598
599 private fun String.checkIsValidFullIdentifier(messageSupplier: (String) -> String) {
600 if (split('.').any { !it.matches(IDENTIFIER_REGEX) }) {
601 throw IllegalArgumentException(messageSupplier.invoke(this))
602 }
603 }
604
605 private fun String.checkIsValidIdentifier(messageSupplier: () -> String) {
606 if (!matches(IDENTIFIER_REGEX)) {
607 throw IllegalArgumentException(messageSupplier.invoke())
608 }
609 }
610 }
611