{"record":{"id":"18870d67b13d5926","repo":"getredash/redash","slug":"pagination-path-should-be-a-string","errorCode":null,"errorMessage":"'pagination.path' should be a string","messagePattern":"'pagination\\.path' should be a string","errorType":"validation","errorClass":"QueryParseError","httpStatus":null,"severity":"error","filePath":"redash/query_runner/json_ds.py","lineNumber":251,"sourceCode":"\n    @staticmethod\n    def from_config(configuration, pagination):\n        if not isinstance(pagination, dict) or not isinstance(pagination.get(\"type\"), str):\n            raise QueryParseError(\"'pagination' should be an object with a `type` property\")\n\n        if pagination[\"type\"] == \"url\":\n            return UrlPagination(pagination)\n        elif pagination[\"type\"] == \"token\":\n            return TokenPagination(pagination)\n\n        raise QueryParseError(\"Unknown 'pagination.type' {}\".format(pagination[\"type\"]))\n\n\nclass UrlPagination(RequestPagination):\n    def __init__(self, pagination):\n        self.path = pagination.get(\"path\", \"_links.next.href\")\n        if not isinstance(self.path, str):\n            raise QueryParseError(\"'pagination.path' should be a string\")\n\n    def next(self, url, request_options, response):\n        next_url = _apply_path_search(response, self.path, \"\")\n        if not next_url:\n            return False, None, request_options\n\n        next_url = urljoin(url, next_url)\n        return True, next_url, request_options\n\n\nclass TokenPagination(RequestPagination):\n    def __init__(self, pagination):\n        self.fields = pagination.get(\"fields\", [\"next_page_token\", \"page_token\"])\n        if not isinstance(self.fields, list) or len(self.fields) != 2:\n            raise QueryParseError(\"'pagination.fields' should be a list of 2 field names\")\n\n    def next(self, url, request_options, response):\n        next_token = _apply_path_search(response, self.fields[0], \"\")","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/getredash/redash/blob/ca79fe988d81cdac9675b412f3dfcab107bc1fbc/redash/query_runner/json_ds.py#L233-L269","documentation":"UrlPagination validates that the optional 'pagination.path' setting is a string; it defaults to '_links.next.href' (JSON API style). A non-string path (number, object, list) means the config is malformed and the runner cannot locate the next-page URL in responses.","triggerScenarios":"Supplying {\"pagination\": {\"type\": \"url\", \"path\": 123}} or \"path\": [\"next\"] — any non-string value for pagination.path.","commonSituations":"Developer passes a JSON pointer array or a dotted path with a typo in type, or a YAML/JSON templating system injects a number/boolean into the field.","solutions":["Make pagination.path a dotted JSON path string, e.g. \"_links.next.href\" or \"data.next_url\"","Omit path to use the default '_links.next.href'","Verify the path actually exists in the API response payload"],"exampleFix":"// before\n{\"pagination\": {\"type\": \"url\", \"path\": [\"links\", \"next\"]}}\n// after\n{\"pagination\": {\"type\": \"url\", \"path\": \"links.next\"}}","handlingStrategy":"validation","validationCode":"path = cfg.get('pagination', {}).get('path', '_links.next.href')\nassert isinstance(path, str) and path, 'pagination.path must be a dotted string path'","typeGuard":"def valid_path(p: dict) -> bool:\n    path = p.get('path', '_links.next.href')\n    return isinstance(path, str) and len(path) > 0","tryCatchPattern":null,"preventionTips":["Express JSON pointer paths as dotted strings, never arrays","Omit path when the API follows JSON API _links conventions"],"tags":["redash","json-datasource","pagination","type-validation"],"backgroundTag":"query-config-validation-failed","analyzedSha":"ca79fe988d81cdac9675b412f3dfcab107bc1fbc","analyzedAt":"2026-08-28T18:32:34.637Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}