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

unevaluatedItems boolean schema error, Can't resolve $ref with component schemas #1233

Open
jeremyfiel opened this issue Aug 15, 2023 · 6 comments · Fixed by #1295
Open

unevaluatedItems boolean schema error, Can't resolve $ref with component schemas #1233

jeremyfiel opened this issue Aug 15, 2023 · 6 comments · Fixed by #1295
Labels
p2 Type: Bug Something isn't working

Comments

@jeremyfiel
Copy link
Contributor

jeremyfiel commented Aug 15, 2023

Describe the bug

Two issues here:

1. unevaluatedItems is reporting an error Expected type 'Schema' but got 'Boolean'
related to #457 and #532, I don't see unevaluatedItems updated similar to additionalProperties where it can be a boolean or a schema.

  1. Can't resolve $ref for Challenge and File component schemas.
    These two referenced schemas are not resolved but they appear to be correctly defined in items array. I tried using https://json-schema.hyperjump.io and it says my schema is valid.

To Reproduce
Steps to reproduce the behavior:

  1. Given this redocly.yaml file
extends:
 - recommended
  1. And this OpenAPI file(s)
OAS 3.1 schema
{
    "openapi": "3.1.0",
    "info": {
        "title": "freeCodeCamp Classroom",
        "version": "0.0.1",
        "contact": {
            "name": "Classroom Team",
            "email": ""
        },
        "license": {
            "name": "",
            "url": ""
        }
    },
    "servers": [
        {
            "url": "https://api.freecodecamp.org"
        }
    ],
    "security": [],
    "tags": [
        {
            "name": "Classroom user data",
            "description": ""
        }
    ],
    "paths": {
        "/classroom/userData": {
            "get": {
                "summary": "",
                "description": "",
                "operationId": "getUserData",
                "tags": [
                    "Classroom user data"
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/header_accept"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/userData"
                                },
                                "examples": {
                                    "Classroom_User_Data": {
                                        "$ref": "#/components/examples/userData"
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/400"
                    },
                    "401": {
                        "$ref": "#/components/responses/401"
                    },
                    "403": {
                        "$ref": "#/components/responses/403"
                    },
                    "404": {
                        "$ref": "#/components/responses/404"
                    },
                    "500": {
                        "$ref": "#/components/responses/500"
                    },
                    "503": {
                        "$ref": "#/components/responses/503"
                    }
                }
            },
            "post": {
                "summary": "",
                "description": "",
                "operationId": "queryUserData",
                "tags": [
                    "Classroom user data"
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/header_contentType"
                    }
                ],
                "requestBody": {
                    "$ref": "#/components/requestBodies/userDataRequest"
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/userData"
                                },
                                "examples": {
                                    "Classroom_User_Data_Request": {
                                        "$ref": "#/components/examples/userData"
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/400"
                    },
                    "401": {
                        "$ref": "#/components/responses/401"
                    },
                    "403": {
                        "$ref": "#/components/responses/403"
                    },
                    "404": {
                        "$ref": "#/components/responses/404"
                    },
                    "500": {
                        "$ref": "#/components/responses/500"
                    },
                    "503": {
                        "$ref": "#/components/responses/503"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "problem_json_error": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string"
                    },
                    "status": {
                        "type": "number",
                        "minimum": 200,
                        "maximum": 511
                    },
                    "title": {
                        "type": "string"
                    },
                    "detail": {
                        "type": "string"
                    },
                    "instance": {
                        "type": "string",
                        "format": "uri-reference"
                    }
                }
            },
            "userData": {
                "$id": "https://api.freecodecamp.org/classroom/userData",
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "description": "list of students",
                "type": "array",
                "uniqueItems": true,
                "items": {
                    "type": "object",
                    "properties": {
                        "user": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "type": "string"
                                },
                                "email": {
                                    "description": "student identifier",
                                    "type": "string",
                                    "format": "email"
                                }
                            }
                        },
                        "certifications": {
                            "description": "name of the certification (\"2022/responsive-web-design\")",
                            "type": "array",
                            "uniqueItems": true,
                            "minItems": 1,
                            "items": {
                                "description": "name of the certification block",
                                "type": "object",
                                "propertyNames": {
                                    "pattern": "^[A-Za-z0-9][\/A-Za-z0-9-]*[A-Za-z]$"
                                },
                                "additionalProperties": {
                                    "type": "object",
                                    "unevaluatedProperties": false,
                                    "properties": {
                                        "blocks": {
                                            "description": "List of the challenges/blocks inside of a certification",
                                            "type": "array",
                                            "uniqueItems": true,
                                            "items": {
                                                "type": "object",
                                                "minProperties": 1,
                                                "additionalProperties": {
                                                    "description": "name of the block (learn-css-...)",
                                                    "type": "object",
                                                    "properties": {
                                                        "completedChallenges": {
                                                            "type": "array",
                                                            "description": "list of challenges completed by the student inside of this block",
                                                            "uniqueItems": true,
                                                            "items": {
                                                                "$ref": "#/$defs/Challenge"
                                                            }
                                                        }
                                                    },
                                                    "required": [
                                                        "completedChallenges"
                                                    ]
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "required": [
                        "user",
                        "certifications"
                    ]
                },
                "$defs": {
                    "Challenge": {
                        "description": "A representation of an individual challenge",
                        "type": "object",
                        "unevaluatedProperties": false,
                        "required": [
                            "id",
                            "challengeName",
                            "completedDate"
                        ],
                        "properties": {
                            "id": {
                                "description": "A unique identifier of a completed course",
                                "type": "string",
                                "minLength": 1
                            },
                            "challengeName": {
                                "type": "string"
                            },
                            "completedDate": {
                                "description": "A Unix timestamp representation",
                                "type": "integer"
                            },
                            "files": {
                                "description": "A list of files pertaining to the challenge",
                                "type": "array",
                                "uniqueItems": true,
                                "unevaluatedItems": false,
                                "items": {
                                    "$ref": "#/$defs/File"
                                }
                            }
                        }
                    },
                    "File": {
                        "description": "A representation of a file",
                        "type": "string",
                        "format": "binary"
                    }
                }
            }
        },
        "parameters": {
            "header_accept": {
                "name": "accept",
                "description": "",
                "in": "header",
                "schema": {
                    "type": "string"
                },
                "examples": {
                    "accept_header": {
                        "summary": "A standard header indicating the accepted media type",
                        "value": "application/json"
                    }
                }
            },
            "header_contentType": {
                "name": "content-type",
                "description": "",
                "in": "header",
                "schema": {
                    "type": "string"
                },
                "examples": {
                    "content-type": {
                        "summary": "A standard header indicating the content media type",
                        "value": "application/json"
                    }
                }
            }
        },
        "requestBodies": {
            "userDataRequest": {
                "description": "",
                "content": {
                    "application/json": {
                        "schema": {
                            "$schema": "https://json-schema.org/draft/2020-12/schema",
                            "title": "UserDataRequest",
                            "description": "Queries a collection of users and certifications",
                            "type": "object",
                            "unevaluatedProperties": false,
                            "properties": {
                                "users": {
                                    "type": "array",
                                    "uniqueItems": true,
                                    "minItems": 1,
                                    "prefixItems": [
                                        {
                                            "type": "object",
                                            "unevaluatedProperties": false,
                                            "properties": {
                                                "id": {
                                                    "type": "string"
                                                }
                                            },
                                            "required": [
                                                "id"
                                            ]
                                        }
                                    ],
                                    "items": {
                                        "description": "name of the certification block",
                                        "type": "string",
                                        "pattern": "^[A-Za-z0-9][\/A-Za-z0-9-]*[A-Za-z]$"
                                    }
                                }
                            },
                            "required": [
                                "users"
                            ]
                        },
                        "examples": {
                            "Classroom_User_Data_Request": {
                                "$ref": "#/components/examples/userDataRequest"
                            }
                        }
                    }
                },
                "required": true
            }
        },
        "responses": {
            "400": {
                "description": "Bad Request",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/problem_json_error"
                        },
                        "examples": {
                            "Bad_request_problem_json": {
                                "summary": "A problem+json representation of an error",
                                "$ref": "#/components/examples/Bad_request_problem_json"
                            }
                        }
                    }
                }
            },
            "401": {
                "description": "Unauthorized",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/problem_json_error"
                        }
                    }
                }
            },
            "403": {
                "description": "Forbidden",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/problem_json_error"
                        }
                    }
                }
            },
            "404": {
                "description": "Not Found",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/problem_json_error"
                        }
                    }
                }
            },
            "500": {
                "description": "Internal Server Error",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/problem_json_error"
                        }
                    }
                }
            },
            "503": {
                "description": "Service Unavailable",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/problem_json_error"
                        }
                    }
                }
            }
        },
        "examples": {
            "userData": {
                "summary": "userData from the Classroom",
                "value": [
                    {
                        "user": {
                            "id": "",
                            "email": "[email protected]"
                        },
                        "certifications": [
                            {
                                "2022/responsive-web-design": {
                                    "blocks": [
                                        {
                                            "learn-basic-css-by-building-a-cafe-menu": {
                                                "completedChallenges": [
                                                    {
                                                        "id": "5f33071498eb2472b87ddee4",
                                                        "challengeName": "Step 1",
                                                        "completedDate": 1475094716730,
                                                        "files": []
                                                    },
                                                    {
                                                        "id": "5f3313e74582ad9d063e3a38",
                                                        "challengeName": "Step 2",
                                                        "completedDate": 1537207306322,
                                                        "files": []
                                                    }
                                                ]
                                            }
                                        }
                                    ]
                                }
                            },
                            {
                                "quality-assurance": {
                                    "blocks": [
                                        {
                                            "advanced-node-and-express": {
                                                "completedChallenges": [
                                                    {
                                                        "id": "5895f700f9fc0f352b528e63",
                                                        "challengeName": "Set up a Template Engine",
                                                        "completedDate": 98448684,
                                                        "files": []
                                                    },
                                                    {
                                                        "id": "5895f70df9fc0f352b528e6a",
                                                        "challengeName": "Create New Middleware",
                                                        "completedDate": 98448643284,
                                                        "files": []
                                                    }
                                                ]
                                            }
                                        },
                                        {
                                            "quality-assurance-and-testing-with-chai": {
                                                "completedChallenges": [
                                                    {
                                                        "id": "587d824a367417b2b2512c46",
                                                        "challengeName": "Learn How JavaScript Assertions Work",
                                                        "completedDate": 47664591,
                                                        "files": []
                                                    }
                                                ]
                                            }
                                        }
                                    ]
                                }
                            }
                        ]
                    }
                ]
            },
            "Bad_request_problem_json": {
                "summary": "A problem+json representation of an error",
                "value": {
                    "type": "Bad Request error",
                    "status": 400,
                    "title": "400 - Bad Request",
                    "detail": "You submitted a request payload with invalid JSON. Please validate your instance and resubmit"
                }
            },
            "userDataRequest": {
                "summary": "",
                "value": {
                    "users": [
                        {
                            "id": "123456"
                        },
                        "2022/responsive-web-design",
                        "quality-assurance"
                    ]
                }
            }
        }
    }
}
  1. Run this command with these arguments... redocly lint --config redocly.yml fcc-classroom-userdata-openapi.json --format stylish
  2. See error
fcc-classroom-userdata-openapi.json:
  254:53  error    spec                  Expected type `Schema` (object) but got `boolean`
  255:42  error    no-unresolved-refs    Can't resolve $ref
  206:70  error    no-unresolved-refs    Can't resolve $ref
  228:21  warning  no-unused-components  Component: "Challenge" is never used.
  261:21  warning  no-unused-components  Component: "File" is never used.

Expected behavior

All component schemas should be referenced and unevaluatedItems: false is a valid schema type.

Redocly Version(s)

Redocly OpenAPI - vs code v0.2.24

Node.js Version(s)

v14.18.1

@jeremyfiel jeremyfiel added the Type: Bug Something isn't working label Aug 15, 2023
@tatomyr
Copy link
Contributor

tatomyr commented Aug 16, 2023

Thanks for the report @jeremyfiel!

@tatomyr tatomyr added the p2 label Aug 16, 2023
@jeremyfiel
Copy link
Contributor Author

further discussion with JSON Schema folks and they mentioned it may be an issue with resolving the base uri $id in the nested schema. It's possible ajv doesn't support this.

this seems to be a very typical use case where it's equivalent to $ref an external schema defined as 2020-12. In this case, it's dereferenced inside the OAS definition but should resolve in the same manner as if it were externally referenced.

@RomanHotsiy
Copy link
Member

This is most likely an error with redocly-cli, not ajv. We don't use AJV for $ref resolution.
We'll take a look. Thanks!

@RomanHotsiy
Copy link
Member

I just checked it and we indeed do not support $id in the nested schema properly.

@RomanHotsiy
Copy link
Member

Created a PR with potential fix here: #1286

@jeremyfiel would you be able to test the fix for your use case?

jeremyfiel added a commit to jeremyfiel/redocly-cli that referenced this issue Oct 12, 2023
Per JSON Schema 2019-09+ spec. this annotation keyword
can be any valid JSON Schema schema. e.g. `boolean` or `schema`
https://json-schema.org/draft/2019-09/json-schema-core#unevaluatedItems

partially fixes Redocly#1233
tatomyr pushed a commit to jeremyfiel/redocly-cli that referenced this issue Oct 13, 2023
Per JSON Schema 2019-09+ spec. this annotation keyword
can be any valid JSON Schema schema. e.g. `boolean` or `schema`
https://json-schema.org/draft/2019-09/json-schema-core#unevaluatedItems

partially fixes Redocly#1233
jeremyfiel added a commit to jeremyfiel/redocly-cli that referenced this issue Oct 13, 2023
Per JSON Schema 2019-09+ spec. this annotation keyword
can be any valid JSON Schema schema. e.g. `boolean` or `schema`
https://json-schema.org/draft/2019-09/json-schema-core#unevaluatedItems

partially fixes Redocly#1233
tatomyr pushed a commit to jeremyfiel/redocly-cli that referenced this issue Oct 13, 2023
Per JSON Schema 2019-09+ spec. this annotation keyword
can be any valid JSON Schema schema. e.g. `boolean` or `schema`
https://json-schema.org/draft/2019-09/json-schema-core#unevaluatedItems

partially fixes Redocly#1233
@jeremyfiel
Copy link
Contributor Author

@tatomyr please reopen for the second item mentioned. @RomanHotsiy was still working on the pr for the embedded $id resolution.

@tatomyr tatomyr reopened this Oct 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p2 Type: Bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants