500 when itemId cannot be found

Describe the issue:

  • when querying a record by ID with the CDA, I’m getting an error if the ID is not known. I would expect to simple return null instead. Not a 500.

    My request

  pagePlan(filter: {id: {eq: "Ow5qwxbpTcd-yq-ZrgjlHWA"}}) {
    id
  }

error response

 "errors": [
    {
      "message": "cannot coerce `\"Ow5qwxbpTcd-yq-ZrgjlHWA\"` to ItemId",
      "locations": [
        {
          "line": 2,
          "column": 25
        }
      ],
      "path": [
        "query",
        "pagePlan",
        "filter",
        "id",
        "eq"
      ],
      "extensions": {
        "code": "argumentLiteralsIncompatible",
        "typeName": "CoercionError"
      }
    }
  ]

@thomas.vanleynseele it will normally return null IF the requested ID is a valid but nonexistent DatoCMS ID. It will 500 only if the ID is an invalid string that cannot be a Dato ID. This is a serverside type safety thing: Not all alphanumeric strings are valid Dato IDs, and they must match a certain format to be considered valid.

See this previous thread for more details: Best way to handle "not found" on `ItemId` lookups - #4 by roger

But TLDR you can import isValidId() from https://github.com/datocms/js-rest-api-clients/blob/main/packages/cma-client/src/utilities/id.ts#L34-L71 and use that to check your id param before querying the API:

export function isValidId(id: string) {
  // For backward compatibility, first check to see if this is an older-style integer ID formerly used by Dato
  if (/^\d+$/.test(id)) {
    const intId = BigInt(id);
    const maxDatoIntegerId = 281474976710655; // Max 6-byte/48-bit unsigned int
    return intId <= maxDatoIntegerId;
  }

  const bytes = fromUrlSafeBase64toUint8Array(id);

  // UUIDs are 16 bytes
  if (bytes.length !== 16) {
    return false;
  }

  // The variant field determines the layout of the UUID
  // (see RFC 4122, sec. 4.1.1)

  const variant = bytes.at(8)!;

  // Variant must be the one described in RFC 4122
  if ((variant & 0b11000000) !== 0b10000000) {
    return false;
  }

  // The version number is in the most significant 4 bits
  // of the time stamp (see RFC 4122, sec. 4.1.3)

  const version = bytes.at(6)! >> 4;

  // Version number must be 4 (randomly generated)
  if (version !== 0x4) {
    return false;
  }

  return true;
}

function fromUrlSafeBase64toUint8Array(urlSafeBase64: string): Uint8Array {
  // Convert from URL-safe format (see RFC 4648, sec. 5)
  const base64 = urlSafeBase64
    // Replace - with +
    .replace(/-/g, '+')
    // Replace _ with /
    .replace(/_/g, '/');

  return typeof Buffer === 'undefined'
    ? Uint8Array.from(atob(base64), (c) => c.charCodeAt(0))
    : new Uint8Array(Buffer.from(base64, 'base64'));
}

(I provided the snippet just for clarity, but it’s safer to import directly from our lib if you can, in case it ever changes in the future.)

Thanks Roger,

All clear :+1:

1 Like