From 187fb6018d378932b12b136dfe01b4de43213a7d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:04:04 +0300 Subject: [PATCH 1/9] Developer guide: show what the protocol generators actually emit The three generate-* appendices each stop at a colon where a listing should be: "The @GraphQLClient interface looks like:", "Call sites use the static factory:", "A subscription returns a GraphQLSubscription handle whose cancel() ends the stream:". Six of them. Rather than hand-writing something that looks like generated code, this runs the generators. GenerateGraphQLMojo.Generator, GenerateGrpcMojo. Generator and GenerateOpenApiMojo.Generator were driven against the exact schema, proto and spec the chapters describe -- the same fixtures their unit tests use -- and the output committed under docs/demos/common, with only tag markers added. So the listings are the generators' answer, not a plausible reconstruction of it, and a change in emission shape shows up here as a diff. That also settles a claim that was wrong: the openapi chapter says models are emitted as "@Mapped record (Java 17+) or class (Java 8)", and the Pet.java it pointed at was a PropertyBusinessObject. The real one is a record. PetApi.java was close but not what the generator writes either. Both are now the generator's output. The call sites are hand-written, because a generator does not emit those. Each carries the one thing its response type gets wrong by default: gRPC- Web reports failure under HTTP 200 so the status is the thing to read, and a GraphQL response can carry data and errors at once so isOk() is about the errors array rather than about getData(). Ratchet drops from 34 to 28. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/example/hello/GreeterCallSite.java | 27 ++++++++++ .../java/com/example/hello/GreeterGrpc.java | 22 ++++++++ .../java/com/example/hello/HelloReply.java | 12 +++++ .../java/com/example/hello/HelloRequest.java | 13 +++++ .../src/main/java/com/example/hello/Mood.java | 21 ++++++++ .../java/com/example/petstore/PetApi.java | 14 ++--- .../com/example/petstore/PetApiCallSite.java | 24 +++++++++ .../java/com/example/petstore/model/Pet.java | 18 ++----- .../com/example/starwars/AddReviewData.java | 10 ++++ .../starwars/AddReviewData_CreateReview.java | 11 ++++ .../java/com/example/starwars/ColorInput.java | 12 +++++ .../java/com/example/starwars/Episode.java | 8 +++ .../com/example/starwars/HeroNameData.java | 10 ++++ .../example/starwars/HeroNameData_Hero.java | 12 +++++ .../starwars/HeroNameData_Hero_Friends.java | 10 ++++ .../com/example/starwars/OnReviewData.java | 10 ++++ .../starwars/OnReviewData_ReviewAdded.java | 10 ++++ .../com/example/starwars/ReviewInput.java | 12 +++++ .../com/example/starwars/StarWarsApi.java | 32 ++++++++++++ .../example/starwars/StarWarsCallSite.java | 52 +++++++++++++++++++ .../appendix_goal_generate_graphql.adoc | 15 ++++++ .../appendix_goal_generate_grpc.adoc | 10 ++++ .../appendix_goal_generate_openapi.adoc | 5 ++ .../missing-code-blocks-baseline.txt | 6 --- 24 files changed, 351 insertions(+), 25 deletions(-) create mode 100644 docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java create mode 100644 docs/demos/common/src/main/java/com/example/hello/GreeterGrpc.java create mode 100644 docs/demos/common/src/main/java/com/example/hello/HelloReply.java create mode 100644 docs/demos/common/src/main/java/com/example/hello/HelloRequest.java create mode 100644 docs/demos/common/src/main/java/com/example/hello/Mood.java create mode 100644 docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/AddReviewData.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/AddReviewData_CreateReview.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/ColorInput.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/Episode.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/HeroNameData.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero_Friends.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/OnReviewData.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/OnReviewData_ReviewAdded.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/ReviewInput.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/StarWarsApi.java create mode 100644 docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java diff --git a/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java b/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java new file mode 100644 index 00000000000..f05ef42f5ce --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java @@ -0,0 +1,27 @@ +package com.example.hello; + +import com.codename1.components.ToastBar; +import java.util.Arrays; + +/// Call site for the `@GrpcClient` interface `cn1:generate-grpc` emits. +/// Included by the developer guide's `generate-grpc` appendix. +class GreeterCallSite { + + // tag::appendix-goal-generate-grpc-java-002[] + void sayHello(String bearerToken) { + GreeterGrpc greeter = GreeterGrpc.of("https://grpc.example.com"); + HelloRequest request = new HelloRequest("Ada", Arrays.asList("ada", "countess"), Mood.HAPPY); + + greeter.sayHello(request, bearerToken, response -> { + if (response.isOk()) { + ToastBar.showInfoMessage(response.getResponseData().message()); + } else { + // getResponseCode() is the gRPC status, not the HTTP one -- + // a gRPC-Web call can carry a failure under HTTP 200. + ToastBar.showErrorMessage("gRPC status " + response.getResponseCode() + + ": " + response.getResponseErrorMessage()); + } + }); + } + // end::appendix-goal-generate-grpc-java-002[] +} diff --git a/docs/demos/common/src/main/java/com/example/hello/GreeterGrpc.java b/docs/demos/common/src/main/java/com/example/hello/GreeterGrpc.java new file mode 100644 index 00000000000..8fcdf54a0cf --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/hello/GreeterGrpc.java @@ -0,0 +1,22 @@ +// Generated by cn1:generate-grpc. +package com.example.hello; + +import com.codename1.annotations.grpc.GrpcClient; +import com.codename1.annotations.grpc.Rpc; +import com.codename1.annotations.rest.Header; +import com.codename1.io.grpc.GrpcClients; +import com.codename1.io.grpc.GrpcResponse; +import com.codename1.util.OnComplete; + +// tag::appendix-goal-generate-grpc-java-001[] +@GrpcClient("helloworld.Greeter") +public interface GreeterGrpc { + + @Rpc("SayHello") + void sayHello(com.example.hello.HelloRequest request, @Header("Authorization") String bearerToken, OnComplete> callback); + + static GreeterGrpc of(String baseUrl) { + return GrpcClients.create(GreeterGrpc.class, baseUrl); + } +} +// end::appendix-goal-generate-grpc-java-001[] diff --git a/docs/demos/common/src/main/java/com/example/hello/HelloReply.java b/docs/demos/common/src/main/java/com/example/hello/HelloReply.java new file mode 100644 index 00000000000..007139f09dc --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/hello/HelloReply.java @@ -0,0 +1,12 @@ +// Generated by cn1:generate-grpc. +package com.example.hello; + +import com.codename1.annotations.grpc.ProtoField; +import com.codename1.annotations.grpc.ProtoMessage; + +@ProtoMessage +public record HelloReply( + @ProtoField(tag = 1) String message, + @ProtoField(tag = 2, wireType = ProtoField.WireKind.SINT, name = "emoji_code") int emojiCode, + @ProtoField(tag = 3) com.example.hello.HelloRequest echo +) {} diff --git a/docs/demos/common/src/main/java/com/example/hello/HelloRequest.java b/docs/demos/common/src/main/java/com/example/hello/HelloRequest.java new file mode 100644 index 00000000000..84641d72ce9 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/hello/HelloRequest.java @@ -0,0 +1,13 @@ +// Generated by cn1:generate-grpc. +package com.example.hello; + +import com.codename1.annotations.grpc.ProtoField; +import com.codename1.annotations.grpc.ProtoMessage; +import java.util.List; + +@ProtoMessage +public record HelloRequest( + @ProtoField(tag = 1) String name, + @ProtoField(tag = 2) List aliases, + @ProtoField(tag = 3) com.example.hello.Mood mood +) {} diff --git a/docs/demos/common/src/main/java/com/example/hello/Mood.java b/docs/demos/common/src/main/java/com/example/hello/Mood.java new file mode 100644 index 00000000000..d56efbbd82c --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/hello/Mood.java @@ -0,0 +1,21 @@ +// Generated by cn1:generate-grpc. +package com.example.hello; + +import com.codename1.annotations.grpc.ProtoEnum; + +@ProtoEnum +public enum Mood { + UNKNOWN(0), + HAPPY(1), + SAD(2); + + public final int number; + Mood(int n) { this.number = n; } + + public static Mood forNumber(int n) { + for (Mood v : values()) { + if (v.number == n) return v; + } + return null; + } +} diff --git a/docs/demos/common/src/main/java/com/example/petstore/PetApi.java b/docs/demos/common/src/main/java/com/example/petstore/PetApi.java index 45e66f18216..4a8dc73081b 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/PetApi.java +++ b/docs/demos/common/src/main/java/com/example/petstore/PetApi.java @@ -1,10 +1,16 @@ +// Generated by cn1:generate-openapi. package com.example.petstore; import com.codename1.annotations.rest.Body; +import com.codename1.annotations.rest.Cookie; +import com.codename1.annotations.rest.DELETE; import com.codename1.annotations.rest.GET; import com.codename1.annotations.rest.Header; +import com.codename1.annotations.rest.PATCH; import com.codename1.annotations.rest.POST; +import com.codename1.annotations.rest.PUT; import com.codename1.annotations.rest.Path; +import com.codename1.annotations.rest.Query; import com.codename1.annotations.rest.RestClient; import com.codename1.io.rest.Response; import com.codename1.io.rest.RestClients; @@ -15,14 +21,10 @@ public interface PetApi { @GET("/pet/{petId}") - void getPetById(@Path("petId") Long petId, - @Header("Authorization") String bearerToken, - OnComplete> callback); + void getPetById(@Path("petId") Long petId, @Header("Authorization") String bearerToken, OnComplete> callback); @POST("/pet") - void addPet(@Body com.example.petstore.model.Pet body, - @Header("Authorization") String bearerToken, - OnComplete> callback); + void addPet(@Body com.example.petstore.model.Pet body, @Header("Authorization") String bearerToken, OnComplete> callback); static PetApi of(String baseUrl) { return RestClients.create(PetApi.class, baseUrl); diff --git a/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java b/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java new file mode 100644 index 00000000000..9b58f75f457 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java @@ -0,0 +1,24 @@ +package com.example.petstore; + +import com.codename1.components.ToastBar; +import com.example.petstore.model.Pet; + +/// Call site for the `@RestClient` interface `cn1:generate-openapi` emits. +/// Included by the developer guide's `generate-openapi` appendix. +class PetApiCallSite { + + // tag::appendix-goal-generate-openapi-java-003[] + void loadPet(String bearerToken) { + PetApi api = PetApi.of("https://petstore3.swagger.io/api/v3"); + + api.getPetById(10L, bearerToken, response -> { + if (response.getResponseCode() == 200) { + Pet pet = response.getResponseData(); + ToastBar.showInfoMessage(pet.name()); + } else { + ToastBar.showErrorMessage(response.getResponseErrorMessage()); + } + }); + } + // end::appendix-goal-generate-openapi-java-003[] +} diff --git a/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java b/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java index 98ac91c5ad8..c16cc8bff0f 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java +++ b/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java @@ -1,18 +1,10 @@ +// Generated by cn1:generate-openapi. package com.example.petstore.model; -import com.codename1.properties.Property; -import com.codename1.properties.PropertyBusinessObject; -import com.codename1.properties.PropertyIndex; +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; // tag::appendix-goal-generate-openapi-java-002[] -public class Pet implements PropertyBusinessObject { - public final Property id = new Property("id"); - public final Property name = new Property("name"); - private final PropertyIndex index = new PropertyIndex(this, "Pet", id, name); - - @Override - public PropertyIndex getPropertyIndex() { - return index; - } -} +@Mapped +public record Pet(@JsonProperty("id") Long id, @JsonProperty("name") String name) {} // end::appendix-goal-generate-openapi-java-002[] diff --git a/docs/demos/common/src/main/java/com/example/starwars/AddReviewData.java b/docs/demos/common/src/main/java/com/example/starwars/AddReviewData.java new file mode 100644 index 00000000000..e8deb5177bd --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/AddReviewData.java @@ -0,0 +1,10 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record AddReviewData( + AddReviewData_CreateReview createReview +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/AddReviewData_CreateReview.java b/docs/demos/common/src/main/java/com/example/starwars/AddReviewData_CreateReview.java new file mode 100644 index 00000000000..adbcc6ce575 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/AddReviewData_CreateReview.java @@ -0,0 +1,11 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record AddReviewData_CreateReview( + Integer stars, + String commentary +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/ColorInput.java b/docs/demos/common/src/main/java/com/example/starwars/ColorInput.java new file mode 100644 index 00000000000..c0384894d4a --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/ColorInput.java @@ -0,0 +1,12 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record ColorInput( + Integer red, + Integer green, + Integer blue +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/Episode.java b/docs/demos/common/src/main/java/com/example/starwars/Episode.java new file mode 100644 index 00000000000..f98ab40fea5 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/Episode.java @@ -0,0 +1,8 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +public enum Episode { + NEWHOPE, + EMPIRE, + JEDI +} diff --git a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData.java b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData.java new file mode 100644 index 00000000000..cb717e57255 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData.java @@ -0,0 +1,10 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record HeroNameData( + HeroNameData_Hero hero +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero.java b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero.java new file mode 100644 index 00000000000..04b197bba44 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero.java @@ -0,0 +1,12 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; +import java.util.List; + +@Mapped +public record HeroNameData_Hero( + String name, + List friends +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero_Friends.java b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero_Friends.java new file mode 100644 index 00000000000..0ad5b810363 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero_Friends.java @@ -0,0 +1,10 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record HeroNameData_Hero_Friends( + String name +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/OnReviewData.java b/docs/demos/common/src/main/java/com/example/starwars/OnReviewData.java new file mode 100644 index 00000000000..36a26cef21b --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/OnReviewData.java @@ -0,0 +1,10 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record OnReviewData( + OnReviewData_ReviewAdded reviewAdded +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/OnReviewData_ReviewAdded.java b/docs/demos/common/src/main/java/com/example/starwars/OnReviewData_ReviewAdded.java new file mode 100644 index 00000000000..fab4c087502 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/OnReviewData_ReviewAdded.java @@ -0,0 +1,10 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record OnReviewData_ReviewAdded( + Integer stars +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/ReviewInput.java b/docs/demos/common/src/main/java/com/example/starwars/ReviewInput.java new file mode 100644 index 00000000000..1e1021c07e2 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/ReviewInput.java @@ -0,0 +1,12 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record ReviewInput( + Integer stars, + String commentary, + ColorInput favoriteColor +) {} diff --git a/docs/demos/common/src/main/java/com/example/starwars/StarWarsApi.java b/docs/demos/common/src/main/java/com/example/starwars/StarWarsApi.java new file mode 100644 index 00000000000..2bfdb318130 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/StarWarsApi.java @@ -0,0 +1,32 @@ +// Generated by cn1:generate-graphql. +package com.example.starwars; + +import com.codename1.annotations.graphql.GraphQLClient; +import com.codename1.annotations.graphql.Query; +import com.codename1.annotations.graphql.Mutation; +import com.codename1.annotations.graphql.Subscription; +import com.codename1.annotations.graphql.Var; +import com.codename1.annotations.rest.Header; +import com.codename1.io.graphql.GraphQLClients; +import com.codename1.io.graphql.GraphQLResponse; +import com.codename1.io.graphql.GraphQLSubscription; +import com.codename1.util.OnComplete; + +// tag::appendix-goal-generate-graphql-java-001[] +@GraphQLClient("https://example.com/graphql") +public interface StarWarsApi { + + @Query(value = "query HeroName($episode: Episode) { hero(episode: $episode) { ...HeroFields friends { name } } } fragment HeroFields on Character { name }", operationName = "HeroName") + void heroName(@Var("episode") Episode episode, @Header("Authorization") String bearerToken, OnComplete> callback); + + @Mutation(value = "mutation AddReview($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }", operationName = "AddReview") + void addReview(@Var("ep") Episode ep, @Var("review") ReviewInput review, @Header("Authorization") String bearerToken, OnComplete> callback); + + @Subscription(value = "subscription OnReview($ep: Episode!) { reviewAdded(episode: $ep) { stars } }", operationName = "OnReview") + GraphQLSubscription onReview(@Var("ep") Episode ep, @Header("Authorization") String bearerToken, GraphQLSubscription.Handler handler); + + static StarWarsApi of(String endpoint) { + return GraphQLClients.create(StarWarsApi.class, endpoint); + } +} +// end::appendix-goal-generate-graphql-java-001[] diff --git a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java new file mode 100644 index 00000000000..54297755da9 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java @@ -0,0 +1,52 @@ +package com.example.starwars; + +import com.codename1.components.ToastBar; +import com.codename1.io.graphql.GraphQLResponse; +import com.codename1.io.graphql.GraphQLSubscription; + +/// Call sites for the `@GraphQLClient` interface `cn1:generate-graphql` emits. +/// Included by the developer guide's `generate-graphql` appendix. +class StarWarsCallSite { + + // tag::appendix-goal-generate-graphql-java-002[] + void heroName(String bearerToken) { + StarWarsApi api = StarWarsApi.of("https://swapi.example.com/graphql"); + + api.heroName(Episode.EMPIRE, bearerToken, response -> { + // A GraphQL response can carry data and errors at once, so isOk() + // reports whether the errors array came back empty rather than + // whether there is anything in getData(). + if (response.isOk()) { + ToastBar.showInfoMessage(response.getData().hero().name()); + } else { + ToastBar.showErrorMessage(response.getResponseErrorMessage()); + } + }); + } + // end::appendix-goal-generate-graphql-java-002[] + + // tag::appendix-goal-generate-graphql-java-003[] + GraphQLSubscription watchReviews(String bearerToken) { + StarWarsApi api = StarWarsApi.of("https://swapi.example.com/graphql"); + + return api.onReview(Episode.JEDI, bearerToken, + new GraphQLSubscription.Handler() { + @Override + public void onNext(GraphQLResponse response) { + ToastBar.showInfoMessage(response.getData().reviewAdded().stars() + " stars"); + } + + @Override + public void onError(GraphQLResponse response) { + ToastBar.showErrorMessage(response.getResponseErrorMessage()); + } + + @Override + public void onComplete() { + } + }); + // Hold on to the handle and call cancel() to end the stream -- + // leaving the form is the usual place to do it. + } + // end::appendix-goal-generate-graphql-java-003[] +} diff --git a/docs/developer-guide/appendix_goal_generate_graphql.adoc b/docs/developer-guide/appendix_goal_generate_graphql.adoc index e4d1f4f7eaf..e9fc80b37d2 100644 --- a/docs/developer-guide/appendix_goal_generate_graphql.adoc +++ b/docs/developer-guide/appendix_goal_generate_graphql.adoc @@ -113,14 +113,29 @@ their `name()`. Built-in scalars map to their boxed Java type The `@GraphQLClient` interface looks like: +[source,java] +---- +include::../demos/common/src/main/java/com/example/starwars/StarWarsApi.java[tag=appendix-goal-generate-graphql-java-001,indent=0] +---- + Call sites use the static factory. Queries and mutations report through an `OnComplete>`: +[source,java] +---- +include::../demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java[tag=appendix-goal-generate-graphql-java-002,indent=0] +---- + A subscription returns a `GraphQLSubscription` handle whose `cancel()` ends the stream: +[source,java] +---- +include::../demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java[tag=appendix-goal-generate-graphql-java-003,indent=0] +---- + The `Impl` class that performs the request lives in `target/generated-sources` -- the project source never references it diff --git a/docs/developer-guide/appendix_goal_generate_grpc.adoc b/docs/developer-guide/appendix_goal_generate_grpc.adoc index 0c42eeb74bc..23ca53c9950 100644 --- a/docs/developer-guide/appendix_goal_generate_grpc.adoc +++ b/docs/developer-guide/appendix_goal_generate_grpc.adoc @@ -74,9 +74,19 @@ optional `name` attribute so introspection tooling can recover it. The `@GrpcClient` interface looks like: +[source,java] +---- +include::../demos/common/src/main/java/com/example/hello/GreeterGrpc.java[tag=appendix-goal-generate-grpc-java-001,indent=0] +---- + Call sites use the static factory: +[source,java] +---- +include::../demos/common/src/main/java/com/example/hello/GreeterCallSite.java[tag=appendix-goal-generate-grpc-java-002,indent=0] +---- + The `GrpcImpl` class that actually performs the gRPC-Web POST lives in `target/generated-sources` -- the project source diff --git a/docs/developer-guide/appendix_goal_generate_openapi.adoc b/docs/developer-guide/appendix_goal_generate_openapi.adoc index 174a44d87a7..92f95567a6c 100644 --- a/docs/developer-guide/appendix_goal_generate_openapi.adoc +++ b/docs/developer-guide/appendix_goal_generate_openapi.adoc @@ -91,6 +91,11 @@ collisions between an API class name and a same-named model. Call sites use the static factory: +[source,java] +---- +include::../demos/common/src/main/java/com/example/petstore/PetApiCallSite.java[tag=appendix-goal-generate-openapi-java-003,indent=0] +---- + The `ApiImpl` class that actually performs the HTTP call lives in `target/generated-sources` -- the project source never references diff --git a/scripts/developer-guide/missing-code-blocks-baseline.txt b/scripts/developer-guide/missing-code-blocks-baseline.txt index 2d64765057a..9f552230ed0 100644 --- a/scripts/developer-guide/missing-code-blocks-baseline.txt +++ b/scripts/developer-guide/missing-code-blocks-baseline.txt @@ -20,12 +20,6 @@ Monetization.asciidoc You are now ready to see the full magic of the `validateAn SVG-Transcoder.asciidoc the generated class directly: The-Components-Of-Codename-One.asciidoc Call the builder from a Maven plugin, an Ant task or a one-shot `main`: The-Components-Of-Codename-One.asciidoc This code should output "The result was 7" to the console. It's fully asynchronous, so you can include this code anywhere without worrying about it "bogging down" your code. The full signature of this form of the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html#execute(java.lang.String,com.codename1.util.SuccessCallback)[execute()] method is: -appendix_goal_generate_graphql.adoc The `@GraphQLClient` interface looks like: -appendix_goal_generate_graphql.adoc an `OnComplete>`: -appendix_goal_generate_graphql.adoc ends the stream: -appendix_goal_generate_grpc.adoc Call sites use the static factory: -appendix_goal_generate_grpc.adoc The `@GrpcClient` interface looks like: -appendix_goal_generate_openapi.adoc Call sites use the static factory: io.asciidoc In the above code you do the following: io.asciidoc Since you assume most developers reading this will be familiar with Java here is the way to implement the multipart upload in the servlet API: io.asciidoc That's what the new method of `URLImage` does: From edc5268304d01f67b62418e0393b47ce0f7f9e3c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:08:28 +0300 Subject: [PATCH 2/9] Developer guide: the protocol call sites must survive a successful empty answer Three of the four listings dereferenced whatever came back. GraphQL is the sharp one, twice. isOk() reports that the errors array came back empty, which is not the same as the query having found anything: the schema declares hero as Character, not Character!, so a clean response can carry a null selection. And on a subscription, onError is the end of the stream -- a per-field failure arrives at onNext instead, as a next payload whose errors array is non-empty and whose data may be partial or absent, which is what GraphQLSubscription.Handler#onNext documents. REST and gRPC get the smaller version of the same: a 2xx with an empty or unmappable body, and an OK status with no message frame, both leave the response data null. Each guard carries the reason, since the trap is in the response type's contract rather than in this code. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/example/hello/GreeterCallSite.java | 4 ++- .../com/example/petstore/PetApiCallSite.java | 11 +++++--- .../example/starwars/StarWarsCallSite.java | 26 ++++++++++++++----- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java b/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java index f05ef42f5ce..dcab03f9ebe 100644 --- a/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java +++ b/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java @@ -13,8 +13,10 @@ void sayHello(String bearerToken) { HelloRequest request = new HelloRequest("Ada", Arrays.asList("ada", "countess"), Mood.HAPPY); greeter.sayHello(request, bearerToken, response -> { - if (response.isOk()) { + if (response.isOk() && response.getResponseData() != null) { ToastBar.showInfoMessage(response.getResponseData().message()); + } else if (response.isOk()) { + ToastBar.showErrorMessage("The server returned an empty reply"); } else { // getResponseCode() is the gRPC status, not the HTTP one -- // a gRPC-Web call can carry a failure under HTTP 200. diff --git a/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java b/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java index 9b58f75f457..d99205dacd7 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java +++ b/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java @@ -12,11 +12,14 @@ void loadPet(String bearerToken) { PetApi api = PetApi.of("https://petstore3.swagger.io/api/v3"); api.getPetById(10L, bearerToken, response -> { - if (response.getResponseCode() == 200) { - Pet pet = response.getResponseData(); - ToastBar.showInfoMessage(pet.name()); - } else { + Pet pet = response.getResponseData(); + if (response.getResponseCode() != 200) { ToastBar.showErrorMessage(response.getResponseErrorMessage()); + } else if (pet == null) { + // A 2xx with an empty or unmappable body leaves this null. + ToastBar.showErrorMessage("The server returned no pet"); + } else { + ToastBar.showInfoMessage(pet.name()); } }); } diff --git a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java index 54297755da9..15698721cec 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java +++ b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java @@ -13,13 +13,16 @@ void heroName(String bearerToken) { StarWarsApi api = StarWarsApi.of("https://swapi.example.com/graphql"); api.heroName(Episode.EMPIRE, bearerToken, response -> { - // A GraphQL response can carry data and errors at once, so isOk() - // reports whether the errors array came back empty rather than - // whether there is anything in getData(). - if (response.isOk()) { - ToastBar.showInfoMessage(response.getData().hero().name()); - } else { + // isOk() reports that the errors array came back empty, which is + // not the same as the query having found anything: the schema + // declares hero as Character, not Character!, so a successful + // response can still carry a null selection. + if (!response.isOk()) { ToastBar.showErrorMessage(response.getResponseErrorMessage()); + } else if (response.getData() == null || response.getData().hero() == null) { + ToastBar.showInfoMessage("No hero for that episode"); + } else { + ToastBar.showInfoMessage(response.getData().hero().name()); } }); } @@ -33,7 +36,16 @@ GraphQLSubscription watchReviews(String bearerToken) { new GraphQLSubscription.Handler() { @Override public void onNext(GraphQLResponse response) { - ToastBar.showInfoMessage(response.getData().reviewAdded().stars() + " stars"); + // onError is for the end of the stream. A per-field failure + // arrives here instead, as a next payload whose errors array + // is non-empty and whose data may be partial or absent. + if (response.hasErrors()) { + ToastBar.showErrorMessage(response.getResponseErrorMessage()); + } + OnReviewData data = response.getData(); + if (data != null && data.reviewAdded() != null) { + ToastBar.showInfoMessage(data.reviewAdded().stars() + " stars"); + } } @Override From 5652e0674eb4f168bfaec819406468521473dc27 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:10:18 +0300 Subject: [PATCH 3/9] Developer guide: license headers on the committed generator output check-copyright-headers covers every added source, and the generators do not emit one. The header goes above the "Generated by" line, alongside the tag markers, as the only edits made to the generators' output. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/example/hello/GreeterCallSite.java | 22 +++++++++++++++++++ .../java/com/example/hello/GreeterGrpc.java | 22 +++++++++++++++++++ .../java/com/example/hello/HelloReply.java | 22 +++++++++++++++++++ .../java/com/example/hello/HelloRequest.java | 22 +++++++++++++++++++ .../src/main/java/com/example/hello/Mood.java | 22 +++++++++++++++++++ .../java/com/example/petstore/PetApi.java | 22 +++++++++++++++++++ .../com/example/petstore/PetApiCallSite.java | 22 +++++++++++++++++++ .../java/com/example/petstore/model/Pet.java | 22 +++++++++++++++++++ .../com/example/starwars/AddReviewData.java | 22 +++++++++++++++++++ .../starwars/AddReviewData_CreateReview.java | 22 +++++++++++++++++++ .../java/com/example/starwars/ColorInput.java | 22 +++++++++++++++++++ .../java/com/example/starwars/Episode.java | 22 +++++++++++++++++++ .../com/example/starwars/HeroNameData.java | 22 +++++++++++++++++++ .../example/starwars/HeroNameData_Hero.java | 22 +++++++++++++++++++ .../starwars/HeroNameData_Hero_Friends.java | 22 +++++++++++++++++++ .../com/example/starwars/OnReviewData.java | 22 +++++++++++++++++++ .../starwars/OnReviewData_ReviewAdded.java | 22 +++++++++++++++++++ .../com/example/starwars/ReviewInput.java | 22 +++++++++++++++++++ .../com/example/starwars/StarWarsApi.java | 22 +++++++++++++++++++ .../example/starwars/StarWarsCallSite.java | 22 +++++++++++++++++++ 20 files changed, 440 insertions(+) diff --git a/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java b/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java index dcab03f9ebe..38d3022b858 100644 --- a/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java +++ b/docs/demos/common/src/main/java/com/example/hello/GreeterCallSite.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.example.hello; import com.codename1.components.ToastBar; diff --git a/docs/demos/common/src/main/java/com/example/hello/GreeterGrpc.java b/docs/demos/common/src/main/java/com/example/hello/GreeterGrpc.java index 8fcdf54a0cf..17423ea2a0a 100644 --- a/docs/demos/common/src/main/java/com/example/hello/GreeterGrpc.java +++ b/docs/demos/common/src/main/java/com/example/hello/GreeterGrpc.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-grpc. package com.example.hello; diff --git a/docs/demos/common/src/main/java/com/example/hello/HelloReply.java b/docs/demos/common/src/main/java/com/example/hello/HelloReply.java index 007139f09dc..97c1fc36d08 100644 --- a/docs/demos/common/src/main/java/com/example/hello/HelloReply.java +++ b/docs/demos/common/src/main/java/com/example/hello/HelloReply.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-grpc. package com.example.hello; diff --git a/docs/demos/common/src/main/java/com/example/hello/HelloRequest.java b/docs/demos/common/src/main/java/com/example/hello/HelloRequest.java index 84641d72ce9..32f4d9639f4 100644 --- a/docs/demos/common/src/main/java/com/example/hello/HelloRequest.java +++ b/docs/demos/common/src/main/java/com/example/hello/HelloRequest.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-grpc. package com.example.hello; diff --git a/docs/demos/common/src/main/java/com/example/hello/Mood.java b/docs/demos/common/src/main/java/com/example/hello/Mood.java index d56efbbd82c..55828a15f47 100644 --- a/docs/demos/common/src/main/java/com/example/hello/Mood.java +++ b/docs/demos/common/src/main/java/com/example/hello/Mood.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-grpc. package com.example.hello; diff --git a/docs/demos/common/src/main/java/com/example/petstore/PetApi.java b/docs/demos/common/src/main/java/com/example/petstore/PetApi.java index 4a8dc73081b..0549434d9d0 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/PetApi.java +++ b/docs/demos/common/src/main/java/com/example/petstore/PetApi.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-openapi. package com.example.petstore; diff --git a/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java b/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java index d99205dacd7..3db48614e89 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java +++ b/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.example.petstore; import com.codename1.components.ToastBar; diff --git a/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java b/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java index c16cc8bff0f..d3917293b45 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java +++ b/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-openapi. package com.example.petstore.model; diff --git a/docs/demos/common/src/main/java/com/example/starwars/AddReviewData.java b/docs/demos/common/src/main/java/com/example/starwars/AddReviewData.java index e8deb5177bd..5ebb10e2bd0 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/AddReviewData.java +++ b/docs/demos/common/src/main/java/com/example/starwars/AddReviewData.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/AddReviewData_CreateReview.java b/docs/demos/common/src/main/java/com/example/starwars/AddReviewData_CreateReview.java index adbcc6ce575..fde719bb43c 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/AddReviewData_CreateReview.java +++ b/docs/demos/common/src/main/java/com/example/starwars/AddReviewData_CreateReview.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/ColorInput.java b/docs/demos/common/src/main/java/com/example/starwars/ColorInput.java index c0384894d4a..60473800f05 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/ColorInput.java +++ b/docs/demos/common/src/main/java/com/example/starwars/ColorInput.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/Episode.java b/docs/demos/common/src/main/java/com/example/starwars/Episode.java index f98ab40fea5..93ce646160f 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/Episode.java +++ b/docs/demos/common/src/main/java/com/example/starwars/Episode.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData.java b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData.java index cb717e57255..fcc27ad46dd 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData.java +++ b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero.java b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero.java index 04b197bba44..97e94f16ed3 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero.java +++ b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero_Friends.java b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero_Friends.java index 0ad5b810363..05299a0d771 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero_Friends.java +++ b/docs/demos/common/src/main/java/com/example/starwars/HeroNameData_Hero_Friends.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/OnReviewData.java b/docs/demos/common/src/main/java/com/example/starwars/OnReviewData.java index 36a26cef21b..6d37cc6f156 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/OnReviewData.java +++ b/docs/demos/common/src/main/java/com/example/starwars/OnReviewData.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/OnReviewData_ReviewAdded.java b/docs/demos/common/src/main/java/com/example/starwars/OnReviewData_ReviewAdded.java index fab4c087502..b3aba88f286 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/OnReviewData_ReviewAdded.java +++ b/docs/demos/common/src/main/java/com/example/starwars/OnReviewData_ReviewAdded.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/ReviewInput.java b/docs/demos/common/src/main/java/com/example/starwars/ReviewInput.java index 1e1021c07e2..a0901ee3a07 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/ReviewInput.java +++ b/docs/demos/common/src/main/java/com/example/starwars/ReviewInput.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/StarWarsApi.java b/docs/demos/common/src/main/java/com/example/starwars/StarWarsApi.java index 2bfdb318130..1772da15c93 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/StarWarsApi.java +++ b/docs/demos/common/src/main/java/com/example/starwars/StarWarsApi.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ // Generated by cn1:generate-graphql. package com.example.starwars; diff --git a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java index 15698721cec..11b72bd6e87 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java +++ b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.example.starwars; import com.codename1.components.ToastBar; From dc1bace9ff593bc3d7f27f8b75f0cd23231f07b0 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:15:49 +0300 Subject: [PATCH 4/9] Developer guide: isOk() classifies the payload, not the trip A GraphQL call that never reached the server comes back from GraphQL.postResponse as new GraphQLResponse(0, null, emptyList(), message) -- httpCode 0, no errors, no data -- and decodeJson does the same for an empty or unparseable body. isOk() only asks whether the errors array is empty, so it answers true for every one of those, and the listing reported "No hero for that episode" for an auth failure, a 500 and a dropped connection alike. The query call site now checks the HTTP code first and only then classifies the payload. The reason is beside the check, since it is the response type's contract that makes it necessary. The subscription call site needs no equivalent: deliverNext builds its response with code 200 and a message only from the errors array, and every transport failure goes to onError instead, so hasErrors() is the right discriminator there. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/example/starwars/StarWarsCallSite.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java index 11b72bd6e87..b220d57f9cc 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java +++ b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java @@ -35,13 +35,17 @@ void heroName(String bearerToken) { StarWarsApi api = StarWarsApi.of("https://swapi.example.com/graphql"); api.heroName(Episode.EMPIRE, bearerToken, response -> { - // isOk() reports that the errors array came back empty, which is - // not the same as the query having found anything: the schema - // declares hero as Character, not Character!, so a successful - // response can still carry a null selection. - if (!response.isOk()) { + // isOk() classifies the GraphQL payload, not the trip. A call that + // never reached the server arrives with getResponseCode() == 0, an + // empty errors array and null data -- so isOk() is true. Check the + // HTTP code before believing it. + boolean reachedTheServer = response.getResponseCode() >= 200 + && response.getResponseCode() <= 299; + if (!reachedTheServer || !response.isOk()) { ToastBar.showErrorMessage(response.getResponseErrorMessage()); } else if (response.getData() == null || response.getData().hero() == null) { + // And an error-free answer still need not have found anything: + // the schema declares hero as Character, not Character!. ToastBar.showInfoMessage("No hero for that episode"); } else { ToastBar.showInfoMessage(response.getData().hero().name()); From 89b0c5a4583e8f8e5bf9d08c85bdb526fe7e60f2 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:21:56 +0300 Subject: [PATCH 5/9] Developer guide: the REST callback gets three different things RestClientAnnotationProcessor's generated impl calls back with null when the request never completed, and on a 4xx or 5xx its error handler forwards the raw Response cast to Response. So the one parameter arrives as null, as an error body typed as a Pet, or as a Pet, and the listing read getResponseData() first. The error-status case is the nasty one and it is worth the reader knowing about: reading that payload as a Pet is a cast that does not throw on ParparVM, so on a device it hands String bytes to Pet's field reads rather than raising ClassCastException. Guarded in the order the response has to be classified, with the reason beside it. gRPC and GraphQL need no equivalent: GrpcWeb always answers with a GrpcResponse carrying STATUS_TRANSPORT_FAILURE, and GraphQL.postResponse always answers with a GraphQLResponse. Neither ever passes null, and neither reuses another payload's type. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/example/petstore/PetApiCallSite.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java b/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java index 3db48614e89..d9236dda515 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java +++ b/docs/demos/common/src/main/java/com/example/petstore/PetApiCallSite.java @@ -23,7 +23,6 @@ package com.example.petstore; import com.codename1.components.ToastBar; -import com.example.petstore.model.Pet; /// Call site for the `@RestClient` interface `cn1:generate-openapi` emits. /// Included by the developer guide's `generate-openapi` appendix. @@ -34,14 +33,20 @@ void loadPet(String bearerToken) { PetApi api = PetApi.of("https://petstore3.swagger.io/api/v3"); api.getPetById(10L, bearerToken, response -> { - Pet pet = response.getResponseData(); - if (response.getResponseCode() != 200) { + // Two things have to be settled before the payload is a Pet. + // The generated impl passes null when the request never + // completed, and on an error status it forwards the raw + // Response under this type -- so on that path + // getResponseData() is the error body, not a Pet, and + // touching it as one is a cast that does not throw on iOS. + if (response == null) { + ToastBar.showErrorMessage("The request did not complete"); + } else if (response.getResponseCode() < 200 || response.getResponseCode() > 299) { ToastBar.showErrorMessage(response.getResponseErrorMessage()); - } else if (pet == null) { - // A 2xx with an empty or unmappable body leaves this null. + } else if (response.getResponseData() == null) { ToastBar.showErrorMessage("The server returned no pet"); } else { - ToastBar.showInfoMessage(pet.name()); + ToastBar.showInfoMessage(response.getResponseData().name()); } }); } From a7f2f6c8fb24584f675332b936ccd5ed970cf03b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:34:53 +0300 Subject: [PATCH 6/9] cn1:generate-openapi emits a reference to a class it does not write Generating from the Swagger Petstore, the spec this appendix names, produces a Pet.java that does not compile: cannot find symbol symbol: class Tag location: package com.example.petstore.model Tag and Category declare the same two properties, unifyShapes collapses identical shapes to one record, and Tag is the one that goes. But property types are resolved in pass 2, before unification has run, so Pet.tags kept the name of the class that was about to be dropped and nothing ever emits it. Operations are immune because they are built after unifyShapes -- the comment there even says so -- which is why no test caught it. unifyShapes now records what it renamed and re-points the property types afterwards, matching on a token boundary so model.Tag inside model.TagSummary is left alone. A regression test drives the two-identical- schemas case and asserts both halves: Tag.java is not emitted, and Pet names Category. That is also what this change documents. The appendix's file tree listed Tag.java among the emitted models and the listings beside it came from a two-field test fixture, so the chapter described neither what the goal emits nor what it emitted here. Both listings are now the goal's output for the cut-down Petstore spec committed alongside, the file tree is what that run actually wrote, and the missing Tag.java is explained rather than quietly absent. Co-Authored-By: Claude Opus 5 (1M context) --- .../java/com/example/petstore/PetApi.java | 13 +- .../java/com/example/petstore/StoreApi.java | 56 +++++++++ .../java/com/example/petstore/UserApi.java | 53 +++++++++ .../com/example/petstore/model/Category.java | 30 +++++ .../com/example/petstore/model/Order.java | 30 +++++ .../java/com/example/petstore/model/Pet.java | 2 +- .../java/com/example/petstore/model/User.java | 30 +++++ ...pendix-goal-generate-openapi-petstore.json | 112 ++++++++++++++++++ .../appendix_goal_generate_openapi.adoc | 24 +++- .../codename1/maven/GenerateOpenApiMojo.java | 50 ++++++++ .../maven/GenerateOpenApiMojoTest.java | 36 ++++++ 11 files changed, 427 insertions(+), 9 deletions(-) create mode 100644 docs/demos/common/src/main/java/com/example/petstore/StoreApi.java create mode 100644 docs/demos/common/src/main/java/com/example/petstore/UserApi.java create mode 100644 docs/demos/common/src/main/java/com/example/petstore/model/Category.java create mode 100644 docs/demos/common/src/main/java/com/example/petstore/model/Order.java create mode 100644 docs/demos/common/src/main/java/com/example/petstore/model/User.java create mode 100644 docs/demos/common/src/main/snippets/developer-guide/appendix-goal-generate-openapi-petstore.json diff --git a/docs/demos/common/src/main/java/com/example/petstore/PetApi.java b/docs/demos/common/src/main/java/com/example/petstore/PetApi.java index 0549434d9d0..09130e88f37 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/PetApi.java +++ b/docs/demos/common/src/main/java/com/example/petstore/PetApi.java @@ -42,11 +42,20 @@ @RestClient public interface PetApi { + @POST("/pet") + void addPet(@Body com.example.petstore.model.Pet body, @Header("Authorization") String bearerToken, OnComplete> callback); + + @PUT("/pet") + void updatePet(@Body com.example.petstore.model.Pet body, @Header("Authorization") String bearerToken, OnComplete> callback); + + @GET("/pet/findByStatus") + void findPetsByStatus(@Query("status") String status, @Header("Authorization") String bearerToken, OnComplete>> callback); + @GET("/pet/{petId}") void getPetById(@Path("petId") Long petId, @Header("Authorization") String bearerToken, OnComplete> callback); - @POST("/pet") - void addPet(@Body com.example.petstore.model.Pet body, @Header("Authorization") String bearerToken, OnComplete> callback); + @DELETE("/pet/{petId}") + void deletePet(@Path("petId") Long petId, @Header("Authorization") String bearerToken, OnComplete> callback); static PetApi of(String baseUrl) { return RestClients.create(PetApi.class, baseUrl); diff --git a/docs/demos/common/src/main/java/com/example/petstore/StoreApi.java b/docs/demos/common/src/main/java/com/example/petstore/StoreApi.java new file mode 100644 index 00000000000..e4318617392 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/petstore/StoreApi.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +// Generated by cn1:generate-openapi. +package com.example.petstore; + +import com.codename1.annotations.rest.Body; +import com.codename1.annotations.rest.Cookie; +import com.codename1.annotations.rest.DELETE; +import com.codename1.annotations.rest.GET; +import com.codename1.annotations.rest.Header; +import com.codename1.annotations.rest.PATCH; +import com.codename1.annotations.rest.POST; +import com.codename1.annotations.rest.PUT; +import com.codename1.annotations.rest.Path; +import com.codename1.annotations.rest.Query; +import com.codename1.annotations.rest.RestClient; +import com.codename1.io.rest.Response; +import com.codename1.io.rest.RestClients; +import com.codename1.util.OnComplete; + +@RestClient +public interface StoreApi { + + @POST("/store/order") + void placeOrder(@Body com.example.petstore.model.Order body, @Header("Authorization") String bearerToken, OnComplete> callback); + + @GET("/store/order/{orderId}") + void getOrderById(@Path("orderId") Long orderId, @Header("Authorization") String bearerToken, OnComplete> callback); + + @DELETE("/store/order/{orderId}") + void deleteOrder(@Path("orderId") Long orderId, @Header("Authorization") String bearerToken, OnComplete> callback); + + static StoreApi of(String baseUrl) { + return RestClients.create(StoreApi.class, baseUrl); + } +} diff --git a/docs/demos/common/src/main/java/com/example/petstore/UserApi.java b/docs/demos/common/src/main/java/com/example/petstore/UserApi.java new file mode 100644 index 00000000000..696050235d7 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/petstore/UserApi.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +// Generated by cn1:generate-openapi. +package com.example.petstore; + +import com.codename1.annotations.rest.Body; +import com.codename1.annotations.rest.Cookie; +import com.codename1.annotations.rest.DELETE; +import com.codename1.annotations.rest.GET; +import com.codename1.annotations.rest.Header; +import com.codename1.annotations.rest.PATCH; +import com.codename1.annotations.rest.POST; +import com.codename1.annotations.rest.PUT; +import com.codename1.annotations.rest.Path; +import com.codename1.annotations.rest.Query; +import com.codename1.annotations.rest.RestClient; +import com.codename1.io.rest.Response; +import com.codename1.io.rest.RestClients; +import com.codename1.util.OnComplete; + +@RestClient +public interface UserApi { + + @POST("/user") + void createUser(@Body com.example.petstore.model.User body, @Header("Authorization") String bearerToken, OnComplete> callback); + + @GET("/user/{username}") + void getUserByName(@Path("username") String username, @Header("Authorization") String bearerToken, OnComplete> callback); + + static UserApi of(String baseUrl) { + return RestClients.create(UserApi.class, baseUrl); + } +} diff --git a/docs/demos/common/src/main/java/com/example/petstore/model/Category.java b/docs/demos/common/src/main/java/com/example/petstore/model/Category.java new file mode 100644 index 00000000000..1b5a8e71088 --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/petstore/model/Category.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +// Generated by cn1:generate-openapi. +package com.example.petstore.model; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record Category(@JsonProperty("id") Long id, @JsonProperty("name") String name) {} diff --git a/docs/demos/common/src/main/java/com/example/petstore/model/Order.java b/docs/demos/common/src/main/java/com/example/petstore/model/Order.java new file mode 100644 index 00000000000..9f37044741a --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/petstore/model/Order.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +// Generated by cn1:generate-openapi. +package com.example.petstore.model; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record Order(@JsonProperty("id") Long id, @JsonProperty("petId") Long petId, @JsonProperty("quantity") Integer quantity, @JsonProperty("shipDate") String shipDate, @JsonProperty("status") String status, @JsonProperty("complete") Boolean complete) {} diff --git a/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java b/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java index d3917293b45..79071e56291 100644 --- a/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java +++ b/docs/demos/common/src/main/java/com/example/petstore/model/Pet.java @@ -28,5 +28,5 @@ // tag::appendix-goal-generate-openapi-java-002[] @Mapped -public record Pet(@JsonProperty("id") Long id, @JsonProperty("name") String name) {} +public record Pet(@JsonProperty("id") Long id, @JsonProperty("name") String name, @JsonProperty("category") com.example.petstore.model.Category category, @JsonProperty("photoUrls") java.util.List photoUrls, @JsonProperty("tags") java.util.List tags, @JsonProperty("status") String status) {} // end::appendix-goal-generate-openapi-java-002[] diff --git a/docs/demos/common/src/main/java/com/example/petstore/model/User.java b/docs/demos/common/src/main/java/com/example/petstore/model/User.java new file mode 100644 index 00000000000..1d39d3e890a --- /dev/null +++ b/docs/demos/common/src/main/java/com/example/petstore/model/User.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +// Generated by cn1:generate-openapi. +package com.example.petstore.model; + +import com.codename1.annotations.JsonProperty; +import com.codename1.annotations.Mapped; + +@Mapped +public record User(@JsonProperty("id") Long id, @JsonProperty("username") String username, @JsonProperty("firstName") String firstName, @JsonProperty("lastName") String lastName, @JsonProperty("email") String email, @JsonProperty("password") String password, @JsonProperty("phone") String phone, @JsonProperty("userStatus") Integer userStatus) {} diff --git a/docs/demos/common/src/main/snippets/developer-guide/appendix-goal-generate-openapi-petstore.json b/docs/demos/common/src/main/snippets/developer-guide/appendix-goal-generate-openapi-petstore.json new file mode 100644 index 00000000000..e128f838d58 --- /dev/null +++ b/docs/demos/common/src/main/snippets/developer-guide/appendix-goal-generate-openapi-petstore.json @@ -0,0 +1,112 @@ +{ + "openapi": "3.0.3", + "info": { "title": "Swagger Petstore", "version": "1.0.20" }, + "servers": [ { "url": "https://petstore3.swagger.io/api/v3" } ], + "paths": { + "/pet": { + "post": { + "tags": ["pet"], "operationId": "addPet", + "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } }, + "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } } } + }, + "put": { + "tags": ["pet"], "operationId": "updatePet", + "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } }, + "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } } } + } + }, + "/pet/findByStatus": { + "get": { + "tags": ["pet"], "operationId": "findPetsByStatus", + "parameters": [ { "name": "status", "in": "query", "schema": { "type": "string" } } ], + "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Pet" } } } } } } + } + }, + "/pet/{petId}": { + "get": { + "tags": ["pet"], "operationId": "getPetById", + "parameters": [ { "name": "petId", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } } ], + "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } } } + }, + "delete": { + "tags": ["pet"], "operationId": "deletePet", + "parameters": [ { "name": "petId", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } } ], + "responses": { "200": { "description": "ok" } } + } + }, + "/store/order": { + "post": { + "tags": ["store"], "operationId": "placeOrder", + "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Order" } } } }, + "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Order" } } } } } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": ["store"], "operationId": "getOrderById", + "parameters": [ { "name": "orderId", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } } ], + "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Order" } } } } } + }, + "delete": { + "tags": ["store"], "operationId": "deleteOrder", + "parameters": [ { "name": "orderId", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } } ], + "responses": { "200": { "description": "ok" } } + } + }, + "/user": { + "post": { + "tags": ["user"], "operationId": "createUser", + "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } }, + "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } } } + } + }, + "/user/{username}": { + "get": { + "tags": ["user"], "operationId": "getUserByName", + "parameters": [ { "name": "username", "in": "path", "required": true, "schema": { "type": "string" } } ], + "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } } } + } + } + }, + "components": { + "schemas": { + "Category": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" } } }, + "Tag": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" } } }, + "Pet": { + "type": "object", + "properties": { + "id": { "type": "integer", "format": "int64" }, + "name": { "type": "string" }, + "category": { "$ref": "#/components/schemas/Category" }, + "photoUrls": { "type": "array", "items": { "type": "string" } }, + "tags": { "type": "array", "items": { "$ref": "#/components/schemas/Tag" } }, + "status": { "type": "string" } + } + }, + "Order": { + "type": "object", + "properties": { + "id": { "type": "integer", "format": "int64" }, + "petId": { "type": "integer", "format": "int64" }, + "quantity": { "type": "integer", "format": "int32" }, + "shipDate": { "type": "string", "format": "date-time" }, + "status": { "type": "string" }, + "complete": { "type": "boolean" } + } + }, + "User": { + "type": "object", + "properties": { + "id": { "type": "integer", "format": "int64" }, + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer", "format": "int32" } + } + } + } + } +} diff --git a/docs/developer-guide/appendix_goal_generate_openapi.adoc b/docs/developer-guide/appendix_goal_generate_openapi.adoc index 92f95567a6c..8b98c065e15 100644 --- a/docs/developer-guide/appendix_goal_generate_openapi.adoc +++ b/docs/developer-guide/appendix_goal_generate_openapi.adoc @@ -48,25 +48,37 @@ files (only missing files are written). ==== Generated output -For the Swagger Petstore reference spec the goal emits, under +The listings in this section are the goal's own output, generated from a +Swagger Petstore specification cut down to the `pet`, `store` and `user` +tags. That spec is in the repository at +`docs/demos/common/src/main/snippets/developer-guide/appendix-goal-generate-openapi-petstore.json`, +so you can run the goal against it and get these files byte for byte. + +One `@RestClient` interface per tag and one model per schema land under `common/src/main/java`: [listing] ---- com/example/petstore/ PetApi.java // @RestClient interface, methods addPet, updatePet, - // findPetsByStatus, getPetById, deletePet, ... - StoreApi.java // @RestClient interface, methods getInventory, - // placeOrder, getOrderById, deleteOrder - UserApi.java // @RestClient interface + // findPetsByStatus, getPetById, deletePet + StoreApi.java // @RestClient interface, methods placeOrder, + // getOrderById, deleteOrder + UserApi.java // @RestClient interface, methods createUser, + // getUserByName com/example/petstore/model/ Pet.java // @Mapped record (Java 17+) or class (Java 8) Order.java User.java Category.java - Tag.java ---- +`Tag.java` is missing from that list on purpose. The Petstore declares +`Tag` and `Category` with the same two properties, and identical shapes +collapse to one record, so `Pet.tags()` comes back typed as a list of +`Category`. Give the two schemas different shapes if you want them to +stay separate classes. + Each `@RestClient` interface method is annotated with the HTTP verb and path; parameters are annotated `@Path` / `@Query` / `@Header` / `@Body` so the processor knows how to assemble the `Rest` call. The diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateOpenApiMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateOpenApiMojo.java index 9c4b47ef670..2f21bbbc7cc 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateOpenApiMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/GenerateOpenApiMojo.java @@ -379,6 +379,9 @@ private SchemaInfo buildSchemaInfo(String name, Map schema) { /// emitted record/class. We keep the first-encountered name and alias /// the duplicates to it. private void unifyShapes() { + // Java type name of a schema that gets unified away -> the name of + // the schema it collapsed into. + Map renamed = new LinkedHashMap(); for (Map.Entry e : schemaByName.entrySet()) { SchemaInfo info = e.getValue(); String shape = shapeOf(info); @@ -388,10 +391,57 @@ private void unifyShapes() { nameAliases.put(info.specName, info.javaName); } else { info.isCanonical = false; + if (!info.javaName.equals(prior.javaName)) { + renamed.put(info.javaName, prior.javaName); + } info.javaName = prior.javaName; nameAliases.put(info.specName, prior.javaName); } } + if (renamed.isEmpty()) { + return; + } + // Property types were resolved in pass 2, before any of this was + // known, so a property pointing at a schema that has just been + // unified away still names a class nothing emits. Left alone that + // is a generated model which does not compile -- the Swagger + // Petstore hits it, where Tag and Category have the same shape and + // Pet references both. + for (SchemaInfo info : schemaByName.values()) { + for (PropInfo p : info.props) { + p.javaType = retypeModelReferences(p.javaType, renamed); + } + } + } + + /// Rewrites every `.` occurrence in a resolved + /// Java type to the canonical name it was unified into. The match has + /// to end on a non-identifier character so `model.Tag` inside + /// `model.TagSummary` is left alone. + private String retypeModelReferences(String javaType, Map renamed) { + if (javaType == null) { + return null; + } + String out = javaType; + for (Map.Entry e : renamed.entrySet()) { + String stale = modelPackage + "." + e.getKey(); + String canonical = modelPackage + "." + e.getValue(); + int from = 0; + for (;;) { + int at = out.indexOf(stale, from); + if (at < 0) { + break; + } + int after = at + stale.length(); + if (after < out.length() && Character.isJavaIdentifierPart(out.charAt(after))) { + from = after; + continue; + } + out = out.substring(0, at) + canonical + out.substring(after); + from = at + canonical.length(); + } + } + return out; } private static String shapeOf(SchemaInfo s) { diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/GenerateOpenApiMojoTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/GenerateOpenApiMojoTest.java index 9213073b6f3..6774ac3c5b9 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/GenerateOpenApiMojoTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/GenerateOpenApiMojoTest.java @@ -197,6 +197,42 @@ void respectsOverwriteFalseAndPreservesUserEdits(@TempDir Path tmp) throws Excep "overwrite=false should preserve user edits; was:\n" + apiSrc); } + /// The Swagger Petstore case: Tag and Category have the same shape, so one + /// is unified away, and Pet references both. Property types are resolved + /// before unification runs, so the surviving reference used to name the + /// dropped class and the emitted model did not compile. + @Test + void unifiedAwaySchemaIsNotLeftDanglingInAReference(@TempDir Path tmp) throws Exception { + String spec = + "{\"openapi\":\"3.0.0\",\"info\":{\"title\":\"t\",\"version\":\"1\"}," + + "\"paths\":{\"/pet\":{\"get\":{\"tags\":[\"Pet\"],\"operationId\":\"getPet\"," + + " \"responses\":{\"200\":{\"description\":\"ok\",\"content\":{\"application/json\":" + + " {\"schema\":{\"$ref\":\"#/components/schemas/Pet\"}}}}}}}}," + + "\"components\":{\"schemas\":{" + + " \"Category\":{\"type\":\"object\",\"properties\":{" + + " \"id\":{\"type\":\"integer\",\"format\":\"int64\"},\"name\":{\"type\":\"string\"}}}," + + " \"Tag\":{\"type\":\"object\",\"properties\":{" + + " \"id\":{\"type\":\"integer\",\"format\":\"int64\"},\"name\":{\"type\":\"string\"}}}," + + " \"Pet\":{\"type\":\"object\",\"properties\":{" + + " \"category\":{\"$ref\":\"#/components/schemas/Category\"}," + + " \"tags\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Tag\"}}}}" + + "}}}"; + File out = tmp.toFile(); + new GenerateOpenApiMojo.Generator(parse(spec), "com.example.petstore", out, + true, /*emitRecords*/ true, new SystemStreamLog()).run(); + + File tag = new File(out, "com/example/petstore/model/Tag.java"); + File category = new File(out, "com/example/petstore/model/Category.java"); + assertTrue(category.exists(), "Category is the canonical shape and must be emitted"); + assertFalse(tag.exists(), "Tag is structurally identical and should unify away"); + + String petSrc = readString(new File(out, "com/example/petstore/model/Pet.java")); + assertFalse(petSrc.contains("model.Tag"), + "Pet must not reference the class that was unified away; was:\n" + petSrc); + assertTrue(petSrc.contains("java.util.List tags"), + "the tags property should retype to the surviving class; was:\n" + petSrc); + } + @Test void parseJavaVersionHandlesShapes() { org.junit.jupiter.api.Assertions.assertEquals(8, GenerateOpenApiMojo.parseJavaVersion("1.8")); From 660874e396bc6f9bb9902a427bc3d4b8dfa2a86f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:39:55 +0300 Subject: [PATCH 7/9] Developer guide: a 2xx that would not parse is not an empty result decodeJson keeps the HTTP code when the body is empty or unparseable and returns no errors and no data, so the HTTP-range check added last round still let that land on "No hero for that episode". getResponseErrorMessage() is the one accessor that is null only on a clean success -- it carries the first GraphQL error, the transport failure, or the parse failure -- so that is what the guard reads now, with the range check kept for an error status whose body happened to decode. And the openapi appendix claimed the committed files come back byte for byte from a rerun, which they do not: they carry a license header and the tag:: markers the guide includes them by, and emitApi/emitModel write neither. Said plainly instead. Co-Authored-By: Claude Opus 5 (1M context) --- .../com/example/starwars/StarWarsCallSite.java | 14 +++++++++----- .../appendix_goal_generate_openapi.adoc | 4 +++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java index b220d57f9cc..f10c9cb8b4f 100644 --- a/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java +++ b/docs/demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java @@ -35,13 +35,17 @@ void heroName(String bearerToken) { StarWarsApi api = StarWarsApi.of("https://swapi.example.com/graphql"); api.heroName(Episode.EMPIRE, bearerToken, response -> { - // isOk() classifies the GraphQL payload, not the trip. A call that - // never reached the server arrives with getResponseCode() == 0, an - // empty errors array and null data -- so isOk() is true. Check the - // HTTP code before believing it. + // isOk() classifies the GraphQL payload, not the trip: a call that + // never reached the server, and a 2xx whose body would not parse, + // both arrive with an empty errors array and null data, so isOk() + // answers true for them. getResponseErrorMessage() is the one + // thing that is null only on a clean success -- it carries the + // first GraphQL error, the transport failure, or the parse + // failure -- and the HTTP code catches an error status whose body + // happened to decode. boolean reachedTheServer = response.getResponseCode() >= 200 && response.getResponseCode() <= 299; - if (!reachedTheServer || !response.isOk()) { + if (!reachedTheServer || response.getResponseErrorMessage() != null) { ToastBar.showErrorMessage(response.getResponseErrorMessage()); } else if (response.getData() == null || response.getData().hero() == null) { // And an error-free answer still need not have found anything: diff --git a/docs/developer-guide/appendix_goal_generate_openapi.adoc b/docs/developer-guide/appendix_goal_generate_openapi.adoc index 8b98c065e15..bc542d5a004 100644 --- a/docs/developer-guide/appendix_goal_generate_openapi.adoc +++ b/docs/developer-guide/appendix_goal_generate_openapi.adoc @@ -52,7 +52,9 @@ The listings in this section are the goal's own output, generated from a Swagger Petstore specification cut down to the `pet`, `store` and `user` tags. That spec is in the repository at `docs/demos/common/src/main/snippets/developer-guide/appendix-goal-generate-openapi-petstore.json`, -so you can run the goal against it and get these files byte for byte. +so you can run the goal against it and compare. The committed copies carry a +license header and the `tag::` markers this guide includes them by; strip +those two additions and the rest is exactly what the goal wrote. One `@RestClient` interface per tag and one model per schema land under `common/src/main/java`: From 4082fadd47d364e72bc9ebcc4c2e1271f3edcb02 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:46:19 +0300 Subject: [PATCH 8/9] Developer guide: the full license header on the test file the gate now checks check-copyright-headers accepts the short Oracle-era header only on files a change does not touch. Adding the regression test made this one modified, so it needs the complete Codename One header. Co-Authored-By: Claude Opus 5 (1M context) --- .../maven/GenerateOpenApiMojoTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/GenerateOpenApiMojoTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/GenerateOpenApiMojoTest.java index 6774ac3c5b9..1ac5b4d3ce4 100644 --- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/GenerateOpenApiMojoTest.java +++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/GenerateOpenApiMojoTest.java @@ -1,5 +1,24 @@ /* * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. */ package com.codename1.maven; From 14bf42127bf1303f338c3c615ce78b0c303d9a26 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:52:56 +0300 Subject: [PATCH 9/9] Developer guide: the call sites are the Java 17 shape, and say so All three appendices document both emission modes, and all three call sites were written against the record output: constructors with arguments, reads through accessors. On a Java 8 target the generator writes classes with a public no-arg constructor and public fields, so none of those lines compile. Each call site now carries a note saying which shape it shows and what changes on the other, which is smaller than duplicating three listings and keeps the compiled examples matching what the docs module actually builds. Co-Authored-By: Claude Opus 5 (1M context) --- docs/developer-guide/appendix_goal_generate_graphql.adoc | 5 +++++ docs/developer-guide/appendix_goal_generate_grpc.adoc | 5 +++++ docs/developer-guide/appendix_goal_generate_openapi.adoc | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/docs/developer-guide/appendix_goal_generate_graphql.adoc b/docs/developer-guide/appendix_goal_generate_graphql.adoc index e9fc80b37d2..02873b28f8b 100644 --- a/docs/developer-guide/appendix_goal_generate_graphql.adoc +++ b/docs/developer-guide/appendix_goal_generate_graphql.adoc @@ -127,6 +127,11 @@ an `OnComplete>`: include::../demos/common/src/main/java/com/example/starwars/StarWarsCallSite.java[tag=appendix-goal-generate-graphql-java-002,indent=0] ---- +NOTE: The call sites here are written against the Java 17 output, where the +response types are records. On a Java 8 target the generator emits classes +with public fields instead, so `response.getData().hero().name()` becomes +`response.getData().hero.name`. + A subscription returns a `GraphQLSubscription` handle whose `cancel()` ends the stream: diff --git a/docs/developer-guide/appendix_goal_generate_grpc.adoc b/docs/developer-guide/appendix_goal_generate_grpc.adoc index 23ca53c9950..1b8c3c12f4e 100644 --- a/docs/developer-guide/appendix_goal_generate_grpc.adoc +++ b/docs/developer-guide/appendix_goal_generate_grpc.adoc @@ -87,6 +87,11 @@ Call sites use the static factory: include::../demos/common/src/main/java/com/example/hello/GreeterCallSite.java[tag=appendix-goal-generate-grpc-java-002,indent=0] ---- +NOTE: The call site above is written against the Java 17 output, where the +messages are records. On a Java 8 target the generator emits classes with a +public no-arg constructor and public fields instead, so construction becomes +`new HelloRequest(); r.name = ...` and a read is `reply.message` rather than `reply.message()`. + The `GrpcImpl` class that actually performs the gRPC-Web POST lives in `target/generated-sources` -- the project source diff --git a/docs/developer-guide/appendix_goal_generate_openapi.adoc b/docs/developer-guide/appendix_goal_generate_openapi.adoc index bc542d5a004..8e6354cea3c 100644 --- a/docs/developer-guide/appendix_goal_generate_openapi.adoc +++ b/docs/developer-guide/appendix_goal_generate_openapi.adoc @@ -110,6 +110,10 @@ Call sites use the static factory: include::../demos/common/src/main/java/com/example/petstore/PetApiCallSite.java[tag=appendix-goal-generate-openapi-java-003,indent=0] ---- +NOTE: The call site here is written against the Java 17 output, where `Pet` +is a record. On a Java 8 target the generator emits a class with public +fields instead, so `pet.name()` becomes `pet.name`. + The `ApiImpl` class that actually performs the HTTP call lives in `target/generated-sources` -- the project source never references