Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ jobs:
id: gitversion
- name: Add NuGet package source
run: dotnet nuget add source https://nuget.pkg.github.com/${{github.repository_owner}}/index.json --name github --username ${{github.actor}} --password ${{github.token}} --store-password-in-clear-text
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- uses: actions/setup-node@v7
with:
node-version: 24
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
/artifacts/
**/TestResults/
/src/SmokeTest.TypeScript/generated/
/src/SmokeTest.Jvm/generated/
/src/SmokeTest.Jvm/.gradle/
/src/SmokeTest.Jvm/build/
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Build](https://github.com/TypedRest/CodeGeneration/actions/workflows/build.yml/badge.svg)](https://github.com/TypedRest/CodeGeneration/actions/workflows/build.yml)
[![API documentation](https://img.shields.io/badge/api-docs-orange.svg)](https://code-generation.typedrest.net/)
Tool that automatically infers [TypedRest Endpoints](https://typedrest.net/endpoints/) from patterns in [OpenAPI/Swagger](https://swagger.io/resources/open-api/) documents and generates source code for TypedRest clients. It can generate C# and TypeScript clients.
Tool that automatically infers [TypedRest Endpoints](https://typedrest.net/endpoints/) from patterns in [OpenAPI/Swagger](https://swagger.io/resources/open-api/) documents and generates source code for TypedRest clients. It can generate C#, TypeScript, Kotlin and Java clients.

Write a C# client to disk with the command-line tool:

Expand All @@ -13,6 +13,11 @@ Write a TypeScript client to disk with the command-line tool:

typedrest-codegen generate -l typescript -f myapi.yml -o src/myclient/ -s MyService --generate-dtos

Write a Java or Kotlin client to disk with the command-line tool:

typedrest-codegen generate -l java -f myapi.yml -o src/main/java/ -s MyService -n com.mycompany.myservice --generate-dtos
typedrest-codegen generate -l kotlin -f myapi.yml -o src/main/kotlin/ -s MyService -n com.mycompany.myservice --generate-dtos

Or generate a C# client during compilation instead, with nothing written to disk:

dotnet add package TypedRest.SourceGenerator
Expand All @@ -36,6 +41,15 @@ Generates C# source code for TypedRest .NET clients from OpenAPI/Swagger documen
[![TypedRest.CodeGeneration.TypeScript](https://img.shields.io/nuget/v/TypedRest.CodeGeneration.TypeScript.svg?label=TypedRest.CodeGeneration.TypeScript)](https://www.nuget.org/packages/TypedRest.CodeGeneration.TypeScript/)
Generates TypeScript source code for TypedRest clients from OpenAPI/Swagger documents.

[![TypedRest.CodeGeneration.Jvm](https://img.shields.io/nuget/v/TypedRest.CodeGeneration.Jvm.svg?label=TypedRest.CodeGeneration.Jvm)](https://www.nuget.org/packages/TypedRest.CodeGeneration.Jvm/)
Shared logic for generating source code for JVM-based languages.

[![TypedRest.CodeGeneration.Java](https://img.shields.io/nuget/v/TypedRest.CodeGeneration.Java.svg?label=TypedRest.CodeGeneration.Java)](https://www.nuget.org/packages/TypedRest.CodeGeneration.Java/)
Generates Java source code for TypedRest clients from OpenAPI/Swagger documents. [TypedRest for the JVM](https://github.com/TypedRest/TypedRest-Java) is written in Kotlin, so prefer the Kotlin generator unless your own source is Java.

[![TypedRest.CodeGeneration.Kotlin](https://img.shields.io/nuget/v/TypedRest.CodeGeneration.Kotlin.svg?label=TypedRest.CodeGeneration.Kotlin)](https://www.nuget.org/packages/TypedRest.CodeGeneration.Kotlin/)
Generates Kotlin source code for TypedRest clients from OpenAPI/Swagger documents.

You can also [build your own generator](https://typedrest.net/code-generation/custom-code/) for more complex APIs. For the relevant types and methods take a look at the **[API documentation](https://code-generation.typedrest.net/)**.

[![TypedRest.SourceGenerator](https://img.shields.io/nuget/v/TypedRest.SourceGenerator.svg?label=TypedRest.SourceGenerator)](https://www.nuget.org/packages/TypedRest.SourceGenerator/)
Expand Down
8 changes: 8 additions & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ foreach (var type in doc.GenerateTypedRest(new GenerationOptions("MyService")
| [TypedRest.CodeGeneration](https://www.nuget.org/packages/TypedRest.CodeGeneration/) | <xref:TypedRest.CodeGeneration> | Parses OpenAPI/Swagger documents and infers TypedRest Endpoints from patterns. |
| [TypedRest.CodeGeneration.CSharp](https://www.nuget.org/packages/TypedRest.CodeGeneration.CSharp/) | <xref:TypedRest.CodeGeneration.CSharp> | Generates C# source code for TypedRest .NET clients from OpenAPI/Swagger documents. |
| [TypedRest.CodeGeneration.TypeScript](https://www.nuget.org/packages/TypedRest.CodeGeneration.TypeScript/) | <xref:TypedRest.CodeGeneration.TypeScript> | Generates TypeScript source code for TypedRest clients from OpenAPI/Swagger documents. |
| [TypedRest.CodeGeneration.Jvm](https://www.nuget.org/packages/TypedRest.CodeGeneration.Jvm/) | <xref:TypedRest.CodeGeneration.Jvm> | Shared logic for generating source code for JVM-based languages. |
| [TypedRest.CodeGeneration.Kotlin](https://www.nuget.org/packages/TypedRest.CodeGeneration.Kotlin/) | <xref:TypedRest.CodeGeneration.Kotlin> | Generates Kotlin source code for TypedRest clients from OpenAPI/Swagger documents. |
| [TypedRest.CodeGeneration.Java](https://www.nuget.org/packages/TypedRest.CodeGeneration.Java/) | <xref:TypedRest.CodeGeneration.Java> | Generates Java source code for TypedRest clients from OpenAPI/Swagger documents. |
| [TypedRest.SourceGenerator](https://www.nuget.org/packages/TypedRest.SourceGenerator/) | | Roslyn [source generator](https://typedrest.net/code-generation/source-generator/) that builds clients during compilation. |
| [typedrest-codegen](https://www.nuget.org/packages/typedrest-codegen/) | | [Command-line tool](https://typedrest.net/code-generation/cli/) that writes the generated code to disk. |

Expand All @@ -36,9 +39,14 @@ foreach (var type in doc.GenerateTypedRest(new GenerationOptions("MyService")
```mermaid
flowchart TD
cli["typedrest-codegen"] --> csharp
cli --> java
cli --> kotlin
cli --> typescript
sourcegen["TypedRest.SourceGenerator"] --> csharp
csharp["TypedRest.CodeGeneration.<br>CSharp"] --> core
jvm["TypedRest.CodeGeneration.<br>Jvm"] --> core
java["TypedRest.CodeGeneration.<br>Java"] --> jvm
kotlin["TypedRest.CodeGeneration.<br>Kotlin"] --> jvm
typescript["TypedRest.CodeGeneration.<br>TypeScript"] --> core
core["TypedRest.CodeGeneration"]
```
42 changes: 42 additions & 0 deletions src/SmokeTest.Jvm/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
plugins {
kotlin("jvm") version "2.4.10"
kotlin("plugin.serialization") version "2.4.10"
}

repositories {
mavenCentral()
}

kotlin {
jvmToolchain(21)
}

sourceSets {
main {
kotlin.srcDir("generated/kotlin")
java.srcDir("generated/java")
}
}

dependencies {
implementation("net.typedrest:typedrest:0.33.0")
implementation("net.typedrest:typedrest-serializers-jackson:0.32.0")

// The @Serializable and @SerialName annotations the Kotlin generator emits. TypedRest depends on
// kotlinx-serialization only as `implementation`, so it does not reach a consumer's compile classpath and has
// to be declared here; the kotlin("plugin.serialization") plugin adds the compiler plugin but no dependency.
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")

// Carries the @Nullable annotations the Java generator emits, so Kotlin sees real nullability
compileOnly("org.jspecify:jspecify:1.0.1")
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
allWarningsAsErrors.set(true)
}
}

tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))
}
1 change: 1 addition & 0 deletions src/SmokeTest.Jvm/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = "smoketest-jvm"
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package net.typedrest.smoketest;

import net.typedrest.smoketest.java.SampleClient;
import net.typedrest.smoketest.java.dtos.Contact;
import net.typedrest.smoketest.java.dtos.Note;

import java.io.InputStream;
import java.net.URI;
import java.util.List;

public final class UsageJava {
private UsageJava() {}

public static SampleClient client(URI uri) {
return new SampleClient(uri);
}

public static List<Contact> readAllContacts(SampleClient client) {
return client.contacts.readAll();
}

public static Note readNote(SampleClient client, String id) {
return client.contacts.get(id).note.read();
}

public static void writeNote(SampleClient client, String id, Note note) {
client.contacts.get(id).note.set(note);
}

public static InputStream pokeAndDownload(SampleClient client, Contact contact) {
// get() also accepts an entity, extracting its id property
var element = client.contacts.get(contact);
element.poke.invoke();
return element.picture.download();
}

public static Note createContact(SampleClient client, Contact contact) {
var created = client.contacts.create(contact);
return created == null ? null : created.note.read();
}

public static Contact requiredProperties() {
// The generated DTO has both a no-argument constructor for the serializer and a full one
return new Contact(null, "John", "Doe");
}
}
34 changes: 34 additions & 0 deletions src/SmokeTest.Jvm/src/main/kotlin/UsageKotlin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package net.typedrest.smoketest

import net.typedrest.smoketest.kotlin.SampleClient
import net.typedrest.smoketest.kotlin.dtos.Contact
import net.typedrest.smoketest.kotlin.dtos.Note
import java.io.InputStream
import java.net.URI

fun client(uri: URI): SampleClient =
SampleClient(uri)

fun readAllContacts(client: SampleClient): List<Contact> =
client.contacts.readAll()

fun readNote(client: SampleClient, id: String): Note =
client.contacts[id].note.read()

fun writeNote(client: SampleClient, id: String, note: Note) {
client.contacts[id].note.set(note)
}

fun pokeAndDownload(client: SampleClient, contact: Contact): InputStream {
// get() also accepts an entity, extracting its id property
val element = client.contacts[contact]
element.poke.invoke()
return element.picture.download()
}

fun createContact(client: SampleClient, contact: Contact): Note? =
client.contacts.create(contact)?.note?.read()

fun requiredProperties(): Contact =
// firstName and lastName are required, id is not and defaults to null
Contact(firstName = "John", lastName = "Doe")
8 changes: 6 additions & 2 deletions src/TypedRest.CodeGeneration.Cli/Commands/Generate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Microsoft.CodeAnalysis.CSharp;
using TypedRest.CodeGeneration.CSharp;
using TypedRest.CodeGeneration.Generation;
using TypedRest.CodeGeneration.Java;
using TypedRest.CodeGeneration.Kotlin;
using TypedRest.CodeGeneration.TypeScript;

namespace TypedRest.CodeGeneration.Cli.Commands;
Expand All @@ -15,7 +17,7 @@ public class Generate : CommandBase
[Option('s', "service-name", HelpText = "The service name to use for the entry endpoint.", Required = true)]
public string ServiceName { get; set; } = default!;

[Option('l', "language", Default = CSharpClientGenerator.LanguageName, HelpText = "The language to generate: 'csharp' or 'typescript'.")]
[Option('l', "language", Default = CSharpClientGenerator.LanguageName, HelpText = "The language to generate: 'csharp', 'typescript', 'kotlin' or 'java'.")]
public string Language { get; set; } = CSharpClientGenerator.LanguageName;

[Option('n', "namespace", HelpText = "The C# namespace for the endpoints, or the directory for TypeScript. Uses service-name if not set.")]
Expand Down Expand Up @@ -45,7 +47,9 @@ public class Generate : CommandBase
private static ClientGeneratorRegistry Generators
=> new ClientGeneratorRegistry()
.Add(new CSharpClientGenerator())
.Add(new TypeScriptClientGenerator());
.Add(new TypeScriptClientGenerator())
.Add(new KotlinClientGenerator())
.Add(new JavaClientGenerator());

public override int Run()
{
Expand Down
34 changes: 30 additions & 4 deletions src/TypedRest.CodeGeneration.Cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ Generates a TypedRest client.
| `-f`, `--file` (required) | The path to the Swagger or OpenAPI spec file. Use `-` to read from standard input. | |
| `-o`, `--output` (required) | The directory to write the generated source code to. | |
| `-s`, `--service-name` (required) | The service name to use for the entry endpoint. | |
| `-l`, `--language` | The language to generate: `csharp` or `typescript`. | `csharp` |
| `-n`, `--namespace` | The C# namespace for the endpoints, or the directory for TypeScript. | the service name |
| `--dto-namespace` | The C# namespace for the DTOs, or the directory for TypeScript. | see below |
| `-l`, `--language` | The language to generate: `csharp`, `typescript`, `kotlin` or `java`. | `csharp` |
| `-n`, `--namespace` | The namespace (C#), package (Kotlin/Java) or directory (TypeScript) for the endpoints. | the service name |
| `--dto-namespace` | The same for the DTOs. | see below |
| `--generate-interfaces` | Also generate interfaces for the endpoints. **C# only.** | off |
| `--generate-dtos` | Also generate DTOs for the schemas in the document. | off |
| `--generate-entry-constructor` | Give the entry endpoint a constructor taking the base URI. Pass `false` to write your own in a partial class. **C# only.** | on |
| `--generate-entry-constructor` | Give the entry endpoint a constructor taking the base URI. Pass `false` to write your own. **Not for TypeScript.** | on |
| `--lang-version` | The minimum C# version the generated code must compile with, using the same values as the MSBuild `LangVersion` property. **C# only.** | `latest` |
| `--serializer` | The JSON serializer the generated DTOs are annotated for. See below. | per language |

Expand Down Expand Up @@ -63,6 +63,32 @@ Endpoints become classes deriving from the TypedRest endpoint types and exposing

`--serializer` has no effect here, for the same reason: there is no serializer to choose and nothing to annotate.

### Kotlin

typedrest-codegen generate -l kotlin -f myapi.yml -o src/main/kotlin/ -s MyService -n com.mycompany.myservice --generate-dtos

The generated code derives from [TypedRest for the JVM](https://github.com/TypedRest/TypedRest-Java), so add `net.typedrest:typedrest` to the consuming project — plus `net.typedrest:typedrest-reactive` if the document describes any polling or streaming endpoints.

One file per type, in a directory matching its package, so `--output` is the source root (`src/main/kotlin/`) rather than the package directory. `--namespace` is the package for the endpoints and `--dto-namespace` the one for the DTOs, defaulting to a `dtos` subpackage of the endpoints.

Endpoints become `open class`es deriving from the TypedRest `Impl` classes and exposing their children as `val`s. DTOs become `data class`es, and schemas with an `enum` become `enum class`es. Optional properties are nullable and default to `null`; required ones get no default, so a missing value is a compile error.

`--serializer` picks `kotlinx` (default), `jackson` or `moshi`. kotlinx.serialization is what `EntryEndpoint` itself defaults to, so a client generated for it passes no serializer at all; the others are passed explicitly.

Generating DTOs for `kotlinx` needs both the `kotlin("plugin.serialization")` Gradle plugin **and** an explicit `org.jetbrains.kotlinx:kotlinx-serialization-json` dependency: TypedRest depends on it only as `implementation`, so it does not reach your compile classpath, and the plugin adds the compiler plugin but no dependency.

### Java

typedrest-codegen generate -l java -f myapi.yml -o src/main/java/ -s MyService -n com.mycompany.myservice --generate-dtos

Prefer the Kotlin generator if you can: TypedRest for the JVM is written in Kotlin, so that is the lower-friction direction. Use this one when your own source is Java.

The layout matches the Kotlin generator's. Endpoints expose their children as `public final` fields rather than getters, because a getter recomputing the endpoint on every call would hand out a new instance each time and throw away the response cache. DTOs become plain classes with public fields, a no-argument constructor and a full one.

Properties the document does not require are annotated with JSpecify's `@Nullable`, so that Kotlin consumers get real null safety instead of platform types. Add `org.jspecify:jspecify` to the consuming project, or drop the annotations by generating Kotlin instead.

`--serializer` picks `jackson` (default) or `moshi`. `kotlinx` is rejected here: kotlinx.serialization generates its serializers with a Kotlin compiler plugin and cannot handle a class written in Java, so a client generated for it would compile and then fail to deserialize anything.

## `pattern`

Runs only the inference step and writes the result back into the document as an `x-typedrest` extension, for inspecting or hand-editing what the tool infers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<ItemGroup>
<ProjectReference Include="..\TypedRest.CodeGeneration.CSharp\TypedRest.CodeGeneration.CSharp.csproj" />
<ProjectReference Include="..\TypedRest.CodeGeneration.TypeScript\TypedRest.CodeGeneration.TypeScript.csproj" />
<ProjectReference Include="..\TypedRest.CodeGeneration.Java\TypedRest.CodeGeneration.Java.csproj" />
<ProjectReference Include="..\TypedRest.CodeGeneration.Kotlin\TypedRest.CodeGeneration.Kotlin.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
Expand Down
Loading
Loading