GithubHelp home page GithubHelp logo

rest-schemagen's People

Contributors

bernhardbln avatar busoniu avatar dibimer avatar joergadler avatar leflamm avatar m-kn avatar mweirauch avatar renemazala avatar skynetsonar avatar wuan avatar yasammez avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rest-schemagen's Issues

Allowed values should be aware of type

The current version uses Strings by default. In case of boolean and number values the real type should be used.

The default values are inconsistent with the allowed values:

{
  "type": "boolean",
  "default": false,
  "enum": ["true"]
}

Reference-Checking doesn't account for the whole typed path

Consider a situation

class Container<T> {
  public final ObjectWithSchema<T> root;
}

Now, when trying to construct a schema for ObjectWithSchema<Container<ObjectWithSchema<Foo>>> the algorithm will recursively drill into the generic, but fail to see the difference between ObjectWithSchema<Foo> and ObjectWithSchema<Container<...>>, resulting in a pummelled schema

{
  "properties": {
    "root": {
      "$ref":"#"
    }
  }
}

Possible solution: always use the whole type with all type arguments when caching a schema for recursive use.

Add currentValue to Parameter

In order to set the allowed values independent of the current value used in the returned link, the Parameter class should have a field currentValue which is returned in the get method.

For example, if the field currentValue is added and the line

return allowedValues.isEmpty() ? defaultValue : allowedValues.get(0);

is replaced by

if (currentValue != null) {
    return currentValue
} else {
    return allowedValues.isEmpty() ? defaultValue : allowedValues.get(0);
}

then everything should be working as before, with the option to provide a currentValue different from any of the allowedValues.

Support template links for PUT

Using PUT for creating a resource with client side id's leads to problems when creating the link to the resource. Probably the BeanParam should not be treated as payload in com.mercateo.common.rest.schemagen.RestJsonSchemaGenerator#createInputSchema.

    @PUT
    @Path("{app-id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public ObjectWithSchema<Void> createResource(
            @NotNull @Valid @BeanParam ResourceIdParameterBean idParameterBean,
            @NonNull @Valid ResourceJson resourceJson) { ... }
15:37:59 ERROR com.mercateo.rest.jersey.utils.exception.RFCExceptionMapper - Sending error to client
java.lang.IllegalStateException: multiple properties named <> found
	at com.mercateo.common.rest.schemagen.RestJsonSchemaGenerator.createInputSchema(RestJsonSchemaGenerator.java:132)
	at com.mercateo.common.rest.schemagen.link.LinkCreator.addSchemaIfNeeded(LinkCreator.java:216)
	at com.mercateo.common.rest.schemagen.link.LinkCreator.createFor(LinkCreator.java:120)
	at com.mercateo.common.rest.schemagen.link.LinkCreator.createFor(LinkCreator.java:79)
	at com.mercateo.common.rest.schemagen.link.LinkFactory.forCall(LinkFactory.java:143)
	at com.mercateo.common.rest.schemagen.link.LinkFactory.forCall(LinkFactory.java:124)
	at com.mercateo.common.rest.schemagen.link.LinkFactory.forCall(LinkFactory.java:64)
        ...

Add support for Maps with enum keys

JSON Hyperschema doesn't allow for keys to be dynamic, so when we have something like

public Map<Foo, Bar> someMap;

with example JSON

{
  'key1':'val1',
  'key2':'val2',
}

we cannot do

{
  'type':'map', /* this doesn't exist )-: */
  'keys': {
    'type':'string',
    'enum': [ 'key1', 'key2' ],
  }
  'values': {
    'type':'string'
  }
}

But iff the keyset is finite, i.e., for practical reasons, an enum, like

public enum Keys = { KEY_1, KEY_2 };
public Map<Keys, String> someMap;

we can do the following:

'someMap': {
  'type':'object',
  'properties': {
    'KEY_1': {
      'type':'string' },
    'KEY_2': {
      'type':'string' },
    },
}

Therefore, the SchemaGenerator needs to, when encountering a Map, check whether the keyClazz is instanceof Enum and if so, unwind the whole thing. Otherwise it should emit object and not touch it any further.

ENUM is not created for UUIDs

Currently the ENUMs are generated for all fields except for fileds of type UUID.

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
class ExampleJson {

    @NotNull
    private final UUID id;

    @NotNull
    private final UUID appId;

    @Nullable
    @Size(min = 1, max = 35)
    private final String contactName;

    @NotNull
    @Size(min = 1, max = 35)
    private final String city;

    @NotNull
    private final CountryCode countryCode;

    @NotNull
    @Size(min = 1, max = 35)
    private final String street;

    @NotNull
    @Size(min = 1, max = 10)
    private final String zipcode;
}
    Optional<Link> getUpdateLink(ExampleData exampleData) {
        ExampleJson exampleJson = new ExampleJson(exampleData.getId(), exampleData
                .getAppId(), "contactName", "city", exampleData.getCountryCode(), "street", "zipcode");
        
        final CallContext context = Parameter.createContext();

        final Parameter.Builder<ExampleJson > exampleJsonBuilder = context.builderFor(
                ExampleJson.class);

        final Parameter<ExampleJson> updateJson = exampleJsonBuilder 
                .allowValues(exampleJson)
                .defaultValue(exampleJson).build();

        return linkFactory.forCall(Rel.UPDATE, r -> r.update(
                new IdParameterBean(exampleData.getId()
                        .toString()), updateJson), context);
    }

Result:

{
                "href": "http://localhost:8080/api/addresses/48278ad2-9ba0-42c1-9ba4-c33ba8684666",
                "schema": {
                    "type": "object",
                    "properties": {
                        "appId": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$"
                        },
                        "city": {
                            "type": "string",
                            "default": "city",
                            "enum": [
                                "city"
                            ],
                            "minLength": 1,
                            "maxLength": 35
                        },
                        "contactName": {
                            "type": "string",
                            "default": "",
                            "enum": [
                                ""
                            ],
                            "minLength": 1,
                            "maxLength": 35
                        },
                        "countryCode": {
                            "type": "string",
                            "default": "DE",
                            "enum": [
                                "DE"
                            ]
                        },
                        "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$"
                        },
                        "street": {
                            "type": "string",
                            "default": "street",
                            "enum": [
                                "street"
                            ],
                            "minLength": 1,
                            "maxLength": 35
                        },
                        "zipcode": {
                            "type": "string",
                            "default": "zipcode",
                            "enum": [
                                "zipcode"
                            ],
                            "minLength": 1,
                            "maxLength": 10
                        }
                    },
                    "required": [
                        "appId",
                        "city",
                        "companyName",
                        "countryCode",
                        "id",
                        "street",
                        "zipcode"
                    ]
                },
                "rel": "update",
                "mediaType": "application/json",
                "method": "PUT"
            }

Query Parameters not encoded correctly

When creating a Link for a Response, query parameters are not encoded correctly.

Easiest way to reproduce: try to pass some Json {"key": "value"} as a query parameter value.

java.lang.IllegalArgumentException: Illegal character """ at position 5 is not allowed as a start of a name in a path template "fq={ "foo": {"bar": "zort","narf": "poit" }}".
at org.glassfish.jersey.uri.internal.UriTemplateParser.parseName(UriTemplateParser.java:330)
at org.glassfish.jersey.uri.internal.UriTemplateParser.parse(UriTemplateParser.java:255)
at org.glassfish.jersey.uri.internal.UriTemplateParser.<init>(UriTemplateParser.java:114)
at org.glassfish.jersey.uri.UriTemplate.createUriComponent(UriTemplate.java:1004)
at org.glassfish.jersey.uri.UriTemplate.createURIWithStringValues(UriTemplate.java:970)
at org.glassfish.jersey.uri.UriTemplate.createURIWithStringValues(UriTemplate.java:827)
at org.glassfish.jersey.uri.UriTemplate.createURI(UriTemplate.java:790)
at org.glassfish.jersey.uri.internal.JerseyUriBuilder._buildFromMap(JerseyUriBuilder.java:822)
at org.glassfish.jersey.uri.internal.JerseyUriBuilder.buildFromMap(JerseyUriBuilder.java:801)
at com.mercateo.common.rest.schemagen.link.LinkCreator.mergeUri(LinkCreator.java:125)
at com.mercateo.common.rest.schemagen.link.LinkCreator.createFor(LinkCreator.java:110)
at com.mercateo.common.rest.schemagen.link.LinkCreator.createFor(LinkCreator.java:79)
at com.mercateo.common.rest.schemagen.link.LinkFactory.forCall(LinkFactory.java:143)
at com.mercateo.common.rest.schemagen.link.LinkFactory.forCall(LinkFactory.java:124)
at com.mercateo.common.rest.schemagen.link.LinkFactory.forCall(LinkFactory.java:64)
at com.mercateo.content.articleservice.rest.resource.article.ArticlesResource.lambda$createPaginatedResponse$2(ArticlesResource.java:166)
at com.mercateo.common.rest.schemagen.PaginationLinkBuilder.createSelfLink(PaginationLinkBuilder.java:50)
at com.mercateo.common.rest.schemagen.PaginationLinkBuilder.generateLinks(PaginationLinkBuilder.java:40)
at com.mercateo.common.rest.schemagen.types.PaginatedResponseBuilder.build(PaginatedResponseBuilder.java:44)

Wrong default value in schema

For the JSON-type boolean, always a default value is generated in the schema, see
com.mercateo.common.rest.schemagen.json.mapper.BooleanJsonPropertyMapper
This is not the case for Integer
com.mercateo.common.rest.schemagen.json.mapper.IntegerJsonPropertyMapper

Unfortunately this not always correct. In the following example there should be no default value:
/users gives the list of all users
/users?admin=true gives the list of all admin users
/users?admin=false gives the list of all non-admin users
In Java Code the admin flag would be of type Boolean and its value would be null, if the query parameter is not given.

One solution would be, to remove the default value of all primitives and use only default values, if given by annotations.

Another solution would be to differentiate between the Java types boolean and Boolean (and similarly for other primitives) and use the java default values only for boolean, int, etc. If this solution is chosen, one should also think about if this should only happen in the schema or if it should also happen in the target_schema. Maybe one should leave it out for the target_schema as the Java primitives will always have a value when being returned and no default value would be needed.

Interesting places could be:
com.mercateo.common.rest.schemagen.SchemaPropertyGenerator#builtins
com.mercateo.common.rest.schemagen.PropertyTypeMapper#TYPE_MAP
com.mercateo.common.rest.schemagen.json.mapper.PropertyJsonSchemaMapperForRoot#primitivePropertyMappers

Type leak since 0.18.5

With the introduction of com.mercateo:reflection with 0.18.5 one can easily bring a service down with OOMs in Metaspace (or Compressed Class Space, Code Cache) when these pools are configured with an upper bound (e.g. with -XX:MaxMetaspaceSize) or unbound until the host system memory is depleted. (Metaspace is unbound since Java8 and leaks like this might go unnoticed without proper monitoring.)

Reproduce:
Construct LinkFactory with LinkMetaFactory.createFactoryFor(MyResource.class) in the method body of your resource endpoint. For every call a new type/class is created with the following naming pattern: sun.reflect.GeneratedSerializationConstructorAccessorxxxx where xxxx is a increasing number. (A VisualVM Heapdump is sufficient to get a picture of the described behaviour.)

When a resource is called a dozen or hundred times a second the GC can't hold up scanning and unloading all the temporary created types from Metaspace (holding the type defintions) and will lead to Metaspace-OOM and service starvation.

Remediation/Workaround:
Keep a reference to the created proxy LinkFactory.

Handle iterable Query parameters

Multiple Query parameters like in

    @GET
    public void multipleQueryParameters(@QueryParam("test") Iterable<String> parameters) {
        ...
    }

are not handled correctly, as their string representation is added as a single parameter:

basePath/resource?test=%5Bfoo%2C+bar%2C+baz%5D

One would expect to get

basePath/resource?test=foo&tes=bar&test=baz

instead.

wrong recursion detection in com.mercateo.common.rest.schemagen.SchemaPropertyGenerator.getUnwrappedFieldsMap

If a class A has multiple fields of class B and the fields are annotated with @JsonUnwrapped
(with different "prefix"-attribute), then the above method throws an IllegalStateException with the message "recursion detected while unwrapping field ...".

But, this is no recursion.

Example:

@Value
public class A {
    @JsonUnwrapped(prefix="b1.")
    private final B b1;

    @JsonUnwrapped(prefix="b2.")
    private final B b2;
}

The method detects class B for field b1 first and adds it to the unwrappedTypes HashSet without problems. Field b2 having the same class will result in this exception.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.