{"id":21253942,"url":"https://github.com/courseorchestra/hurdy-gurdy","last_synced_at":"2026-07-12T04:00:31.793Z","repository":{"id":37804536,"uuid":"448626169","full_name":"CourseOrchestra/hurdy-gurdy","owner":"CourseOrchestra","description":"Generate client and server side Java/Kotlin code based on OpenAPI spec","archived":false,"fork":false,"pushed_at":"2026-07-06T15:59:01.000Z","size":607,"stargazers_count":38,"open_issues_count":1,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-07-06T17:23:24.619Z","etag":null,"topics":["hurdy-gurdy","openapi","openapi-generator"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CourseOrchestra.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-01-16T17:34:56.000Z","updated_at":"2026-07-06T16:00:34.000Z","dependencies_parsed_at":"2023-10-12T05:22:11.487Z","dependency_job_id":"1a735e7c-89da-4174-87f4-521dbeb5f683","html_url":"https://github.com/CourseOrchestra/hurdy-gurdy","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/CourseOrchestra/hurdy-gurdy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CourseOrchestra%2Fhurdy-gurdy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CourseOrchestra%2Fhurdy-gurdy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CourseOrchestra%2Fhurdy-gurdy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CourseOrchestra%2Fhurdy-gurdy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CourseOrchestra","download_url":"https://codeload.github.com/CourseOrchestra/hurdy-gurdy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CourseOrchestra%2Fhurdy-gurdy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35381310,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-12T02:00:06.386Z","response_time":87,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hurdy-gurdy","openapi","openapi-generator"],"created_at":"2024-11-21T03:53:33.421Z","updated_at":"2026-07-12T04:00:31.783Z","avatar_url":"https://github.com/CourseOrchestra.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"docs/logo.png\" alt=\"Hurdy-Gurdy\" width=\"220\"\u003e\u003c/p\u003e\r\n\r\n[![Actions Status: build](https://github.com/courseorchestra/hurdy-gurdy/workflows/build/badge.svg)](https://github.com/courseorchestra/hurdy-gurdy/actions?query=workflow%3A\"build\")\r\n\r\n[![Maven Central](https://img.shields.io/maven-central/v/ru.curs/hurdy-gurdy)](https://central.sonatype.com/artifact/ru.curs/hurdy-gurdy)\r\n\r\n\r\n\r\n# Hurdy-Gurdy\r\n\r\nGenerates client and server side Java/Kotlin code based on OpenAPI spec, using [swagger-parser](https://github.com/swagger-api/swagger-parser), [JavaPoet](https://github.com/palantir/javapoet) and [KotlinPoet](https://github.com/square/kotlinpoet).\r\n\r\n## Usage examples\r\n\r\nJava or Kotlin? Spring or Quarkus? Maven, Gradle, or plain CLI?  [Fill this form](https://courseorchestra.github.io/hurdy-gurdy/) and copy the config for your build tool.\r\n\r\n### Maven plugin\r\n\r\n```xml\r\n\u003cplugin\u003e\r\n    \u003cgroupId\u003eru.curs\u003c/groupId\u003e\r\n    \u003cartifactId\u003ehurdy-gurdy\u003c/artifactId\u003e\r\n    \u003cversion\u003e2.10\u003c/version\u003e\r\n    \u003cconfiguration\u003e\r\n        \u003c!--Root package for generated code--\u003e\r\n        \u003crootPackage\u003ecom.example.project\u003c/rootPackage\u003e\r\n        \u003cspec\u003e${basedir}/src/main/openapi/api.yaml\u003c/spec\u003e\r\n        \u003c!--Set to true if you want to have HttpServletResponse response \r\n        parameter in Controller interface: good for server-side code.\r\n        Set to false (default) if you don't need one \r\n        (good for client-side)--\u003e\r\n        \u003cgenerateResponseParameter\u003etrue\u003c/generateResponseParameter\u003e\r\n        \u003c!--Optional: target web framework (spring|quarkus, default spring)\r\n        and which interfaces to generate (any subset of controller,api,client;\r\n        default controller) — see \"Generated interfaces\" below--\u003e\r\n        \u003cframework\u003espring\u003c/framework\u003e\r\n        \u003cgenerate\u003econtroller,client\u003c/generate\u003e\r\n        \u003c!--Optional: Java DTO style (lombok|pojo|records, default lombok);\r\n        Java only — see \"Java DTO styles\" below--\u003e\r\n        \u003cjavaDtoStyle\u003elombok\u003c/javaDtoStyle\u003e\r\n    \u003c/configuration\u003e\r\n    \u003cexecutions\u003e\r\n        \u003cexecution\u003e\r\n            \u003cgoals\u003e\r\n                \u003cgoal\u003egen-server\u003c/goal\u003e\r\n            \u003c/goals\u003e\r\n        \u003c/execution\u003e\r\n    \u003c/executions\u003e\r\n\u003c/plugin\u003e\r\n```\r\n\r\n### Gradle plugin\r\n\r\n```kotlin\r\nimport ru.curs.hurdygurdy.Framework\r\nimport ru.curs.hurdygurdy.Role\r\nimport ru.curs.hurdygurdy.gradle.Language\r\n\r\nplugins {\r\n    java\r\n    id(\"ru.curs.hurdy-gurdy\") version \"2.11\"\r\n}\r\n\r\nhurdyGurdy {\r\n    \"petstore\" {\r\n        spec = layout.projectDirectory.file(\"src/main/openapi/api.yaml\")\r\n        rootPackage = \"com.example.project\"\r\n        framework = Framework.SPRING            // default SPRING\r\n        language = Language.JAVA                // default JAVA\r\n        generate = setOf(Role.CONTROLLER)       // default [CONTROLLER]\r\n        generateResponseParameter = true        // default false\r\n        forceSnakeCaseForProperties = true      // default true\r\n    }\r\n}\r\n```\r\n\r\nEach named block registers a `generate\u003cName\u003e` task (e.g. `generatePetstore`) whose\r\noutput dir is added to the `main` source set, so `compileJava`/`compileKotlin`\r\ndepend on it automatically. The task is cacheable: an unchanged spec keeps both\r\ngeneration and dependent compilation `UP-TO-DATE`.\r\n\r\nFor Kotlin output, set `language = Language.KOTLIN` and apply the Kotlin JVM plugin.\r\n\r\n### Direct API usage from Kotlin code\r\n\r\n```kotlin\r\nimport ru.curs.hurdygurdy.Framework\r\nimport ru.curs.hurdygurdy.GeneratorParams\r\nimport ru.curs.hurdygurdy.KotlinCodegen\r\nimport ru.curs.hurdygurdy.Role\r\n\r\nval codegen = KotlinCodegen(\r\n    GeneratorParams.rootPackage(\"com.example.project\")\r\n        // optional: spring is the default\r\n        .framework(Framework.QUARKUS)\r\n        // optional: controller alone is the default;\r\n        // a Kotlin collection works too: .generate(listOf(Role.CONTROLLER, Role.CLIENT))\r\n        .generate(Role.CONTROLLER, Role.CLIENT)\r\n)\r\nval yamlPath = project.layout.projectDirectory.asFile.toPath().resolve(\"src/main/openapi/api.yaml\")\r\nval resultPath = project.layout.buildDirectory.get().asFile.toPath().resolve(\"generated-sources\")\r\nFiles.createDirectories(resultPath)\r\ncodegen.generate(yamlPath, resultPath)\r\n```\r\n\r\n### CLI\r\n\r\nBuild the executable fat jar:\r\n\r\n    mvn -DskipTests package\r\n    java -jar target/hurdy-gurdy-\u003cversion\u003e-cli.jar \\\r\n        --spec src/main/openapi/api.yaml \\\r\n        --root-package com.example.project \\\r\n        --output generated-sources \\\r\n        --framework spring --generate controller,client\r\n\r\nOptions: `--language java|kotlin`, `--framework spring|quarkus`,\r\n`--generate` (subset of `controller,api,client`), `--response-parameter`,\r\n`--force-snake-case` (negate with `--no-...`). Run with `--help` for the full list.\r\n\r\nOptionally build a native binary (requires GraalVM):\r\n\r\n    mvn -Pnative -DskipTests package\r\n    ./target/hurdy-gurdy --spec ... --root-package ... --output ...\r\n\r\n\r\n## Configuration parameters\r\n\r\n| Parameter name | Type | Default value | Description |\r\n|--------------------------|------|---------------|-------------|\r\n|`rootPackage`|String | | Sets the root package for all the generated classes. `Controller`, `Api` and `Client` interfaces will be generated in `controller` subpackage, and all the DTOs will be generated in `dto` subpackage.\r\n|`generateResponseParameter`|boolean|false|Set to true if you need access to the raw HTTP response. For `controller`: adds an `HttpServletResponse` parameter to each method (Spring) or makes methods return `jakarta.ws.rs.core.Response` (Quarkus) — useful for returning specific HTTP status codes; together with the operation-level extension `x-include-request: true`, the method also receives the request. For `client`: methods return the HTTP envelope (`ResponseEntity\u003cT\u003e` for Spring, `Response` for Quarkus). Never affects `api` interfaces.\r\n|`generateApiInterface`|boolean|false|**Deprecated** — equivalent to adding `api` to `generate` (see below). Kept for backwards compatibility.\r\n|`forceSnakeCaseForProperties`|boolean|true|By default, hurdy-gurdy expects all the properties of DTO classes to be defined in _snake_case_ in the specification. It converts these names to _camelCase_ for generated classes and sets Jackson's `SnakeCaseStrategy` so that they will still be _snake_case_ in JSON representation. If you don't want this (e. g. if you want your properties to be defined in _camelCase_ everywhere) you can turn off this function via this parameter. \r\n|`framework`|String (`spring`\\|`quarkus`)|`spring`|Selects the web framework whose annotations are emitted on the generated interfaces. `spring` (default) emits Spring MVC annotations (`@GetMapping`, `@PathVariable`, …). `quarkus` emits Jakarta REST / Quarkus annotations (`@GET` + `@Path`, `@PathParam`, `@QueryParam`, `@HeaderParam`, `@RestForm` for multipart). Value is case-insensitive.|\r\n|`generate`|comma-separated subset of `controller`, `api`, `client`|`controller`|Selects which interfaces to generate — any combination in a single run, e.g. `\u003cgenerate\u003econtroller,client\u003c/generate\u003e`. See [Generated interfaces](#generated-interfaces-generate). Case-insensitive.|\r\n|`javaDtoStyle`|String (`lombok`\\|`pojo`\\|`records`)|`lombok`|Shape of the generated **Java** DTOs. `lombok` (default) emits Lombok `@Data` classes (requires Lombok on the classpath). `pojo` emits plain classes with explicit getters/setters plus `equals`/`hashCode`/`toString` — no Lombok dependency. `records` emits Java records. Applies to Java only; Kotlin always generates `data class`es. See [Java DTO styles](#java-dto-styles-javadtostyle). Case-insensitive.|\r\n\r\n## Generated interfaces (`generate`)\r\n\r\nThe `generate` parameter selects which interfaces are emitted for the API paths — any\r\nsubset of `controller`, `api` and `client`, in a single run, all in the `controller`\r\nsubpackage and sharing the same DTOs. Combined with `framework`, this gives six\r\npossible artifacts:\r\n\r\n| `generate` value | `framework=spring` | `framework=quarkus` |\r\n|---|---|---|\r\n| `controller` | `XxxController` — server interface to implement: `@GetMapping`, …; optional `HttpServletResponse` parameter | `XxxController` — Jakarta REST resource interface to implement: `@GET` + `@Path`, …; optional `Response` return type |\r\n| `api` | `XxxApi` — pure contract: same Spring MVC annotations, no response-related artifacts. Directly consumable by [Spring Cloud OpenFeign](https://spring.io/projects/spring-cloud-openfeign) (its default `SpringMvcContract` parses `@GetMapping`/`@RequestMapping` on [OpenFeign](https://github.com/OpenFeign/feign) client interfaces), or as a typed contract for hand-written implementations, e.g. over [REST Assured](https://rest-assured.io/) in tests | `XxxApi` — pure contract: same Jakarta REST annotations, no response-related artifacts. Directly consumable by [MicroProfile REST Client](https://github.com/eclipse/microprofile-rest-client)'s `RestClientBuilder.newBuilder().baseUri(…).build(XxxApi.class)` (no `@RegisterRestClient` needed for the programmatic API), or as a typed contract for hand-written implementations |\r\n| `client` | `XxxClient` — [Spring 6 HTTP Interface](https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface): `@GetExchange`, …; create a proxy with `HttpServiceProxyFactory` | `XxxClient` — [MicroProfile / Quarkus REST Client](https://quarkus.io/guides/rest-client): `@RegisterRestClient` interface, inject it with `@RestClient` |\r\n\r\nFor example, a Quarkus service that also calls itself from tests (or a sibling service\r\nconsuming the same spec) can generate both sides at once:\r\n\r\n```xml\r\n\u003cframework\u003equarkus\u003c/framework\u003e\r\n\u003cgenerate\u003econtroller,client\u003c/generate\u003e\r\n```\r\n\r\n`generateResponseParameter` applies per interface kind: it affects `controller`\r\n(response parameter / `Response` return) and `client` (methods return the HTTP\r\nenvelope — `ResponseEntity\u003cT\u003e` for Spring, `Response` for Quarkus — so callers can\r\ninspect status and headers), and never affects `api`. Server-only constructs\r\n(`HttpServletResponse`, `@Context ContainerRequestContext`, `x-include-request`)\r\nare omitted from `api` and `client` interfaces.\r\n\r\nIn code, use `GeneratorParams.rootPackage(...).generate(Role.CONTROLLER, Role.CLIENT)`.\r\n\r\n## Java DTO styles (`javaDtoStyle`)\r\n\r\nFor Java output, `javaDtoStyle` selects the shape of the generated DTO classes.\r\nIt applies to Java only — Kotlin always generates `data class`es. The choice\r\nnever changes the JSON wire format: all three styles serialize and deserialize\r\nthe same JSON for the same specification.\r\n\r\n| Style | What is generated | Notes |\r\n|---|---|---|\r\n| `lombok` (default) | Lombok `@Data` classes | Requires Lombok on the classpath. |\r\n| `pojo` | Plain classes with explicit getters/setters plus `equals`/`hashCode`/`toString` | No Lombok dependency. Value semantics match `@Data` (own fields only). |\r\n| `records` | Java records | Immutable; requires Java 17+. Record-style `name()` accessors (not `getName()`). |\r\n\r\nIn code:\r\n\r\n```kotlin\r\nimport ru.curs.hurdygurdy.JavaDtoStyle\r\n\r\nval codegen = JavaCodegen(\r\n    GeneratorParams.rootPackage(\"com.example.project\")\r\n        .javaDtoStyle(JavaDtoStyle.RECORDS)\r\n)\r\n```\r\n\r\n### How the styles model polymorphism and inheritance\r\n\r\n`lombok` and `pojo` use ordinary Java classes and behave identically in shape:\r\n\r\n- **`allOf` inheritance** → the subtype `extends` the base class.\r\n- **`discriminator`** → the base carries `@JsonTypeInfo(use = NAME)` +\r\n  `@JsonSubTypes`; subtypes `extend` it. When the schema declares no explicit\r\n  `discriminator.mapping`, the `@JsonSubTypes` names are derived from the subtype\r\n  schema names (the OpenAPI implicit convention), so deserialization works\r\n  without a hand-written mapping.\r\n- **`oneOf`** and a top-level **`anyOf`** of two or more `$ref`s → an interface\r\n  carrying `@JsonTypeInfo(use = DEDUCTION)` + `@JsonSubTypes`; the member classes\r\n  `implement` it.\r\n\r\n`records` cannot use class inheritance (a Java record is `final` and cannot\r\n`extend`), so is-a relationships are expressed through interfaces:\r\n\r\n- **`discriminator`, `oneOf`, top-level `anyOf`** bases become `sealed interface`s\r\n  that `permit` their subtypes; each concrete subtype is a `record` that\r\n  `implements` the base (and any interface it participates in — a type can\r\n  `implement` several).\r\n- **`allOf`-inherited properties** are **flattened** into the subtype record's\r\n  components (records inherit no fields). A plain `allOf` base (no\r\n  `discriminator`/`oneOf`) stays its own record and is still instantiable; the\r\n  subtype simply repeats its components — the JSON is identical.\r\n- **Required** components are validated in a compact constructor\r\n  (`Objects.requireNonNull`), so a missing required value fails fast.\r\n- **`additionalProperties`** become a trailing `Map` component annotated\r\n  `@JsonAnySetter`/`@JsonAnyGetter`.\r\n- A `nullable` self-reference and self-referential (recursive) schemas are\r\n  supported (a record may reference its own type as a component).\r\n\r\n## Quarkus (Jakarta REST) output\r\n\r\nSet `framework` to `quarkus` (Maven `\u003cframework\u003equarkus\u003c/framework\u003e`, or\r\n`GeneratorParams.rootPackage(...).framework(Framework.QUARKUS)` in code) to\r\ngenerate Jakarta REST interfaces instead of Spring MVC ones. DTO classes are\r\nidentical in both modes.\r\n\r\nAnnotation mapping:\r\n\r\n| Concern | Spring | Quarkus (JAX-RS) |\r\n|---------|--------|------------------|\r\n| Interface | *(none)* | `@Path(\"\")` |\r\n| HTTP method | `@GetMapping(value, produces, consumes)` | `@GET` + `@Path(path)` + `@Produces` + `@Consumes` |\r\n| Path parameter | `@PathVariable` | `@PathParam` |\r\n| Query parameter | `@RequestParam` | `@QueryParam` (+ `@DefaultValue`) |\r\n| Header parameter | `@RequestHeader` | `@HeaderParam` |\r\n| Request body | `@RequestBody` | *(unannotated parameter)* |\r\n| Multipart part | `@RequestPart` | `@RestForm` |\r\n\r\n`@Produces` is emitted only when the operation defines a success (2xx) response\r\nmedia type, and `@Consumes` only when the request body defines a media type.\r\n\r\n`generateResponseParameter` has no servlet analog in Quarkus. When enabled, the\r\ngenerated `Controller` method returns `jakarta.ws.rs.core.Response` (instead of the\r\nDTO), and a Javadoc/KDoc `@return` line documents the entity type the `Response` is\r\nexpected to carry. When `generateResponseParameter` is true and the\r\n`x-include-request: true` operation extension is present, the generated `Controller`\r\nmethod also gains a `@Context jakarta.ws.rs.container.ContainerRequestContext\r\nrequestContext` parameter (the Quarkus analog of the Spring `HttpServletRequest`\r\nbehavior).\r\n\r\nThe generated Quarkus code requires `jakarta.ws.rs-api` on the consuming project's\r\nclasspath. For multipart endpoints, it also requires `org.jboss.resteasy.reactive.RestForm`\r\nand `org.jboss.resteasy.reactive.multipart.FileUpload` — both provided by the\r\nQuarkus REST extension.\r\n\r\n## Client generation (`generate=client`)\r\n\r\nWith `client` in the `generate` set, the emitted `XxxClient` interfaces are meant to be\r\n*called*, not implemented — the framework supplies the implementation.\r\n\r\n- **Spring** (`framework=spring`): [Spring 6 HTTP Interface](https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface).\r\n  Methods carry `@GetExchange`/`@PostExchange`/`@PutExchange`/`@PatchExchange`/`@DeleteExchange`;\r\n  parameters keep the same `@PathVariable`/`@RequestParam`/`@RequestHeader`/`@RequestBody`/`@RequestPart`\r\n  annotations. Create a proxy with `HttpServiceProxyFactory`.\r\n- **Quarkus** (`framework=quarkus`): the interface is additionally annotated `@RegisterRestClient`; inject it\r\n  with `@RestClient`. No implementation is written. See the [Quarkus REST Client guide](https://quarkus.io/guides/rest-client).\r\n\r\n`generateResponseParameter=true` makes client methods return the HTTP envelope so callers can inspect\r\nstatus/headers: `ResponseEntity\u003cT\u003e` (Spring) / `jakarta.ws.rs.core.Response` (Quarkus). With\r\n`generateResponseParameter=false` they return the deserialized DTO. Server-only constructs\r\n(`HttpServletResponse`, `@Context ContainerRequestContext`, `x-include-request`) are omitted from client\r\ninterfaces.\r\n\r\n## Inheritance hierarchy compatible with openapi-codegen\r\n\r\n```yaml\r\ncomponents:\r\n  schemas:\r\n    #---------------------------------------------------------------------------\r\n    # Abstract class with discriminator 'vehicle_type'\r\n    #---------------------------------------------------------------------------\r\n    'Vehicle':\r\n      type: object\r\n      nullable: false\r\n      properties:\r\n        'vehicle_type':\r\n          type: string\r\n      discriminator:\r\n        propertyName: vehicle_type\r\n        mapping:\r\n          'CAR': '#/components/schemas/Car'\r\n          'TRUCK': '#/components/schemas/Truck'\r\n    #---------------------------------------------------------------------------\r\n    # Concrete classes\r\n    #---------------------------------------------------------------------------\r\n    'Car':\r\n      nullable: false\r\n      allOf:\r\n        - $ref: \"#/components/schemas/Vehicle\"\r\n        - type: object\r\n          properties:\r\n            'car_property':\r\n              type: string\r\n    'Truck':\r\n      nullable: false\r\n      allOf:\r\n        - $ref: \"#/components/schemas/Vehicle\"\r\n        - type: object\r\n          properties:\r\n            'truck_property':\r\n              type: string\r\n```\r\n\r\nThis will produce the following in Java:\r\n```java\r\n//Vehicle.java\r\n@Data\r\n@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)\r\n@JsonTypeInfo(\r\n        use = JsonTypeInfo.Id.NAME,\r\n        include = JsonTypeInfo.As.PROPERTY,\r\n        property = \"vehicle_type\"\r\n)\r\n@JsonSubTypes({\r\n        @JsonSubTypes.Type(value = Car.class, name = \"CAR\"),\r\n        @JsonSubTypes.Type(value = Truck.class, name = \"TRUCK\")})\r\npublic class Vehicle {\r\n}\r\n\r\n//Car.java\r\n@Data\r\n@EqualsAndHashCode(callSuper = true)\r\n@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)\r\npublic class Car extends Vehicle {\r\n    private String carProperty;\r\n}\r\n```\r\n\r\nThe `@EqualsAndHashCode(callSuper = true)` is emitted on subtypes only (a class that\r\n`extends` a generated parent), so inherited fields participate in `equals`/`hashCode`;\r\nbase and standalone classes keep a plain `@Data`.\r\n\r\nWith `javaDtoStyle=pojo` the same schema produces plain classes (no Lombok) with\r\nexplicit accessors and value methods:\r\n\r\n```java\r\n//Car.java\r\n@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)\r\npublic class Car extends Vehicle {\r\n    private String carProperty;\r\n\r\n    public String getCarProperty() {\r\n        return this.carProperty;\r\n    }\r\n\r\n    public void setCarProperty(String carProperty) {\r\n        this.carProperty = carProperty;\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object o) {\r\n        if (this == o) return true;\r\n        if (o == null || getClass() != o.getClass()) return false;\r\n        if (!super.equals(o)) return false;\r\n        Car that = (Car) o;\r\n        return Objects.equals(carProperty, that.carProperty);\r\n    }\r\n\r\n    @Override\r\n    public int hashCode() {\r\n        return Objects.hash(super.hashCode(), carProperty);\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        return \"Car{\" + \"carProperty=\" + carProperty + \"}\";\r\n    }\r\n}\r\n```\r\n\r\nWith `javaDtoStyle=records` the discriminator base becomes a `sealed interface`\r\nand each subtype a `record`, with inherited properties flattened into the\r\ncomponents and required ones null-checked in a compact constructor:\r\n\r\n```java\r\n//Vehicle.java\r\n@JsonTypeInfo(\r\n    use = JsonTypeInfo.Id.NAME,\r\n    include = JsonTypeInfo.As.PROPERTY,\r\n    property = \"vehicle_type\"\r\n)\r\n@JsonSubTypes({\r\n    @JsonSubTypes.Type(value = Car.class, name = \"CAR\"),\r\n    @JsonSubTypes.Type(value = Truck.class, name = \"TRUCK\")})\r\npublic sealed interface Vehicle permits Car, Truck {\r\n}\r\n\r\n//Car.java\r\n@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)\r\npublic record Car(String carProperty) implements Vehicle {\r\n}\r\n```\r\n\r\nThis will produce the following in Kotlin:\r\n\r\n```kotlin\r\n//Vehicle.kt\r\n@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy::class)\r\n@JsonTypeInfo(\r\n  use = JsonTypeInfo.Id.NAME,\r\n  include = JsonTypeInfo.As.PROPERTY,\r\n  property = \"vehicle_type\"\r\n)\r\n@JsonSubTypes(JsonSubTypes.Type(value = Car::class, name = \"CAR\"),\r\nJsonSubTypes.Type(value = Truck::class, name = \"TRUCK\"))\r\npublic sealed class Vehicle()\r\n\r\n//Car.kt\r\n@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy::class)\r\npublic data class Car(\r\n    public val carProperty: String? = null\r\n) : Vehicle()\r\n```\r\n\r\n## Make DTO classes implement interfaces\r\n\r\nYou can use `x-extends` [extended property](https://swagger.io/docs/specification/openapi-extensions/) on schema element in order to make DTO implement given interface or interfaces: \r\n\r\n```yaml\r\ncomponents:\r\n  schemas:\r\n    MenuItemDTO:\r\n      type: object\r\n      nullable: false\r\n      x-extends:\r\n        - java.lang.Serializable\r\n      title: MenuItemDTO\r\n      properties:\r\n        [....]\r\n```\r\n## References to external specifications\r\nYou can use references to external specification files if they are available on the same file system as the original one. However, hurdy-gurdy does not attempt to generate code for referenced specifications: we believe this should be done explicitly for every spec. Hurdy-gurdy just uses `x-package` extension property on the referenced specification in order to define the location of referenced DTOs.\r\n\r\nFor example, given the following spec fragment:\r\n\r\n```yaml\r\n  /api/v1/external:\r\n    get:\r\n      operationId: external\r\n      responses:\r\n        \"200\":\r\n          description: external file\r\n          content:\r\n            text/csv:\r\n              schema:\r\n                $ref: 'externalfile.yaml#/components/schemas/DatabaseConnectionRequest'\r\n```\r\n\r\nThe `externalfile.yaml` file should be located in the same folder and it should contain `x-package` property:\r\n\r\n```yaml\r\nopenapi: 3.0.1\r\ninfo:\r\npaths:\r\nx-package: com.example\r\n```\r\nThen code generator will suggest that `com.example.dto.DatabaseConnectionRequest` class exists on the classpath.\r\n\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcourseorchestra%2Fhurdy-gurdy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcourseorchestra%2Fhurdy-gurdy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcourseorchestra%2Fhurdy-gurdy/lists"}