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

Handle examples per property for array mocking (using mixing or index) #951

Open
kerwanp opened this issue Nov 4, 2022 · 2 comments
Open
Assignees

Comments

@kerwanp
Copy link

kerwanp commented Nov 4, 2022

Describe the enhancement you'd like to see

Currently when mocking a path returning a schema with properties that have examples it only uses the example.
When returning an array of this schema it also uses the example.

The enhancement would be to use the examples to provide better mocked data for paths returning arrays.
Here are two ways of providing mocked data using the examples of each property of the schema.

  • Randomly mixing examples of each property
  • Using array index to use the example at the same position in the examples

Examples

If I have the following schema:

paths:
  /organizations:
    get:
      operationId: OrganizationsController_list
      parameters: []
      responses:
        default:
          description: ""
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/GetOrganizationResult"
  "/organizations/{id}":
    get:
      operationId: OrganizationsController_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: number
      responses:
        "200":
          description: ""
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetOrganizationResult"
tags: []
servers: []
components:
  schemas:
    GetOrganizationResult:
      type: object
      properties:
        id:
          type: number
          examples:
            - 1
            - 2
            - 3
          example: 1
        name:
          type: string
          examples:
            - Syneki
            - KMP Agency
            - Oyez
          example: Syneki
        domain:
          type: string
          examples:
            - syneki.com
            - kmp.agency
            - oyez.fr
          example: Syneki
      required:
        - id
        - name
        - domain

Before enhancement

Currently it always uses the example of each property, even when returning a list of the schema.

GET /organizations/1
{
  "id": 1
  "name": "Syneki",
  "domain": "syneki.com"
}

GET /organizations
[
  {
    "id": 1
    "name": "Syneki",
    "domain": "syneki.com"
  },
  {
    "id": 1
    "name": "Syneki",
    "domain": "syneki.com"
  },
  {
    "id": 1
    "name": "Syneki",
    "domain": "syneki.com"
  },
  ...
]

After enhancement

# By using the example key
GET /organizations/1
{
  "id": 1
  "name": "Syneki",
  "domain": "syneki.com"
}
# By using the examples key with mixing (taking random example for each property)
GET /organizations/1
{
  "id": 2
  "name": "Syneki",
  "domain": "oyez.fr"
}
# By using the same index for each property
GET /organizations/1
{
  "id": 2
  "name": "KMP Agency",
  "domain": "kmp.agency"
}

# By using the examples key with mixing (taking random example for each property)
GET /organizations
[
  {
    "id": 2
    "name": "KMP Agency",
    "domain": "syneki.com"
  },
  {
    "id": 3
    "name": "Syneki",
    "domain": "oyez.fr"
  },
  {
    "id": 2
    "name": "Oyez",
    "domain": "syneki.com"
  },
  ...
]

# By getting the example with the same index for each property (or an other one if the element does not exist)
GET /organizations
[
  {
    "id": 1
    "name": "Syneki",
    "domain": "syneki.com"
  },
  {
    "id": 2
    "name": "KMP",
    "domain": "kmp.agency"
  },
  {
    "id": 3
    "name": "Oyez",
    "domain": "oyez.fr"
  },
  ...
]

By doing that it becomes also extremely easy to mock really complex schemas with nested objects.

A bit of context

I wanted to add the reason why I find this enhancement extremely interesting.

Currently it is still complex to develop locally on a project using the API Gateway as it has to run on Kubernetes.
If we do an update we have to deploy the app on a Cluster, the OpenAPI definition and then test properly.
Even by using tools to make this process extremely easy, it takes time.

That's why I think that the mocking feature is an important part.

In my case I automatically generate the OpenAPI definition file for each service using Swagger and more specifically @nestjs/swagger.

The example of above has been generated with the following code:

export class GetOrganizationResult extends Organization {

    @ApiProperty({ examples: [1, 2, 3], example: 1 })
    id: number;

    @ApiProperty({ examples: ['Syneki', 'KMP Agency', 'Oyez'], example: 'Syneki' })
    name: string;

    @ApiProperty({ examples: ['syneki.com', 'kmp.agency', 'oyez.fr'], example: 'syneki.com' })
    domain: string;

}

@Controller('organizations')
export class OrganizationsController {
    constructor(
        @InjectRepository(Organization) private organizationsRepository: Repository<Organization>
    ) {}

    @Get('')
    @ApiResponse({ type: [GetOrganizationResult] })
    public async list(): Promise<GetOrganizationResult[]> {
        return this.organizationsRepository.find();
    }

    @Get(':id')
    @ApiParam({ name: 'id', type: 'number' })
    @ApiResponse({ type: GetOrganizationResult, status: 200 })
    public async get(@Param('id') id: number): Promise<GetOrganizationResult> {
        return this.organizationsRepository.findOne({ where: { id }})
    }
}

With few lines of code I can:

  • Write the business logic
  • Write the definitions of paths
  • Write the definitions of my schemas
  • Generate mocking data
  • Generate the OpenAPI definition file

And soon I will implement https://www.npmjs.com/package/class-validator to also generate the validation part.

@mbana
Copy link
Contributor

mbana commented Dec 6, 2022

@kerwanp,

An example, which I know is different to yours, that works is:

components: {}
info:
  title: issue-951-mock
  version: 0.1.0
openapi: 3.0.0
paths:
  /hello:
    get:
      responses:
        "200":
          content:
            application/json:
              examples:
                2:
                  value:
                    message: Hello from a mocked response 2!
                1:
                  value:
                    message: Hello from a mocked response 1!
              schema:
                properties:
                  message:
                    type: string
                type: object
          description: A simple hello world!
  /validated:
    post:
      requestBody:
        content:
          application/json:
            schema:
              properties:
                name:
                  type: string
              required:
                - name
              type: object
      responses:
        "200":
          content:
            text/plain:
              example: Hello mocked Kusk!
              schema:
                type: string
          description: ""
x-kusk:
  cors:
    methods:
      - GET
      - POST
    origins:
      - "*"
  mocking:
    enabled: true
  public_api_path: "simple/spec.json"
kusk mock -i ./issue-951-mock.yaml
$ bash -c 'while [[ true ]]; do curl "http://localhost:8080/hello"; echo; done'
{"message":"Hello from a mocked response 1!"}
{"message":"Hello from a mocked response 1!"}
{"message":"Hello from a mocked response 1!"}
{"message":"Hello from a mocked response 1!"}
{"message":"Hello from a mocked response 1!"}
{"message":"Hello from a mocked response 1!"}
{"message":"Hello from a mocked response 2!"}

NB: That it returns {"message":"Hello from a mocked response 2!"}.

The way I specified examples is per the specification: see the Multiple examples in response bodies section of https://swagger.io/docs/specification/adding-examples/.


Can you point me to the part of the specification that shows that the example you shared is valid?

https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.1/schema.yaml#L168 - examples is of type object not array, so the issue-951-mock example I posted seems correct.

The one you shared uses an array instead. Correct me if I'm wrong but according to https://swagger.io/docs/specification/adding-examples/ under the Object and Property Examples section:

Note that schemas and properties support single example but not multiple examples.

So:

components:
  schemas:
    GetOrganizationResult:
      type: object
      properties:
        id:
          type: number
          examples:
            - 1
            - 2
            - 3
          example: 1
        name:
          type: string
          examples:
            - Syneki
            - KMP Agency
            - Oyez
          example: Syneki
        domain:
          type: string
          examples:
            - syneki.com
            - kmp.agency
            - oyez.fr
          example: Syneki
      required:
        - id
        - name
        - domain

Should not be supported because examples is used instead of example hence why you are seeing that output.

@mbana mbana moved this from Ready to Blocked in Kusk Gateway Product Backlog Dec 7, 2022
@jasmingacic
Copy link
Contributor

@kerwanp It seems that what you reported isn't defined by OpenAPI specs.
Can you confirm this and close issue if you agree?

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

No branches or pull requests

3 participants