Skip to content

fix(api): keep Quran Reflect query casing on content-catalog paths - #110

Open
marouanebatrone wants to merge 1 commit into
quran:mainfrom
marouanebatrone:fix/quranreflect-catalog-query-casing
Open

marouanebatrone wants to merge 1 commit into
quran:mainfrom
marouanebatrone:fix/quranreflect-catalog-query-casing

Conversation

@marouanebatrone

Copy link
Copy Markdown
Contributor

Problem

The content catalog includes the public Quran Reflect reads: postsControllerFeed, postsControllerFindOne, postsControllerGetUserPost, postsControllerGetComments and postsControllerGetAllComment. Their paths start with /quran-reflect/v1, so buildServiceUrl (packages/api/src/sdk/fetcher.ts) sends them to Quran Reflect through the gateway. But it still picks the query key casing from the calling service, so these calls have their keys converted to snake_case as if they were content calls:

await client.content.v4.raw.postsControllerFeed({
  query: {
    "filter[references][0][chapterId]": 2,
    "filter[references][0][from]": 255,
    "filter[references][0][to]": 255,
    "filter[verifiedOnly]": true,
  },
});
// GET …/quran-reflect/v1/posts/feed?filter[references][0][chapter_id]=2&…&filter[verified_only]=true

await client.content.v4.raw.postsControllerGetUserPost({
  path: { id: "abc" },
  query: { sortBy: "popular", postTypeIds: [1, 2] },
});
// GET …/quran-reflect/v1/posts/user-posts/abc?sort_by=popular&post_type_ids=1,2

The content spec (openAPI/content/v4.json) and the Quran Reflect spec (openAPI/user-related-apis/v1.json) both declare these parameters in camelCase: sortBy, postTypeIds, filter[verifiedOnly], filter[postTypeIds], filter[tagsOperator], filter[referencesOperator] and filter[references][0][chapterId]. The same operations called through client.quranReflect.v1.raw already send them as written. So reading an ayah's reflections through the content catalog with an app token sends the reference and verified-only filters under names the API doesn't define.

Change

  • buildServiceUrl now takes the query key casing from the service that serves the path. A path that starts with another service's gateway prefix is served by that service, so the content catalog's Quran Reflect reads keep their camelCase keys, as they already do through client.quranReflect.v1.raw.
  • Only cross-service paths are affected; in the current catalogs, those are the five Quran Reflect reads above. Content, search and analytics paths still send snake_case keys.
  • isCrossServiceGatewayPath() becomes gatewayPathService(), which returns the service the path prefix belongs to, so the prefix is only matched once.
  • A patch changeset is added.

Tests

Two new tests in test/raw-operation.test.ts:

  • buildServiceUrl("content", "/quran-reflect/v1/…") keeps filter[references][0][chapterId], filter[verifiedOnly], postTypeIds and sortBy, while content paths such as /chapters/1/info and /content/api/v4/verses/by_key/1:1 still send include_resources, resource_id and per_page.
  • The same feed query arrives with identical camelCase keys whether it's sent through client.content.v4.raw.postsControllerFeed() (app token) or client.quranReflect.v1.raw.postsControllerFeed() (user token).

Both fail on main with expected null to be '2', because the key arrived as filter[references][0][chapter_id], and pass with the fix.

Checks run locally on current main (Node 26.8.1, pnpm 9.15.4)

  • pnpm test (vitest, build and package smoke test): 29 files, 358 tests passed.
  • pnpm lint --quiet, pnpm typecheck and pnpm build --filter=./packages/*: passed.
  • pnpm --filter @quranjs/api size: within limits, unchanged from main.
  • Prettier: the test file and changeset need no changes. fetcher.ts already differs from Prettier's output on main in unrelated places, and this diff adds no new differences.

The content catalog includes the public Quran Reflect reads
(`postsControllerFeed`, `postsControllerGetUserPost` and the post and
comment reads). Their paths start with `/quran-reflect/v1`, so the
request goes to Quran Reflect through the gateway, but `buildServiceUrl`
chose the query key casing from the calling service. As a content call,
every key was decamelized: `sortBy`, `postTypeIds`, `filter[verifiedOnly]`
and `filter[references][0][chapterId]` went out as `sort_by`,
`post_type_ids`, `filter[verified_only]` and
`filter[references][0][chapter_id]`. Both the content and the Quran
Reflect specs declare the camelCase names, and the same operations
through `client.quranReflect.v1.raw` already keep them.

Take the casing from the service that serves the path. Only
cross-service paths change; content, search and analytics paths still
send snake_case keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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

Successfully merging this pull request may close these issues.

1 participant