Skip to content
This repository was archived by the owner on Jun 14, 2026. It is now read-only.

Commit fef9990

Browse files
author
mtctx
committed
Refactored how to add a custom Provider
1 parent d020d83 commit fef9990

6 files changed

Lines changed: 66 additions & 55 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ No boilerplate. No reflection. No runtime cost.
4747

4848
## Installation
4949

50-
Zappy will be published to **Maven Central** at `v1.0.0`.
50+
Zappy is published to **Maven Central**.
5151

5252
### Gradle (Kotlin DSL)
5353

@@ -127,9 +127,6 @@ annotation class FullName(val zpl: String = "<username>_<numeric:1-99>")
127127
data class Person(
128128
@FullName val fullName: String
129129
)
130-
131-
// register it at startup (e.g. in main or Application class)
132-
dev.mtctx.zappy.processor.registerNewZPLAnnotation(FullName::class)
133130
```
134131

135132
> `@ZappyAnnotation` enables KSP processing.
@@ -150,10 +147,8 @@ object CreditCardZPLProvider : ZPLProvider() {
150147
}
151148
}
152149

153-
// Register at startup (e.g. in main or Application class)
154-
dev.mtctx.zappy.processor.registerNewZPLProvider(CreditCardZPLProvider)
155-
// or
156-
dev.mtctx.zappy.processor.registerNewZPLProvider(CreditCardZPLProvider.mapEntry())
150+
// Register when using mock (e.g., in main or Application class)
151+
mock<Person>(CreditCardZPLProvider)
157152
```
158153

159154
Now use: `<credit-card:19>` → `"1234-5678-9012-3456"`

‎core/src/main/kotlin/dev/mtctx/zappy/zpl/ZPLProvider.kt‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ val defaultZPLProviders = mapOf(
5353
UUIDZPLProvider.mapEntry(),
5454
)
5555

56-
fun String.generateWithZPL(vararg customProviders: ZPLProvider = emptyArray()): String {
57-
val providers = (defaultZPLProviders + customProviders.associateBy { it.id }).mapKeys { it.key.lowercase() }
56+
fun String.generateWithZPLWithProviders(providers: Map<String, ZPLProvider> = emptyMap()): String {
5857
val regex = "<([^:]+)(?::([0-9]+)-([0-9]+)|:([0-9]+)|:-([0-9]+))?>".toRegex()
5958

6059
return regex.replace(this) { match ->
@@ -73,4 +72,7 @@ fun String.generateWithZPL(vararg customProviders: ZPLProvider = emptyArray()):
7372
else -> provider.generate() // Min 1, Max 20 (default, but depends on provider)
7473
}
7574
}
76-
}
75+
}
76+
77+
fun String.generateWithZPL(customProviders: Collection<ZPLProvider> = emptyList()) =
78+
generateWithZPLWithProviders((defaultZPLProviders + customProviders.associateBy { it.id }).mapKeys { it.key.lowercase() })

‎processor/src/main/kotlin/dev/mtctx/zappy/processor/FileGenerator.kt‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ package dev.mtctx.zappy.processor
1919
import com.google.devtools.ksp.isLocal
2020
import com.google.devtools.ksp.processing.KSPLogger
2121
import com.google.devtools.ksp.symbol.*
22-
import com.squareup.kotlinpoet.CodeBlock
23-
import com.squareup.kotlinpoet.FunSpec
24-
import com.squareup.kotlinpoet.buildCodeBlock
22+
import com.squareup.kotlinpoet.*
23+
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
2524
import com.squareup.kotlinpoet.ksp.addOriginatingKSFile
2625
import com.squareup.kotlinpoet.ksp.toClassName
2726
import com.squareup.kotlinpoet.ksp.toTypeName
27+
import dev.mtctx.zappy.zpl.ZPLProvider
2828

2929
fun KSClassDeclaration.processAndReturnFunSpec(
3030
logger: KSPLogger,
@@ -49,6 +49,12 @@ fun KSClassDeclaration.processAndReturnFunSpec(
4949
companionKClass?.declarations?.any { it is KSFunctionDeclaration && it.simpleName.asString() == "mock" } == true
5050

5151
val funSpec = FunSpec.builder("mock_${formatQualifiedName(qualifiedName!!.asString())}_Class")
52+
.addParameter(
53+
ParameterSpec.builder(
54+
"customProviders",
55+
Collection::class.asClassName().parameterizedBy(ZPLProvider::class.asClassName()),
56+
).defaultValue(CodeBlock.of("emptyList()")).build()
57+
)
5258
.returns(classTypeName)
5359
.addOriginatingKSFile(sourceFile)
5460

@@ -73,17 +79,17 @@ fun KSClassDeclaration.processAndReturnFunSpec(
7379
}
7480

7581
private fun KSValueParameter.generateMockValueCode(logger: KSPLogger): CodeBlock {
76-
val regexAnnotation = annotations.find { it.shortName.asString() == "WithRegex" }
77-
if (regexAnnotation != null) {
78-
val pattern = regexAnnotation.arguments.first { it.name?.asString() == "regex" }.value as String
79-
return CodeBlock.of("%S.generateWithZPL()", pattern)
82+
val customAnnotation = annotations.find { it.shortName.asString() == "Custom" }
83+
if (customAnnotation != null) {
84+
val pattern = customAnnotation.arguments.first { it.name?.asString() == "zpl" }.value as String
85+
return CodeBlock.of("%S.generateWithZPL(customProviders)", pattern)
8086
}
8187

8288
val specificAnnotation =
8389
annotations.find { it.annotationType.resolve().declaration.qualifiedName?.asString() in annotationClassNames }
8490
if (specificAnnotation != null) {
85-
val regex = specificAnnotation.arguments.first { it.name?.asString() == "zpl" }.value as String
86-
return CodeBlock.of("%S.generateWithZPL()", regex)
91+
val zpl = specificAnnotation.arguments.first { it.name?.asString() == "zpl" }.value as String
92+
return CodeBlock.of("%S.generateWithZPL(customProviders)", zpl)
8793
}
8894

8995
return when (type.resolve().toTypeName().toString()) {

‎processor/src/main/kotlin/dev/mtctx/zappy/processor/GeneralMockFunction.kt‎

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package dev.mtctx.zappy.processor
1919
import com.google.devtools.ksp.processing.KSPLogger
2020
import com.google.devtools.ksp.symbol.KSClassDeclaration
2121
import com.squareup.kotlinpoet.*
22+
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
23+
import dev.mtctx.zappy.zpl.ZPLProvider
2224

2325
fun buildGeneralMockFunctionAndReturnFileSpec(
2426
logger: KSPLogger,
@@ -29,25 +31,41 @@ fun buildGeneralMockFunctionAndReturnFileSpec(
2931

3032
val typeVariable = TypeVariableName("T", ANY).copy(reified = true)
3133

32-
val funSpec = FunSpec.builder("mock")
34+
val varargFunSpec = FunSpec.builder("mock")
3335
.addAnnotation(AnnotationSpec.builder(Throws::class).addMember("IllegalArgumentException::class").build())
3436
.addModifiers(KModifier.INLINE)
3537
.addTypeVariable(typeVariable)
38+
.addParameter(
39+
ParameterSpec.builder(
40+
"customProviders",
41+
ZPLProvider::class.asClassName(),
42+
KModifier.VARARG
43+
).defaultValue(CodeBlock.of("emptyArray()")).build()
44+
)
3645
.returns(typeVariable)
3746
.addCode(buildCodeBlock {
38-
/**
39-
* @Throws(IllegalArgumentException::class)
40-
* inline fun <reified T> function(): T = when (T::class) {
41-
* CoolData::class -> Cool.function() as T
42-
* else -> throw IllegalArgumentException("${T::class.simpleName} is not annotated with @Mock!")
43-
* }
44-
*/
47+
add("return mock<T>(customProviders.toList())")
48+
})
49+
.addKdoc("Generates a mock of the given type. Throws an exception if the type is not annotated with [dev.mtctx.zappy.annotation.Mock].")
50+
.build()
4551

52+
val funSpec = FunSpec.builder("mock")
53+
.addAnnotation(AnnotationSpec.builder(Throws::class).addMember("IllegalArgumentException::class").build())
54+
.addModifiers(KModifier.INLINE)
55+
.addTypeVariable(typeVariable)
56+
.addParameter(
57+
ParameterSpec.builder(
58+
"customProviders",
59+
Collection::class.asClassName().parameterizedBy(ZPLProvider::class.asClassName()),
60+
).defaultValue(CodeBlock.of("emptyList()")).build()
61+
)
62+
.returns(typeVariable)
63+
.addCode(buildCodeBlock {
4664
add("return when (T::class) {\n")
4765
classes.map { it.qualifiedName!!.asString() }
4866
.forEach { qualifiedName ->
4967
add(
50-
"${qualifiedName}::class -> mock_${formatQualifiedName(qualifiedName)}_Class() as T\n"
68+
"${qualifiedName}::class -> mock_${formatQualifiedName(qualifiedName)}_Class(customProviders) as T\n"
5169
)
5270
}
5371
add($$"else -> throw IllegalArgumentException(\"${T::class.simpleName} is not annotated with @Mock!\")\n")
@@ -57,6 +75,7 @@ fun buildGeneralMockFunctionAndReturnFileSpec(
5775
.build()
5876

5977
return FileSpec.builder("dev.mtctx.zappy", "MockProviders")
78+
.addFunction(varargFunSpec)
6079
.addFunction(funSpec)
6180
.addFunctions(funSpecs)
6281
.addImport("dev.mtctx.zappy.zpl", "generateWithZPL")

‎processor/src/main/kotlin/dev/mtctx/zappy/processor/MockProcessor.kt‎

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ import com.google.devtools.ksp.validate
2424
import com.squareup.kotlinpoet.FunSpec
2525
import com.squareup.kotlinpoet.ksp.writeTo
2626
import dev.mtctx.zappy.annotation.*
27-
import dev.mtctx.zappy.zpl.ZPLProvider
28-
import dev.mtctx.zappy.zpl.defaultZPLProviders
29-
import kotlin.reflect.KClass
3027

3128
internal val annotationClasses = mutableSetOf(
3229
Custom::class,
@@ -47,21 +44,6 @@ internal val externalAnnotationClassNames = mutableSetOf<String>()
4744
internal val annotationClassNames: Set<String>
4845
get() = annotationClasses.mapNotNull { it.qualifiedName }.toSet() + externalAnnotationClassNames
4946

50-
fun registerNewZappyAnnotation(clazz: Iterable<KClass<out Annotation>>) = annotationClasses.addAll(clazz)
51-
fun registerNewZappyAnnotation(clazz: KClass<out Annotation>) = annotationClasses.add(clazz)
52-
fun registerNewZappyAnnotation(vararg classes: KClass<out Annotation>) = annotationClasses.addAll(classes)
53-
54-
internal val zplProviders = mutableMapOf<String, ZPLProvider>().also {
55-
it.putAll(defaultZPLProviders)
56-
}
57-
58-
fun registerNewZPLProvider(provider: ZPLProvider) = zplProviders.put(provider.id, provider)
59-
fun registerNewZPLProvider(vararg providers: ZPLProvider) = zplProviders.putAll(providers.associateBy { it.id })
60-
fun registerNewZPLProvider(providerEntry: Pair<String, ZPLProvider>) =
61-
zplProviders.put(providerEntry.first, providerEntry.second)
62-
63-
fun registerNewZPLProvider(vararg providerEntries: Pair<String, ZPLProvider>) = zplProviders.putAll(providerEntries)
64-
6547
internal var errorOccurred = false
6648

6749
class MockProcessor(
@@ -127,30 +109,30 @@ internal fun KSClassDeclaration.isValidZappyAnnotation(logger: KSPLogger): Boole
127109
return false
128110
}
129111

130-
val regexParameter = constructor.parameters.find { it.name?.asString() == "regex" }
112+
val zplParameter = constructor.parameters.find { it.name?.asString() == "zpl" }
131113

132-
if (regexParameter == null) {
114+
if (zplParameter == null) {
133115
logger.error(
134-
"Annotation class '${simpleName.asString()}' must have a primary constructor parameter named 'regex'.",
116+
"Annotation class '${simpleName.asString()}' must have a primary constructor parameter named 'zpl'.",
135117
this
136118
)
137119
errorOccurred = true
138120
return false
139121
}
140122

141-
val typeName = regexParameter.type.resolve().declaration.qualifiedName?.asString()
123+
val typeName = zplParameter.type.resolve().declaration.qualifiedName?.asString()
142124

143125
if (typeName != "kotlin.String") {
144126
logger.error(
145-
"The 'regex' parameter in '${simpleName.asString()}' must be of type String. Found: $typeName.",
127+
"The 'zpl' parameter in '${simpleName.asString()}' must be of type String. Found: $typeName.",
146128
this
147129
)
148130
errorOccurred = true
149131
return false
150132
}
151133

152-
if (regexParameter.isVal.not() && regexParameter.isVar.not()) {
153-
logger.error("The 'regex' parameter in '${simpleName.asString()}' must be declared with 'val' or 'var'.", this)
134+
if (zplParameter.isVal.not() && zplParameter.isVar.not()) {
135+
logger.error("The 'zpl' parameter in '${simpleName.asString()}' must be declared with 'val' or 'var'.", this)
154136
errorOccurred = true
155137
return false
156138
}

‎test/src/main/kotlin/dev/mtctx/zappy/test/Main.kt‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ object TestZplProvider : ZPLProvider() {
2525
override val characterList = ('a'..'z') + ('A'..'Z') + ('0'..'9') + listOf('-', '_', '=')
2626
}
2727

28+
object Test2ZplProvider : ZPLProvider() {
29+
override val id: String = "test2"
30+
override val characterList = ('a'..'z') + ('A'..'Z') + ('0'..'9') + listOf('-', '_', '=')
31+
}
32+
2833
@ZappyAnnotation
2934
annotation class TestAnno(val zpl: String = "<test>")
3035

@@ -56,6 +61,8 @@ data class Test(
5661
@URL val url: String,
5762
)
5863

64+
val providers = arrayOf(TestZplProvider, Test2ZplProvider)
65+
5966
fun main() {
60-
println(mock<Test>(TestZplProvider))
67+
println(mock<Test>(*providers))
6168
}

0 commit comments

Comments
 (0)