swagger-api / swagger-api/swagger-codegen

[JAVA] Different files generated when using the CLI and when using the generator alone

Open
#11,831 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Mustache
Stars
17.8k
Forks
6k
PR merge metrics
No merged PRs in 30d

Description

Description

Different java classes are generated when using the CLI or utilising its composing functions (182 items against 1000+ when using the CLI).

Swagger-codegen version

3.0.34

Swagger declaration file content or url

Extract of the used spec file:

This is an extract of the used yaml file.

components:
  schemas:
    AccessContextManagerAccessLevel:
      properties:
        apiVersion:
          description: 'apiVersion defines the versioned schema of this representation
            of an object. Servers should convert recognized schemas to the latest
            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
          type: string
        kind:
          description: 'kind is a string value representing the REST resource this
            object represents. Servers may infer this from the endpoint the client
            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
          type: string
        metadata:
          type: V1ObjectMeta
        spec:
          properties:
            accessPolicyRef:
              description: |-
                The AccessContextManagerAccessPolicy this
                AccessContextManagerAccessLevel lives in.
              oneOf:
              - not:
                  required:
                  - external
                required:
                - name
              - not:
                  anyOf:
                  - required:
                    - name
                  - required:
                    - namespace
                required:
                - external
              properties:
                external:
                  description: 'Allowed value: string of the format `accessPolicies/{{value}}`,
                    where {{value}} is the `name` field of an `AccessContextManagerAccessPolicy`
                    resource.'
                  type: string
                name:
                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
                  type: string
                namespace:
                  description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
                  type: string
              type: object
            basic:
              description: A set of predefined conditions for the access level and
                a combining function.
              properties:
                combiningFunction:
                  description: |-
                    How the conditions list should be combined to determine if a request
                    is granted this AccessLevel. If AND is used, each Condition in
                    conditions must be satisfied for the AccessLevel to be applied. If
                    OR is used, at least one Condition in conditions must be satisfied
                    for the AccessLevel to be applied. Default value: "AND" Possible values: ["AND", "OR"].
                  type: string
                conditions:
                  description: A set of requirements for the AccessLevel to be granted.
                  items:
                    properties:
                      devicePolicy:
                        description: |-
                          Device specific restrictions, all restrictions must hold for
                          the Condition to be true. If not specified, all devices are
                          allowed.
                        properties:
                          allowedDeviceManagementLevels:
                            description: |-
                              A list of allowed device management levels.
                              An empty list allows all management levels. Possible values: ["MANAGEMENT_UNSPECIFIED", "NONE", "BASIC", "COMPLETE"].
                            items:
                              type: string
                            type: array
                          allowedEncryptionStatuses:
                            description: |-
                              A list of allowed encryptions statuses.
                              An empty list allows all statuses. Possible values: ["ENCRYPTION_UNSPECIFIED", "ENCRYPTION_UNSUPPORTED", "UNENCRYPTED", "ENCRYPTED"].
                            items:
                              type: string
                            type: array
                          osConstraints:
                            description: |-
                              A list of allowed OS versions.
                              An empty list allows all types and all versions.
                            items:
                              properties:
                                minimumVersion:
                                  description: |-
                                    The minimum allowed OS version. If not set, any version
                                    of this OS satisfies the constraint.
                                    Format: "major.minor.patch" such as "10.5.301", "9.2.1".
                                  type: string
                                osType:
                                  description: 'The operating system type of the device.
                                    Possible values: ["OS_UNSPECIFIED", "DESKTOP_MAC",
                                    "DESKTOP_WINDOWS", "DESKTOP_LINUX", "DESKTOP_CHROME_OS",
                                    "ANDROID", "IOS"].'
                                  type: string
                                requireVerifiedChromeOs:
                                  description: If you specify DESKTOP_CHROME_OS for
                                    osType, you can optionally include requireVerifiedChromeOs
                                    to require Chrome Verified Access.
                                  type: boolean
                              required:
                              - osType
                              type: object
                            type: array
                          requireAdminApproval:
                            description: Whether the device needs to be approved by
                              the customer admin.
                            type: boolean
                          requireCorpOwned:
                            description: Whether the device needs to be corp owned.
                            type: boolean
                          requireScreenLock:
                            description: |-
                              Whether or not screenlock is required for the DevicePolicy
                              to be true. Defaults to false.
                            type: boolean
                        type: object
                      ipSubnetworks:
                        description: |-
                          A list of CIDR block IP subnetwork specification. May be IPv4
                          or IPv6.
                          Note that for a CIDR IP address block, the specified IP address
                          portion must be properly truncated (i.e. all the host bits must
                          be zero) or the input is considered malformed. For example,
                          "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly,
                          for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32"
                          is not. The originating IP of a request must be in one of the
                          listed subnets in order for this Condition to be true.
                          If empty, all IP addresses are allowed.
                        items:
                          type: string
                        type: array
                      members:
                        items:
                          description: |-
                            An allowed list of members (users, service accounts).
                            Using groups is not supported.

                            The signed-in user originating the request must be a part of one
                            of the provided members. If not specified, a request may come
                            from any user (logged in/not logged in, not present in any
                            groups, etc.).
                          oneOf:
                          - required:
                            - serviceAccountRef
                          - required:
                            - user
                          properties:
                            serviceAccountRef:
                              oneOf:
                              - not:
                                  required:
                                  - external
                                required:
                                - name
                              - not:
                                  anyOf:
                                  - required:
                                    - name
                                  - required:
                                    - namespace
                                required:
                                - external
                              properties:
                                external:
                                  description: 'Allowed value: string of the format
                                    `serviceAccount:{{value}}`, where {{value}} is
                                    the `email` field of an `IAMServiceAccount` resource.'
                                  type: string
                                name:
                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
                                  type: string
                                namespace:
                                  description: 'Namespace of the referent. More info:
                                    https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
                                  type: string
                              type: object
                            user:
                              type: string
                          type: object
                        type: array
                      negate:
                        description: |-
                          Whether to negate the Condition. If true, the Condition becomes
                          a NAND over its non-empty fields, each field must be false for
                          the Condition overall to be satisfied. Defaults to false.
                        type: boolean
                      regions:
                        description: |-
                          The request must originate from one of the provided
                          countries/regions.
                          Format: A valid ISO 3166-1 alpha-2 code.
                        items:
                          type: string
                        type: array
                      requiredAccessLevels:
                        items:
                          description: |-
                            A list of other access levels defined in the same policy.
                            Referencing an AccessContextManagerAccessLevel which does not exist
                            is an error. All access levels listed must be granted for the
                            condition to be true.
                          oneOf:
                          - not:
                              required:
                              - external
                            required:
                            - name
                          - not:
                              anyOf:
                              - required:
                                - name
                              - required:
                                - namespace
                            required:
                            - external
                          properties:
                            external:
                              description: 'Allowed value: The `name` field of an
                                `AccessContextManagerAccessLevel` resource.'
                              type: string
                            name:
                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
                              type: string
                            namespace:
                              description: 'Namespace of the referent. More info:
                                https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
                              type: string
                          type: object
                        type: array
                    type: object
                  type: array
              required:
              - conditions
              type: object
            custom:
              description: "Custom access level conditions are set using the Cloud\
                \ Common Expression Language to represent the necessary conditions\
                \ for the level to apply to a request. \nSee CEL spec at: https://github.com/google/cel-spec."
              properties:
                expr:
                  description: "Represents a textual expression in the Common Expression\
                    \ Language (CEL) syntax. CEL is a C-like expression language.\n\
                    This page details the objects and attributes that are used to\
                    \ the build the CEL expressions for \ncustom access levels - https://cloud.google.com/access-context-manager/docs/custom-access-level-spec."
                  properties:
                    description:
                      description: Description of the expression.
                      type: string
                    expression:
                      description: Textual representation of an expression in Common
                        Expression Language syntax.
                      type: string
                    location:
                      description: String indicating the location of the expression
                        for error reporting, e.g. a file name and a position in the
                        file.
                      type: string
                    title:
                      description: Title for the expression, i.e. a short string describing
                        its purpose.
                      type: string
                  required:
                  - expression
                  type: object
              required:
              - expr
              type: object
            description:
              description: Description of the AccessLevel and its use. Does not affect
                behavior.
              type: string
            resourceID:
              description: Immutable. Optional. The name of the resource. Used for
                creation and acquisition. When unset, the value of `metadata.name`
                is used as the default.
              type: string
            title:
              description: Human readable title. Must be unique within the Policy.
              type: string
          required:
          - accessPolicyRef
          - title
          type: object
        status:
          properties:
            conditions:
              description: Conditions represent the latest available observation of
                the resource's current state.
              items:
                properties:
                  lastTransitionTime:
                    description: Last time the condition transitioned from one status
                      to another.
                    type: string
                  message:
                    description: Human-readable message indicating details about last
                      transition.
                    type: string
                  reason:
                    description: Unique, one-word, CamelCase reason for the condition's
                      last transition.
                    type: string
                  status:
                    description: Status is the status of the condition. Can be True,
                      False, Unknown.
                    type: string
                  type:
                    description: Type is the type of the condition.
                    type: string
                type: object
              type: array
            observedGeneration:
              description: ObservedGeneration is the generation of the resource that
                was most recently observed by the Config Connector controller. If
                this is equal to metadata.generation, then that means that the current
                reported status reflects the most recent desired state of the resource.
              type: integer
          type: object
      required:
      - spec
      type: object

Personalised codegen:

import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.swagger.codegen.v3.CodegenModel;
import io.swagger.codegen.v3.generators.java.SpringCodegen;
import io.swagger.v3.oas.models.media.Schema;
import io.vavr.collection.List;

/**
 * This class defines the specific implementation of the CodeGenerator.
 */
public class MyCodegen extends SpringCodegen {

    /**
     * Create a new instance of a model from an existing one and
     * additional metadata.
     * @param name The model's name.
     * @param schema The model's schema.
     * @param allSchemas The model's set of schemas.
     * @return The generated model.
     */
    @Override
    public io.swagger.codegen.v3.CodegenModel fromModel(String name, Schema schema,
                                                        java.util.Map<String, Schema> allSchemas) {
        var ret = super.fromModel(name, schema, allSchemas);

        ret.imports.add(BaseObject.class.getSimpleName());

        boolean hasMetadata = List.ofAll(ret.requiredVars)
                .appendAll(ret.optionalVars)
                .find(prop -> prop.name.equals("metadata") && prop.datatype.equals(
                        V1ObjectMeta.class.getCanonicalName()))
                .isDefined();

        if (hasMetadata) {
            generateMetaModel(ret);
        }

        return ret;
    }

    /**
     * Generate CodegenModel with the previously fetched metadata.
     * @param model The model whose metadata is to be used.
     */
    private void generateMetaModel(CodegenModel model) {
        var metaModel = new CodegenModel();
        metaModel.name = BaseObject.class.getSimpleName();
        metaModel.classname = BaseObject.class.getCanonicalName();
        model.parentModel = metaModel;
        model.parent = BaseObject.class.getSimpleName();
    }

    /**
     * Take a property name and return the getter name for it.
     * This is due to a hateful combination of java primitives and java properties.
     * All the boolean properties are generated as 'Boolean' not 'boolean',
     * since they are all optional. Swagger generates a 'Boolean' getter as 'is<Foo>'.
     * Java properties want all non-'boolean' property getters to be 'get<Foo>'
     * The Yamlifier thus can't find a getter, which means it's not a property it can write.
     * This is fixed in the OpenAPI generator but not the Swagger generator.
     * @param name The property name.
     * @return The getter name.
     */
    @Override
    public String toBooleanGetter(String name) {
        return toGetter(name);
    }
}

Code to perform generation if not using the CLI:

        OpenAPI openAPI = new OpenAPIV3Parser().read(specFile.toAbsolutePath().toString(), null, null);
        ClientOptInput clientOpts = new ClientOptInput().openAPI(openAPI);
        CodegenConfig codegenConfig = CodegenConfigLoader.forName("spring");
        codegenConfig.setOutputDir(outputDir.toAbsolutePath().toString());
        clientOpts.setConfig(codegenConfig);
        ClientOpts clientOps = new ClientOpts();
        clientOps.setProperties(HashMap.of(
                "hideGenerationTimestamp", "true",
                "notNullJacksonAnnotation", "true"
        ).toJavaMap());
        clientOpts.setOpts(clientOps);
        Generator gen = new DefaultGenerator().opts(clientOpts);
        gen.generate();

Code to perform generation using the CL (the output is just a temporary directory):

        var args = List.of(
                "generate",
                "-i", specFile.toAbsolutePath().toString(),
                "-l", MyCodegen.class.getCanonicalName(),
                "-o", outputDir.toAbsolutePath().toString());
Command line used for generation

As above

Steps to reproduce

Run the code with the provided spec file (copy-paste into a YAML file), and try once without the CLI utilizing the above functions and then with the CLI. Notice the difference in the generated files.

The CLI should generate 14 files whereas the other solution will only generate 1.

Related issues/PRs

None

Suggest a fix/enhancement

Investigate what is the difference in the two procedures.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by comparing generation through the CLI with generation through the generator's composing functions, using the supplied OpenAPI YAML extract and version 3.0.34. Trace why the two entry points produce 182 versus more than 1,000 Java classes; done means both paths generate the same class set for the same specification.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
cli, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.