redocly-cli icon indicating copy to clipboard operation
redocly-cli copied to clipboard

Reported Error location does not point at correct node for Parameters

Open jeremyfiel opened this issue 1 year ago • 5 comments

Describe the bug

When an error is produced for a required parameter in the uri, the error location points at the component schema rather than the actual error location in the parameters collection, this makes it difficult to trace which line has the actual error. In this example, the $ref on line 89 should be indicated.

To Reproduce Steps to reproduce the behavior:

  1. Given this redocly.yaml file
extends:
  - recommended
  1. And this OpenAPI file(s)
{
    "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"
                                }
                            }
                        }
                    },
                    "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"
                    },
                    {
                        "$ref": "#/components/parameters/path_userId"
                    }
                ],
                "requestBody": {
                    "$ref": "#/components/requestBodies/userDataRequest"
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/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,
                                "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"
                    }
                }
            },
            "path_userId": {
                "name": "user-id",
                "description": "",
                "in": "path",
                "schema": {
                    "type": "string"
                },
                "required": true
            }
        },
        "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"
                            ]
                        }
                    }
                },
                "required": true
            }
        },
        "responses": {
            "400": {
                "description": "Bad Request",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/problem_json_error"
                        }
                    }
                }
            },
            "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"
                        }
                    }
                }
            }
        }
    }
}
  1. Run this command with these arguments... redocly lint --config redocly.yaml fcc-classroom-userdata-openapi.json

  2. See error

  302:25  error    path-parameters-defined  Path parameter `user-id` is not used in the path `/classroom/userData`.

Expected behavior

The error location line number shall be the correct node where the error occurs, not the referenced location

Redocly Version(s)

redocly --version 1.0.2

Node.js Version(s)

node v14.18.1 (npm v6.14.15)

jeremyfiel avatar Aug 22 '23 02:08 jeremyfiel

Thanks for raising this, but the current behaviour makes sense to me. The problem that the linter describes is that there's a mismatch with the parameter name or where it should be found (path, header, etc). The parameter object itself is therefore the location of the problem, it's a $ref so we point to its declaration, which in this example is in components. The message then provides the context of which path the parameter was expected in - the path and the parameter declaration are the only places in play, the $ref syntax is sort of transparent.

Reporting in the location that makes the error apparent is also consistent with how the other types of error are reported. I'm open to more discussion on this, but for the specific scenario given here, I am not in favour of making changes.

lornajane avatar Aug 22 '23 12:08 lornajane

We used to show "referenced from" for every error if it's referenced. This was gone after some major refactoring but I believe we can bring it back.

I think this would be a great middle-ground.

RomanHotsiy avatar Aug 22 '23 12:08 RomanHotsiy

@lornajane I'm not sure I agree with your comment

The parameter object itself is therefore the location of the problem...

The problem lies in the location where a reusable component has been referenced, the problem itself is not the parameter definition, but the location of its use.

@RomanHotsiy sounds reasonable.

jeremyfiel avatar Aug 22 '23 15:08 jeremyfiel

some additional context on why I raised this,

using the VS Code extension in the design phase, in the problems tab, you can click the error and it will navigate to that line number. the issue I raised is with the current implementation; the error should point you to the location of the error, not the referenced location (components), to quickly fix the problem.

The behavior on the cli may be acceptable with the context provided because you can't interact with the output in the same way as the vsc extension. This is may be considered a separate use case.

jeremyfiel avatar Aug 22 '23 15:08 jeremyfiel

Thank you @jeremyfiel! In such case this is not a bug, but rather enhancement.

We used to show "referenced from" for every error if it's referenced. This was gone after some major refactoring but I believe we can bring it back. I think this would be a great middle-ground.

For me @RomanHotsiy suggestion sounds reasonable. We can use proposed solution in this case.

SmoliyY avatar Aug 23 '23 09:08 SmoliyY