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

Nesting multiple, separate objects defined with oneOf in allOf does not work as expected #544

Open
joshAg opened this issue Sep 19, 2022 · 2 comments

Comments

@joshAg
Copy link

joshAg commented Sep 19, 2022

I think this is related to #513. If I have an object that is all of X and Y, and X is one of X1 or X2 and Y is one of Y1 or Y2, I'd expect that the resulting object would be (X1 | X2) & (Y1 | Y2) (or X & Y, since i don't have a preference how much decomposition there is), not (X1 | X2 | Y1 | Y2).

I'm not entirely sure how to fix this, but I have a test you can add to test/allof_oneof_test.ts that hits this issue and that continues the the player/team example of the other tests.

    it("multiple 'allOf' and 'oneOf' nestings schema", async () => {
        const base: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            allOf: [
                { $ref: '/test/multiple/allOf/oneOf/general' },
                { $ref: '/test/multiple/allOf/oneOf/team' },
                { $ref: '/test/multiple/allOf/oneOf/player' },
            ],
        };
        const general: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/general',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                id: { type: 'integer' },
                type: { type: 'string' },
            },
            required: ['id', 'type'],
        };
        const team: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/team',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            oneOf: [
                { $ref: '/test/multiple/allOf/oneOf/team1' },
                { $ref: '/test/multiple/allOf/oneOf/team2' },
            ],
        };
        const team1: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/team1',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                name: { type: 'string' },
            },
            required: ['id', 'name'],
        };
        const team2: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/team2',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                founded: { type: 'string' },
            },
            required: ['id', 'founded'],
        };
        const player: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/player',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            oneOf: [
                { $ref: '/test/multiple/allOf/oneOf/player1' },
                { $ref: '/test/multiple/allOf/oneOf/player2' },
            ],
        };
        const player1: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/player1',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                firstName: { type: 'string' },
                lastName: { type: 'string' },
            },
            required: ['id', 'firstName', 'lastName'],
        };
        const player2: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/player2',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                name: { type: 'string' },
                born: { type: 'string' },
            },
            required: ['id', 'name', 'born'],
        };

        const result = await dtsGenerator({
            contents: [
                base,
                general,
                team,
                player,
                team1,
                team2,
                player1,
                player2,
            ].map((s) => parseSchema(s)),
        });

        const expected = `declare namespace Test {
    namespace Multiple {
        namespace AllOf {
            export type OneOf = {
                id: number;
                type: string;
            } & (OneOf.Team1 | OneOf.Team2) & (OneOf.Player1 | OneOf.Player2);
            namespace OneOf {
                export interface General {
                    id: number;
                    type: string;
                }
                export type Player = Player1 | Player2;
                export interface Player1 {
                    firstName: string;
                    lastName: string;
                }
                export interface Player2 {
                    name: string;
                    born: string;
                }
                export type Team = Team1 | Team2;
                export interface Team1 {
                    name: string;
                }
                export interface Team2 {
                    founded: string;
                }
            }
        }
    }
}
`;
        assert.strictEqual(result, expected);
    });

or (to show that the issue persists even when X and Y parenthesize the oneOf correctly

    it("multiple 'allOf' and 'oneOf' nestings schema", async () => {
        const base: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            allOf: [
                { $ref: '/test/multiple/allOf/oneOf/team' },
                { $ref: '/test/multiple/allOf/oneOf/player' },
            ],
        };
        const general: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/general',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                id: { type: 'integer' },
                type: { type: 'string' },
            },
            required: ['id', 'type'],
        };
        const team: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/team',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            allOf: [
                { $ref: '/test/multiple/allOf/oneOf/general' },
                {
                    oneOf: [
                        { $ref: '/test/multiple/allOf/oneOf/team1' },
                        { $ref: '/test/multiple/allOf/oneOf/team2' },
                    ],
                },
            ],
        };
        const team1: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/team1',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                name: { type: 'string' },
            },
            required: ['id', 'name'],
        };
        const team2: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/team2',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                founded: { type: 'string' },
            },
            required: ['id', 'founded'],
        };
        const player: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/player',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            allOf: [
                { $ref: '/test/multiple/allOf/oneOf/general' },
                {
                    oneOf: [
                        { $ref: '/test/multiple/allOf/oneOf/player1' },
                        { $ref: '/test/multiple/allOf/oneOf/player2' },
                    ],
                },
            ],
        };
        const player1: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/player1',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                firstName: { type: 'string' },
                lastName: { type: 'string' },
            },
            required: ['id', 'firstName', 'lastName'],
        };
        const player2: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf/player2',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            properties: {
                name: { type: 'string' },
                born: { type: 'string' },
            },
            required: ['id', 'name', 'born'],
        };

        const result = await dtsGenerator({
            contents: [
                base,
                general,
                team,
                player,
                team1,
                team2,
                player1,
                player2,
            ].map((s) => parseSchema(s)),
        });

        const expected = `declare namespace Test {
    namespace Multiple {
        namespace AllOf {
            export type OneOf = {
                id: number;
                type: string;
            } & (OneOf.Team1 | OneOf.Team2) & (OneOf.Player1 | OneOf.Player2);
            namespace OneOf {
                export interface General {
                    id: number;
                    type: string;
                }
                export type Player = {
                    id: number;
                    type: string;
                } & (Player1 | Player2);
                export interface Player1 {
                    firstName: string;
                    lastName: string;
                }
                export interface Player2 {
                    name: string;
                    born: string;
                }
                export type Team = {
                    id: number;
                    type: string;
                } & (Team1 | Team2);
                export interface Team1 {
                    name: string;
                }
                export interface Team2 {
                    founded: string;
                }
            }
        }
    }
}
`;
        assert.strictEqual(result, expected);
    });
@horiuchi
Copy link
Owner

@joshAg Thank you for your report. 👍

I will investigate the cause of this bug.

@joshAg
Copy link
Author

joshAg commented Sep 20, 2022

Thanks for taking a look! If you see a good work-around while investigating, that'd be awesome. While playing around with my actual code-base I observed that changing

const base: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            allOf: [
                { $ref: '/test/multiple/allOf/oneOf/team' },
                { $ref: '/test/multiple/allOf/oneOf/player' },
            ],
        };

to

const base: JsonSchemaDraft07.Schema = {
            $id: '/test/multiple/allOf/oneOf',
            $schema: 'http://json-schema.org/draft-07/schema#',
            type: 'object',
            allOf: [
                { 
                  oneOf: [
                    { $ref: '/test/multiple/allOf/oneOf/team' },
                  ],
                },
                {
                  oneOf: [
                    { $ref: '/test/multiple/allOf/oneOf/player' },
                  ]
                }
            ],
        };

will change the output type to (OneOf.Team | OneOf.Player) instead of (OneOf.Team1 | OneOf.Team2 | OneOf.Player1 | OneOf.Player2), so there's probably a way to 'fool' it with the existing code, but i wasn't able to find it.

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

No branches or pull requests

2 participants