Composition and Inheritance support is missing

Issue #24 closed
Former user created an issue

Hi, We are noticing that the functionality for composition and inheritance is missing from the library. Below examples illustrate our usage;

YAML:

swagger: "2.0"
info:
  title: sample
  description: Sample for Composition and Inheritence
  version: v1

host: localhost:8082

schemes:
  - http
  - https

basePath: /sample
consumes:
  - application/json
produces:
  - application/json

paths:
  /setup:
    post:
      description: Setup a new usr
      parameters:
        - in: body
          name: body
          description: ""
          required: true
          schema:
            $ref: "#/definitions/UserSetup"
          examples:
              {
                "firstname": "sample_first",
                "lastname": "sample_last",
                "city": "sample_city",
                "country": "sample_country"
              }

      responses:
        "201":
          description: "ok"


definitions:

  NameSetup:
    type: object
    required:
    - firstname
    - lastname
    properties:
      firstname:
        type: string
      lastname:
        type: string


  UserSetup:
    allOf:
      - $ref: "#/definitions/NameSetup"
      - type: object
        properties:
          city:
            type: string
          country:
            type: string

Test Code:

SwaggerRequestResponseValidator validator = SwaggerRequestResponseValidator
                .createFor(convertYamlToJson("api/sample.yaml")).build();

        Builder reqBuilder = SimpleRequest.Builder.post("/setup");
        reqBuilder.withBody(readData("requests/user-setup.json"));
        final Request request = reqBuilder.build();

        final Response response = SimpleResponse.Builder.ok().build();

        ValidationReport report = validator.validate(request, response);

        if (report.hasErrors()) {
            for (Message msg : report.getMessages()) {
                System.out.println(msg);
            }
        } else {
            System.out.println("No Errors");
        }

Test Data:

{
                "firstname": "sample_first",
                "lastname": "sample_last",
                "city": "sample_city",
                "country": "sample_country"
              }

Output: ERROR - Object instance has properties which are not allowed by the schema: ["city","country","firstname","lastname"] ERROR - Instance failed to match all required schemas (matched only 1 out of 2)

Comments (13)

  1. James Navin

    I've done some investigation - it looks like the problem is caused by the insertion of "additionalProperties":false by the com.atlassian.oai.validator.schema.SchemaValidator class.

    This was done to support detection of field removal and bad assumptions in the Pact validation scenario, but it seems to be causing problems for this use case.

    e.g. This validates fine:

    {
        "$ref": "#/definitions/User",
        "definitions": {
            "Name": {
                "properties": {
                    "firstname": {
                        "type": "string"
                    },
                    "lastname": {
                        "type": "string"
                    }
                },
                "required": [
                    "firstname",
                    "lastname"
                ],
                "type": "object"
            },
            "User": {
                "allOf": [
                    {
                        "$ref": "#/definitions/Name"
                    },
                    {
                        "properties": {
                            "city": {
                                "type": "string"
                            },
                            "country": {
                                "type": "string"
                            }
                        },
                        "type": "object"
                    }
                ]
            }
        }
    }
    

    but the generated schema

    {
        "$ref": "#/definitions/User",
        "additionalProperties": false,
        "definitions": {
            "Name": {
                "additionalProperties": false,
                "properties": {
                    "firstname": {
                        "type": "string"
                    },
                    "lastname": {
                        "type": "string"
                    }
                },
                "required": [
                    "firstname",
                    "lastname"
                ],
                "type": "object"
            },
            "User": {
                "additionalProperties": false,
                "allOf": [
                    {
                        "$ref": "#/definitions/Name"
                    },
                    {
                        "properties": {
                            "city": {
                                "type": "string"
                            },
                            "country": {
                                "type": "string"
                            }
                        },
                        "type": "object"
                    }
                ]
            }
        }
    }
    

    fails validation with the above errors.

    I will investigate further and see if I can come up with a nice solution that meets both requirements.

  2. James Navin

    I think the best solution for now is to disable insertion of additionalProperties:false if the validation is ignored. Clients will need to opt-out of the additionalProperties validation by setting validation.schema.additionalProperties=IGNORE in a swagger-validator.properties file (or via system properties etc.)

    This will provide a workaround for the case above, while not breaking current consumers that may rely on the additionalProperties validation.

  3. Murthy Kakarlamudi Account Deactivated

    James...We use Polymorphism in our API specs and in testing that I am finding the below errors. I updated the OpenAPI spec you used in the new brach with additional definitions. Below is the updated definition:

    swagger: "2.0"
    info:
      title: sample
      description: Sample for Composition and Inheritence
      version: v1
    
    host: localhost:8082
    
    schemes:
      - http
      - https
    
    basePath: /api
    consumes:
      - application/json
    produces:
      - application/json
    
    paths:
      /user:
        post:
          description: Create a new user
          parameters:
            - in: body
              name: body
              description: ""
              required: true
              schema:
                $ref: "#/definitions/User"
              examples:
                  {
                    "firstname": "sample_first",
                    "lastname": "sample_last",
                    "city": "sample_city",
                    "country": "sample_country"
                  }
    
          responses:
            "201":
              description: "ok"
    
    
    definitions:
    
      Name:
        type: object
        required:
        - firstname
        - lastname
        properties:
          firstname:
            type: string
          lastname:
            type: string
    
      User:
        allOf:
          - $ref: "#/definitions/Name"
          - type: object
            properties:
              city:
                type: string
              country:
                type: string
    
      Pet:
        type: object
        discriminator: petType
        properties:
          name:
            type: string
          petType:
            type: string
        required:
        - name
        - petType
    
      Cat:
        description: A representation of a cat
        allOf:
        - $ref: '#/definitions/Pet'
        - type: object
          properties:
            huntingSkill:
              type: string
              description: The measured skill for hunting
              default: lazy
              enum:
              - clueless
              - lazy
              - adventurous
              - aggressive
          required:
          - huntingSkill
    
      Dog:
        description: A representation of a dog
        allOf:
        - $ref: '#/definitions/Pet'
        - type: object
          properties:
            packSize:
              type: integer
              format: int32
              description: the size of the pack the dog is from
              default: 0
              minimum: 0
          required:
          - packSize
    

    For an invalid scenario, cat-invalid1.json I am getting an error but I am not getting the relevant errors in the ValidationReport.

    cat-invalid1.json

    {
        "name":"My Cat",
        "petType":"Cat"
    
    }
    

    Java Code used to test:

    final SchemaValidator classUnderTest = new SchemaValidator(
                    new SwaggerParser().read("/api/api-composition.yaml"),
                    new MessageResolver(
                            LevelResolver
                                    .create()
                                    .withLevel(ADDITIONAL_PROPERTIES_KEY, ValidationReport.Level.IGNORE)
                                    .build()));
    
    
            final Model schema = new RefModel("#/definitions/Cat");
            final String value = readData("requests/cat-invalid1.json");
    
            ValidationReport report = classUnderTest.validate(value, schema);
            if (report.hasErrors()) {
                for (Message msg : report.getMessages()) {
                    System.out.println(msg);
                }
            } else {
                System.out.println("No Errors");
            }
    

    Output:

    17:28:31.597 [main] INFO  io.swagger.parser.Swagger20Parser - reading from /api/api-composition.yaml
    17:28:31.923 [main] DEBUG c.a.o.v.r.LevelLoader$PropertiesLoader - Levels file /Users/murthy/dev/workspace/validatoin/.swagger-validator does not exist. Skipping.
    ERROR - Instance failed to match all required schemas (matched only 1 out of 2)
    

    My question is why am I not getting an error saying that the required element "huntingSkill" is missing.

    Also for a different test scenario, where in I pass a value that is not listed in the enum for petType, I expected an error but not getting any validation errors back:

    cat-invalid2.json

    {
        "name":"My Cat",
        "petType":"Rabbit",
        "huntingSkill":"lazy"
    }
    

    Output:

    17:38:25.488 [main] INFO  io.swagger.parser.Swagger20Parser - reading from /api/api-composition.yaml
    17:38:25.811 [main] DEBUG c.a.o.v.r.LevelLoader$PropertiesLoader - Levels file /Users/murthy/dev/workspace/validatoin/.swagger-validator does not exist. Skipping.
    No Errors
    

    Can you please help.

  4. James Navin

    Re the first example: Looks like I need to update the error reporting to unpack sub-reports from the validation messages generated by the json schema validation library. I will raise a ticket for that.

    Re the use of the discriminator keyword - that seems to be Swagger specific and so won't be supported by the JSON schema validator library. I'll raise a ticket to see about implementing support for it (it looks like I may be able to use the oneOf keyword - see https://github.com/OAI/OpenAPI-Specification/issues/333)

  5. Murthy Kakarlamudi Account Deactivated

    That would be great. Thanks much. Yeah, oneOf seems to be the equivalent of discriminator.

  6. Murthy Kakarlamudi Account Deactivated

    Are 25 and 26 available in 1.0.6? Because for #25, I am not able to see the errors come out in ValidationReport. Also for #26, If I give a value outside of the enum, It validates fine without any errors. The result should be a failure.

  7. James Navin

    I haven't started working on #25 or #26 yet. v1.0.6 contains the changes around ignoring additionalProperties which will allow the composition via allOf.

    Im hoping to get time to look at the other tickets this week.

  8. Log in to comment