-
Notifications
You must be signed in to change notification settings - Fork 1
add shared utilities (Names, NameUtils, TypeMapping) #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
708cafb
feat: add shared utilities for code generation (Names, NameUtils, Typ…
halotukozak e8d264c
fix: address PR review comments for shared utilities
halotukozak 66b9485
refactor: remove digit prefix handling in `toEnumConstantName` and up…
halotukozak a461566
ktlintformat
halotukozak 0597427
fix: improve acronym handling in `toCamelCase`, `toPascalCase`, and `…
halotukozak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
67 changes: 67 additions & 0 deletions
67
core/src/main/kotlin/com/avsystem/justworks/core/gen/NameUtils.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.avsystem.justworks.core.gen | ||
|
|
||
| private val DELIMITERS = Regex("[_\\-.]+") | ||
| private val CAMEL_BOUNDARY = Regex("(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])") | ||
|
|
||
|
halotukozak marked this conversation as resolved.
|
||
| /** | ||
| * Converts a string to camelCase. | ||
| * Splits on `_`, `-`, `.` delimiters, lowercases first segment, | ||
| * capitalizes subsequent segments, and joins. | ||
| */ | ||
| fun String.toCamelCase(): String = toPascalCase().replaceFirstChar { it.lowercaseChar() } | ||
|
halotukozak marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Converts a string to PascalCase. | ||
| * Like [toCamelCase] but capitalizes the first segment too. | ||
| */ | ||
| fun String.toPascalCase(): String = split(DELIMITERS) | ||
|
halotukozak marked this conversation as resolved.
|
||
| .filter { it.isNotEmpty() } | ||
| .flatMap { it.split(CAMEL_BOUNDARY) } | ||
| .joinToString("") { it.lowercase().replaceFirstChar { c -> c.uppercaseChar() } } | ||
|
|
||
| /** | ||
| * Converts any string to UPPER_SNAKE_CASE for use as an enum constant name. | ||
| * Inserts `_` before uppercase letters in camelCase, replaces non-alphanumeric | ||
| * with `_`, uppercases, deduplicates `_`, trims leading/trailing `_`. | ||
| */ | ||
| fun String.toEnumConstantName(): String { | ||
| val converted = replace(CAMEL_BOUNDARY, "_") | ||
| .replace(Regex("[^a-zA-Z0-9]+"), "_") | ||
| .trim('_') | ||
| .uppercase() | ||
|
|
||
| return when { | ||
| converted.isEmpty() -> this | ||
|
halotukozak marked this conversation as resolved.
|
||
| else -> converted | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Generates a PascalCase operation name from HTTP method and path. | ||
| * Path parameters like {id} become "ById", {userId} becomes "ByUserId". | ||
| * Handles hyphens, underscores, and dots in path segments. | ||
| * | ||
| * Examples: | ||
| * - ("POST", "/pets") -> "PostPets" | ||
| * - ("GET", "/pets/{id}") -> "GetPetsById" | ||
| * - ("PUT", "/users/{userId}/orders/{orderId}") -> "PutUsersByUserIdOrdersByOrderId" | ||
| * - ("GET", "/api-tokens") -> "GetApiTokens" | ||
| */ | ||
| fun operationNameFromPath(method: String, path: String): String { | ||
| val methodPart = method.lowercase().replaceFirstChar { it.uppercase() } | ||
|
|
||
| val pathPart = path | ||
| .split("/") | ||
| .filter { it.isNotEmpty() } | ||
| .joinToString("") { segment -> | ||
| if (segment.startsWith("{") && segment.endsWith("}")) { | ||
| // Path parameter: {id} -> ById, {userId} -> ByUserId | ||
| val paramName = segment.removeSurrounding("{", "}") | ||
| "By" + paramName.toPascalCase() | ||
| } else { | ||
| segment.toPascalCase() | ||
| } | ||
| } | ||
|
|
||
| return methodPart + pathPart | ||
| } | ||
81 changes: 81 additions & 0 deletions
81
core/src/main/kotlin/com/avsystem/justworks/core/gen/Names.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| package com.avsystem.justworks.core.gen | ||
|
|
||
| import com.squareup.kotlinpoet.ClassName | ||
| import com.squareup.kotlinpoet.MemberName | ||
|
|
||
| // ============================================================================ | ||
| // Ktor HTTP Client | ||
| // ============================================================================ | ||
|
|
||
| val HTTP_CLIENT = ClassName("io.ktor.client", "HttpClient") | ||
| val CONTENT_NEGOTIATION = ClassName("io.ktor.client.plugins.contentnegotiation", "ContentNegotiation") | ||
| val HTTP_HEADERS = ClassName("io.ktor.http", "HttpHeaders") | ||
|
|
||
| val JSON_FUN = MemberName("io.ktor.serialization.kotlinx.json", "json") | ||
|
halotukozak marked this conversation as resolved.
|
||
| val BODY_FUN = MemberName("io.ktor.client.call", "body") | ||
| val BODY_AS_TEXT_FUN = MemberName("io.ktor.client.statement", "bodyAsText") | ||
|
halotukozak marked this conversation as resolved.
|
||
| val SET_BODY_FUN = MemberName("io.ktor.client.request", "setBody") | ||
| val CONTENT_TYPE_FUN = MemberName("io.ktor.http", "contentType") | ||
| val CONTENT_TYPE_APPLICATION = ClassName("io.ktor.http", "ContentType", "Application") | ||
| val HEADERS_FUN = MemberName("io.ktor.client.request", "headers") | ||
|
|
||
| val GET_FUN = MemberName("io.ktor.client.request", "get") | ||
| val POST_FUN = MemberName("io.ktor.client.request", "post") | ||
| val PUT_FUN = MemberName("io.ktor.client.request", "put") | ||
| val DELETE_FUN = MemberName("io.ktor.client.request", "delete") | ||
| val PATCH_FUN = MemberName("io.ktor.client.request", "patch") | ||
|
|
||
| // ============================================================================ | ||
| // kotlinx.serialization | ||
| // ============================================================================ | ||
|
|
||
| val SERIALIZABLE = ClassName("kotlinx.serialization", "Serializable") | ||
| val SERIAL_NAME = ClassName("kotlinx.serialization", "SerialName") | ||
| val EXPERIMENTAL_SERIALIZATION_API = ClassName("kotlinx.serialization", "ExperimentalSerializationApi") | ||
| val SERIALIZATION_EXCEPTION = ClassName("kotlinx.serialization", "SerializationException") | ||
| val JSON_CLASS_DISCRIMINATOR = ClassName("kotlinx.serialization.json", "JsonClassDiscriminator") | ||
| val JSON_CLASS = ClassName("kotlinx.serialization.json", "Json") | ||
| val JSON_CONTENT_POLYMORPHIC_SERIALIZER = ClassName("kotlinx.serialization.json", "JsonContentPolymorphicSerializer") | ||
| val JSON_ELEMENT = ClassName("kotlinx.serialization.json", "JsonElement") | ||
| val SERIALIZERS_MODULE = ClassName("kotlinx.serialization.modules", "SerializersModule") | ||
|
|
||
| val JSON_OBJECT_EXT = MemberName("kotlinx.serialization.json", "jsonObject") | ||
|
|
||
| val ENCODE_TO_STRING_FUN = MemberName("kotlinx.serialization", "encodeToString") | ||
| val POLYMORPHIC_FUN = MemberName("kotlinx.serialization.modules", "polymorphic") | ||
| val SUBCLASS_FUN = MemberName("kotlinx.serialization.modules", "subclass") | ||
|
|
||
| // ============================================================================ | ||
| // Date/Time (kotlinx.datetime) | ||
| // ============================================================================ | ||
|
|
||
| val INSTANT = ClassName("kotlinx.datetime", "Instant") | ||
| val LOCAL_DATE = ClassName("kotlinx.datetime", "LocalDate") | ||
|
|
||
| // ============================================================================ | ||
| // Error Handling (Arrow + Kotlin stdlib) | ||
| // ============================================================================ | ||
|
|
||
| val RAISE = ClassName("arrow.core.raise", "Raise") | ||
| val RAISE_FUN = MemberName("arrow.core.raise.context", "raise") | ||
| val HTTP_ERROR = ClassName("com.avsystem.justworks", "HttpError") | ||
| val HTTP_ERROR_TYPE = ClassName("com.avsystem.justworks", "HttpErrorType") | ||
| val HTTP_SUCCESS = ClassName("com.avsystem.justworks", "HttpSuccess") | ||
|
|
||
| // ============================================================================ | ||
| // Kotlin stdlib | ||
| // ============================================================================ | ||
|
|
||
| val CLOSEABLE = ClassName("java.io", "Closeable") | ||
| val OPT_IN = ClassName("kotlin", "OptIn") | ||
|
|
||
| // ============================================================================ | ||
| // Shared client base (generated) | ||
| // ============================================================================ | ||
|
|
||
| val API_CLIENT_BASE = ClassName("com.avsystem.justworks", "ApiClientBase") | ||
| val HTTP_RESPONSE = ClassName("io.ktor.client.statement", "HttpResponse") | ||
| val HTTP_REQUEST_BUILDER = ClassName("io.ktor.client.request", "HttpRequestBuilder") | ||
| val TO_RESULT_FUN = MemberName("com.avsystem.justworks", "toResult") | ||
| val TO_EMPTY_RESULT_FUN = MemberName("com.avsystem.justworks", "toEmptyResult") | ||
| val ENCODE_PARAM_FUN = MemberName("com.avsystem.justworks", "encodeParam") | ||
60 changes: 60 additions & 0 deletions
60
core/src/main/kotlin/com/avsystem/justworks/core/gen/TypeMapping.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package com.avsystem.justworks.core.gen | ||
|
|
||
| import com.avsystem.justworks.core.model.PrimitiveType | ||
| import com.avsystem.justworks.core.model.TypeRef | ||
| import com.squareup.kotlinpoet.ANY | ||
| import com.squareup.kotlinpoet.BOOLEAN | ||
| import com.squareup.kotlinpoet.BYTE_ARRAY | ||
| import com.squareup.kotlinpoet.ClassName | ||
| import com.squareup.kotlinpoet.DOUBLE | ||
| import com.squareup.kotlinpoet.FLOAT | ||
| import com.squareup.kotlinpoet.INT | ||
| import com.squareup.kotlinpoet.LIST | ||
| import com.squareup.kotlinpoet.LONG | ||
| import com.squareup.kotlinpoet.MAP | ||
| import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy | ||
| import com.squareup.kotlinpoet.STRING | ||
| import com.squareup.kotlinpoet.TypeName | ||
|
|
||
| /** | ||
| * Maps [TypeRef] sealed variants to KotlinPoet [TypeName] instances. | ||
| */ | ||
| object TypeMapping { | ||
| fun toTypeName(typeRef: TypeRef, modelPackage: String): TypeName = when (typeRef) { | ||
| is TypeRef.Primitive -> { | ||
| when (typeRef.type) { | ||
| PrimitiveType.STRING -> STRING | ||
| PrimitiveType.INT -> INT | ||
| PrimitiveType.LONG -> LONG | ||
| PrimitiveType.DOUBLE -> DOUBLE | ||
| PrimitiveType.FLOAT -> FLOAT | ||
| PrimitiveType.BOOLEAN -> BOOLEAN | ||
| PrimitiveType.BYTE_ARRAY -> BYTE_ARRAY | ||
| PrimitiveType.DATE_TIME -> INSTANT | ||
| PrimitiveType.DATE -> LOCAL_DATE | ||
| } | ||
| } | ||
|
|
||
| is TypeRef.Array -> { | ||
| LIST.parameterizedBy(toTypeName(typeRef.items, modelPackage)) | ||
| } | ||
|
|
||
| is TypeRef.Map -> { | ||
| MAP.parameterizedBy(STRING, toTypeName(typeRef.valueType, modelPackage)) | ||
| } | ||
|
|
||
| is TypeRef.Reference -> { | ||
| ClassName(modelPackage, typeRef.schemaName) | ||
| } | ||
|
|
||
| is TypeRef.Inline -> { | ||
| // For nested inline classes (e.g., "Pet.Address"), sanitize to "Pet_Address" | ||
| val sanitizedName = typeRef.contextHint.replace(".", "_") | ||
| ClassName(modelPackage, sanitizedName) | ||
| } | ||
|
|
||
| is TypeRef.Unknown -> { | ||
| ANY | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.