Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate class names in different packages get squashed in ControllerDocumentation - modelMap #182

Closed
mbazos opened this issue Dec 4, 2013 · 57 comments

Comments

@mbazos
Copy link

mbazos commented Dec 4, 2013

I haven't yet made a fix for this yet but our team encountered this issue and I believe it's fairly easy to fix but wanted to get feedback from you on how to go about this.

Example of the issue given the following controller method:

    public void sampleMethod3(com.mangofactory.swagger.spring.sample.duplicate.Pet petOne, com.mangofactory.swagger.spring.sample.Pet petTwo) {
    }

Notice that petOne and petTwo are different classes but have the same class name. When the ControllerDocumnetation modelMap is being populated it uses a friendly class name as the key so it would be "Pet" in this instance. With "Pet" being the same key in the model map they will overwrite each other.

Ways I was thinking to go about fixing this:

  1. Just add in the package name...so friendly names will now be fully qualified (Not so friendly names)
  2. Add configuration into SwaggerConfiguration that would trigger friendly/fully qualified names

I am really not sure which one to do...friendly names are nice but people might end up not realizing this is happening so it's almost safer to have fully qualified.

Not sure if you need a reason of why this is important but here is it:

  1. Service versioning - having multiple version of client model object could cause these collision to happen over time
  2. Services that return subsets of a canonical model - some data might be expensive or unnecessary to return in this case service A could return petOne (superset) and service B could return petTwo (subset of petOne).

Let me know what you think and if I have time I don't mind working on this fix as it will benefit our team.

@dilipkrish
Copy link
Member

@mbazos Interesting bug and totally an overlooked case :) Thanks for pointing it out! 👍. Couple of observations, not to be pedantic and all :-o!

  • When you're thinking about these service interfaces, its almost a good idea not to leak internal implementation to the projected service interface.
  • Two versions of a domain shouldn't co-mingle at service boundaries, that adaptation should again be an internal implementation.

With that out of the way, there is certainly a case that has the notion of the model namespaced by package rather than by the name of the type, like in your case com.xxxxx.canonical.Pet and com.xxxxx.summary.Pet.

I would say the simplest solution, (for now) rather than bringing in the package name etc. is the create a custom annotation.

public void sampleMethod3(@ModelAlias("PetCanonical") com.xxxxxx.canonical.Pet petOne, 
                                        @ModelAlias("PetSummary") com.xxxxxx.summary.Pet petTwo) {
    }

I'd modify ResolvedTypes to handle aliasing model names.

I think its fairly simple and also doesnt clutter the names with package names etc. I'm trying to get an intermediate release with 1.2 spec support, so, If you have some cycles I'd love to take in a pr! I'm planning to pay down some tech debt in 0.7 and modularizing the library. I could create a chore to make that an extensibility point.

Let me know if you have any questions.

@mbazos
Copy link
Author

mbazos commented Dec 6, 2013

Thanks for getting back to me so quickly with this. So I completely agree with you on the two points you have outlined. Based on what you are saying and it completely makes sense is that I would rename my objects that are similar to give them a more distinct name.

For the example you outlined above you could have com.xxxxxx.canonical.Pet and com.xxxxxx.summary.PetSummary or com.xxxxxx.canonical.PetSummary. That way the domain objects are distinct and there is no confusion about what Pet domain object is what.

The above annotation becomes valuable when you supporting multiple versions of a given service (happens all the time because consumers might not want to move to the latest...for whatever reason). Imagine this package structure:

 com.xxxxxx.v1.canonical.Pet
 com.xxxxxx.v2.canonical.Pet

I know ideally your canonical model shouldn't change but in practice this happens I see these things happen:

  1. Business needs/rules change
  2. You learn there is a better way to represent your data for your clients
  3. Deprecate features that are no longer applicable
  4. Lazy consumers don't have "time" to upgrade

Service versioning is always an interesting topic. Again I really appreciate the help.

@donovanmuller
Copy link

I have a similar situation except that this is happening on the response class as well.
We have some generated JAXB classes which unfortunately share the same top level class. Even though the classes are fully qualified, springfox seems to pick one and use that everywhere.

Example below:

@ApiOperation(value = "Save something")
public blah.blah.Document create(@RequestBody foo.bar.Document thingy) {
  ...
  return new foo.bar.Document();
}

The Swagger docs will only contain one representation of Document...

@dilipkrish
Copy link
Member

Yeah this needs to be fixed.

@stephenhibbert
Copy link

I'm also having the same issue. A fix would be very much appreciated!

@mbazos
Copy link
Author

mbazos commented Aug 25, 2015

If I have some time I might work on this. I recently upgraded our apps from swagger-springmvc to springfox and I am really liking the re-organization and re-packaging of the libraries.

I am going to be out of the country and won't be back until the last week in September, I will make sure to check here before I start any work on this.

@dilipkrish
Copy link
Member

@mbazos 🤘 would be much appreciated!

However, since this feature affects the core, I would like to collaborate on how you plan to approach this before you work on the PR. Just want to make sure its in-line with the direction we want to take this library.

Let me know if I can answer any questions.

@dilipkrish dilipkrish modified the milestones: 2.3.0, 2.4.0 Aug 28, 2015
@nikitsenka
Copy link

Could you please provide some aprox estimations when the issue will be fixed?

@dilipkrish
Copy link
Member

ping @mbazos!

@mbazos
Copy link
Author

mbazos commented Dec 23, 2015

Yes been very busy with life and work. Currently on break for the holidays will try to get this done before the new year.

@dilipkrish
Copy link
Member

Not a problem... was just pinging to see if you still had plans to help with this feature. It will be much appreciated ... Thanks!

@mbazos
Copy link
Author

mbazos commented Dec 28, 2015

@dilipkrish It's been a while since I have looked at this code and our discussion above references the old version of swagger-springmvc before you worked on the major refactor. Anyway I forked the repo and got my local workstation all up and running.

Just wanted to check a few things:
So I think what needs to change is DefaultTypeNameProvider, is this correct? Right now it's returning the class simple name which will conflict if more than one class have the same name.

Also I took a second look and I can see in the DefaultModelProvider the following:

  private Optional<Model> mapModel(ModelContext parentContext, ResolvedType resolvedType) {
    if (isMapType(resolvedType) && !parentContext.hasSeenBefore(resolvedType)) {
      String typeName = typeNameExtractor.typeName(parentContext);
      return Optional.of(parentContext.getBuilder()
          .id(typeName)
          .type(resolvedType)
          .name(typeName)
          .qualifiedType(ResolvedTypes.simpleQualifiedTypeName(resolvedType))
          .properties(new HashMap<String, ModelProperty>())
          .description("")
          .baseModel("")
          .discriminator("")
          .subTypes(new ArrayList<String>())
          .build());
    }
    return Optional.absent();
  }

I believe .id() needs to be the fully qualified name but .name() needs to be the simple name. Can you just let me know to make sure I am on the right path.

@dilipkrish
Copy link
Member

I believe thats sounds correct but haven't dug in recently to tell u without spelunking. Off hand I know that there is also TypeNameExtractor that uses the TypeNameProviders plugins.

I thought I'd just chime in with a few expectations.

  • It would be good to verify that your solution will work with swagger-codegen
  • I would feature toggle this feature with a flag in the docket similar to the enableUrlTemplating flag _and mark with an @Incubating annotation_.
  • Implement it in such a way that it falls back to current behavior if it doesn't find two models with the same name; similar to how we do operation unique ids using some kind of caching technique like here

I'll report back if I find anything different or interesting. Thanks for helping out!

@mbazos
Copy link
Author

mbazos commented Jan 13, 2016

@dilipkrish just getting comfortable with the project and I have to be honest, I am trying to setup the things you mentioned above and I am partially there but I am having a hard time understanding how everything is connected.

I think I know the code changes I need to make in DefaultModelProvider but I am having a hard time understanding what I need to do to setup the plugin so I can make the decision of when to use this new behavior or not. I am a little confused about the operation/unique ids examples you sent me.

is there an irc chat or something that some of the devs use?

MaksimOrlov added a commit that referenced this issue Oct 5, 2019
Fix sort ordering.
MaksimOrlov added a commit that referenced this issue Oct 5, 2019
Fix checkstyle.
MaksimOrlov added a commit that referenced this issue Oct 5, 2019
Fix resource listing.
MaksimOrlov added a commit that referenced this issue Oct 5, 2019
Fix Webflux tests.
timharrison-moj added a commit to ministryofjustice/prison-api that referenced this issue Nov 8, 2019
… wth generating swagger docs. See issue springfox/springfox#182 - where the package is ignored for classes with the same name, which caused /api/bookings/{bookingId} to incorrectly show the alert structure for model/v1/Alert.java, not model/Alert.java.
Mjwillis pushed a commit to ministryofjustice/prison-api that referenced this issue Nov 8, 2019
… wth generating swagger docs. See issue springfox/springfox#182 - where the package is ignored for classes with the same name, which caused /api/bookings/{bookingId} to incorrectly show the alert structure for model/v1/Alert.java, not model/Alert.java. (#273)
@duke-cliff
Copy link

@dilipkrish Why the ticket is marked as solved? What's the solution to use full name/package name instead of simple name for the model?

@duke-cliff
Copy link

Sorry, saw it was marked as solved in the release notes but not in the actual ticket

dilipkrish added a commit that referenced this issue Jan 6, 2020
@MaksimOrlov
Copy link
Member

@robertsearle, @benjvoigt , the fix has been merged to master branch. Could you, please, provide a feedback for 3.0.0-SNAPSHOT, if the issue was fixed or not ?

@benjvoigt
Copy link

@MaksimOrlov numbering appears to be stable between runs now, ty. I tested with multiple endpoints and also with nested models as return value.

jaredstehler pushed a commit to wn-doolittle/springfox that referenced this issue Jan 23, 2020
* Create a FormParameter model object instead of BodyParameter for fields that are part of a multipart form (in=formData).

* Adapted behavior of ParameterMapper and added tests.

* Fix contract tests.

* Reduce diff from master contract files.

* Bump mixin-deep from 1.3.1 to 1.3.2 in /springfox-swagger-ui/src/web

Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](jonschlinkert/mixin-deep@1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

* [fix issue 3118] discriminator not set to discriminator from model

* fix discriminator usage in test templates

* springfox#182

Fix sort ordering.

* springfox#182

Fix checkstyle.

* springfox#182

Fix resource listing.

* springfox#182

Fix Webflux tests.

* Bump swagger-ui depdency

Security fix detailed in swagger-ui's release notes.
https://github.com/swagger-api/swagger-ui/releases/tag/v3.23.11

* Set the basePath using the request context path if present

This allows the X-Forwarded-Prefix header to work correctly

* Updated spring dependencies to spring 5.2.0 and spring boot 2.2.0, fixed changed interfaces

* Added documentationType in DocPluginManager tests

* Fixed typo (springfox#3161)

* springfox#2932 - Removed trailing space

* springfox#2932 - Spring HATEOAS 1.0.0.RELEASE

* springfox#2932 - Spring Data REST 3.2.0.RELEASE

* springfox#2932 - fix hateos collectionModel rule

* Fixed checkstyle issues in ParameterMapper.java

Signed-off-by: Martin Kremers <info@martinkremers.de>

* More test cases for parameter Mapper spec

Signed-off-by: Martin Kremers <info@martinkremers.de>

* fix for issue springfox#3178

replace  new FileInputStream(resource.getFile()) with springfox#3178

* reéoved unused import

* added test for coverage

* springfox#3189 - handle defaults in Spring placeholders

* springfox#3189 - return default value if property not found in environment

* springfox#2932: Fixed import for model test.

* springfox#2932: Minor version upgrades of spring dependencies. Aligned contract tests with Spring HATEOAS 1.x. Fixed wrong client encoding used in tests.

* jackson 2.10.1

* Bump handlebars from 4.1.2 to 4.5.3 in /springfox-swagger-ui/src/web

Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](handlebars-lang/handlebars.js@v4.1.2...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>

* Update test to pass when running on Windows.

* Upgrade to version of swagger which fixes css injection vulnerability as defined in https://github.com/tarantula-team/CSS-injection-in-Swagger-UI

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Stefan Bissell <26431980+weaselflink@users.noreply.github.com>
Co-authored-by: Maksim Orlov <maksim.orlov@yahoo.fr>
Co-authored-by: Steven Locke <StevenLocke@users.noreply.github.com>
Co-authored-by: Jerry Collings <jcollings@ancestry.com>
Co-authored-by: JoGir <JoGir@users.noreply.github.com>
Co-authored-by: Andree Wormuth <wormuth@users.noreply.github.com>
Co-authored-by: Martin Kremers <info@martinkremers.de>
Co-authored-by: Olivier Gérardin <ogerardin@yahoo.com>
Co-authored-by: Andreas Kluth <AndreasKl@users.noreply.github.com>
Co-authored-by: sullis <github@seansullivan.com>
Co-authored-by: Dilip Krishnan <dilipkrish@users.noreply.github.com>
@stale
Copy link

stale bot commented Jun 24, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Jun 24, 2020
@stale
Copy link

stale bot commented Jul 8, 2020

This issue has been automatically closed because it has not had recent activity. Please re-open a new issue if this is still an issue.

@ande13
Copy link

ande13 commented Apr 15, 2021

For java, you can use the following workaround

@primary
@component
public class SwaggerApiModelTypeNameProvider extends ApiModelTypeNameProvider {

private final Map<String, Pair<String, String>> classNames = new ConcurrentHashMap<>();

@Override
public String nameFor(Class<?> type) {
    ApiModel annotation = AnnotationUtils.findAnnotation(type, ApiModel.class);
    String defaultTypeName = type.getTypeName().replace(type.getPackageName() + ".", "");

    defaultTypeName = annotation != null ? Optional.ofNullable(Strings.emptyToNull(annotation.value())).orElse(defaultTypeName) : defaultTypeName;

    Pair<String, String> classPackagePair = classNames.get(defaultTypeName);
    if(classPackagePair != null) {
        if(!classPackagePair.getValue().equalsIgnoreCase(type.getPackageName())) {
            defaultTypeName = "_" + defaultTypeName;
        }
    }
    classNames.put(defaultTypeName, Pair.of(type.getTypeName(), type.getPackageName()));

    return defaultTypeName;

}

}

You can modify it if needed :)

@bestarshad
Copy link

please refer the files in repo for the fix

same class name issue fix in open api swagger it will generate schema request response as per java annotation naming convention against the java variables. it will not create schema request response as per java variables it will follow schema annotation

github

@xak2000
Copy link

xak2000 commented Jan 27, 2022

@MaksimOrlov Just tried 3.0.0 and the issue is still not fixed.

Maybe because in my case the difference is not a package name, but an outer class name:

package my.package.dto;

public class User {
  private Location location;

  public static class Location {
    private String name;
  }
}

And:

package my.package.dto;

public class Car {
  private Location location;

  public static class Location {
    private Long id;
    private String address;
  }
}

Only one Location model is present in the generated docs. So, one of User or Car has incorrect model for location property.

@anibyl
Copy link

anibyl commented Mar 21, 2022

This helped me:

TypeNameResolver.std.setUseFqn(true);

@jyeonjyan
Copy link

@vignesharunachalam I had the same issue lately. Until a clear solution arises, you can always go around the problem by annotating at least one of your model with @ApiModel and use a unique name (e.g. LegacyPet for instance)

This solution solved my problem the fastest.
My situation for example..

- com.example.domain.dto.foo.Foo.Create
- com.example.domain.dto.bar.Bar.Create

package com.example.domain.dto.foo
@ApiModel("FooCreate")
data class Create(){}

package com.example.domain.dto.bar
@ApiModel("BarCreate")
data class Create(){}

@jyeonjyan
Copy link

@dilipkrish
First of all, thank you for your hard work.
So, is this issue resolved or won't fix?

@Tvaroh
Copy link

Tvaroh commented Mar 12, 2024

For newest swagger annotations this helps:

@Schema(name = "CustomName")

@yupnano
Copy link

yupnano commented Apr 12, 2024

ten years passed 😭

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests