javalin / javalin-openapi

Annotation processor for compile-time OpenAPI & JsonSchema, with out-of-the-box support for Javalin 5.x, Swagger & ReDoc
https://github.com/javalin/javalin-openapi/wiki
Apache License 2.0
45 stars 17 forks source link

Example for object does not work #209

Closed mzielinski closed 7 months ago

mzielinski commented 7 months ago

Actual behavior (the bug)

static class Barbie {

    private String name;
    private String link;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }
}

// should contain object example
@OpenApiExample(objects = {
    @OpenApiExampleProperty(name = "name", value = "Margot Robbie"),
    @OpenApiExampleProperty(name = "link", value = "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
})
public @NotNull Barbie getExampleObjects() {
    return new Barbie();
}

With such configuration, at the end I do not see my examples in the OpenAPI specification

"exampleObjects": {
    "name": "string",
    "link": "string"
}

Specification which is generated is

"exampleObjects": {
    $ref": "#/components/schemas/Barbie",
    "example": {
        "name": "Margot Robbie",
        "link": "https://www.youtube.com/watch?v\u003ddQw4w9WgXcQ" 
    }
}     

....

"Barbie": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "name": {
            "type": "string"
        },
        "link": {
            "type": "string"
        }
    }
}

Expected behavior

I would expect, that example should be injected to the openapi description like

"exampleObjects": {
    "name": "Margot Robbie",
    "link": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}

So specification code generated should be more like

"exampleObjects": {
    "$ref": "#/components/schemas/Barbie",
},          

....

"Barbie": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "name": {
            "type": "string",
            "example": "Margot Robbie"
        },
        "link": {
            "type": "string",
            "link": "https://www.youtube.com/watch?v\u003ddQw4w9WgXcQ"
        }
    }
}

Additional context

Currently, when I change type to Barbie[] (the same as we have in existing example Object[]), then it is properly injected example, but it requires to change specification, what it not acceptable.

dzikoysk commented 7 months ago

The intention behind @OpenApiExample(object = {}) is to attach an example in-place, for e.g. unknown types or this particular use-case (different examples in e.g. request body & in generic entity scheme description), so it's not leaked to referenced types. Thanks to that, we can provide such formats for e.g. Object type:

// should contain inline object example
@OpenApiExample(objects = {
    @OpenApiExampleProperty(name = "name", value = "Margot Robbie"),
    @OpenApiExampleProperty(name = "link", value = "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
})
public @NotNull Object getInlinedExampleObject() {
    return new String[] { timestamp };
}

results in:

"inlinedExampleObject" : {
  "type" : "object",
  "example" : {
    "name" : "Margot Robbie",
    "link" : "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  }
}

And this:

// should contain object example
@OpenApiExample(objects = {
        @OpenApiExampleProperty(name = "Barbie", objects = {
                @OpenApiExampleProperty(name = "name", value = "Margot Robbie"),
                @OpenApiExampleProperty(name = "link", value = "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
        }),
})
public @NotNull Object[] getExampleObjects() {
    return new String[] { timestamp };
}

results in:

"exampleObjects" : {
  "type" : "array",
  "items" : {
    "type" : "object"
  },
  "example" : {
    "Barbie" : {
      "name" : "Margot Robbie",
      "link" : "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    }
  }
},

If we know the type tho, such as your Barbie type, and we want to define global example for specific fields, it should be specified directly on properties within this type, like here:

https://github.com/javalin/javalin-openapi/blob/0d81c03ba1bc745140317cac01e2a6063b44d7fd/examples/javalin-gradle-kotlin/src/main/java/io/javalin/openapi/plugin/test/JavalinTest.java#L383-L397

So in the referenced object:

"foos" : {
  "type" : "array",
  "items" : {
    "$ref" : "#/components/schemas/Foo"
  }
},

we can see these dedicated examples:

"Foo" : {
  "type" : "object",
  "additionalProperties" : false,
  "properties" : {
    "property" : {
      "type" : "string"
    },
    "link" : {
      "type" : "string",
      "example" : "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    }
  }
},

As far as I understand, you'd like to achieve a similar result by enforcing @OpenApiExample to override examples in referenced entities. If that's correct, then I see 3 issues:

  1. Descriptions defined on a reference side, not in the entity definition, can overlap - e.g. the same entity can be used in 2 or more places. Each of them can define a custom description, so we're ending up with an entity definition that has multiple examples.
  2. It might be a bit unclear that there are 2 approaches to do the same thing
  3. It might be a little bit painful to implement 😅
dzikoysk commented 7 months ago

I've updated the example to cover this with cases we were talking about:

https://github.com/javalin/javalin-openapi/blob/c86e9dcddc6421558c28619ac279da9326dfab57/examples/javalin-gradle-kotlin/src/main/java/io/javalin/openapi/plugin/test/JavalinTest.java#L343-L370

That returns:

 "exampleFoo" : {
  "$ref" : "#/components/schemas/Foo",
  "example" : {
    "name" : "Margot Robbie",
    "link" : "Dedicated link"
  }
},
"exampleObject" : {
  "type" : "object",
  "example" : {
    "name" : "Margot Robbie",
    "link" : "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  }
},
"exampleObjects" : {
  "type" : "array",
  "items" : {
    "type" : "object"
  },
  "example" : {
    "Barbie" : {
      "name" : "Margot Robbie",
      "link" : "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    }
  }
},

and unaffected example (as expected) in the Foo scheme:

"Foo" : {
  "type" : "object",
  "additionalProperties" : false,
  "properties" : {
    "link" : {
      "type" : "string",
      "example" : "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    }
  }
},

If I missed the point, just let me know.

mzielinski commented 7 months ago

It is clear. Thank you for adding additional test, and explaining about the issues.