Skip to content

Update Rosetta API spec and align GraphQL examples with v1.0.40 - #5

Open
sprucely wants to merge 2 commits into
mainfrom
swe/OAS_1.0.40
Open

Update Rosetta API spec and align GraphQL examples with v1.0.40#5
sprucely wants to merge 2 commits into
mainfrom
swe/OAS_1.0.40

Conversation

@sprucely

@sprucely sprucely commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added support for group, membership, student association, and position association queries.
    • Added metadata and structured result responses for people, colleges, and majors.
    • Added filtering options for people, colleges, majors, groups, and associations.
    • Payroll associations now include manager and employee identifiers.
  • Improvements

    • Improved handling of varied collection values during response deserialization.
    • Updated GraphQL examples and documentation for the latest API schema.
    • Improved GraphQL integration handling for quota-related responses.

@sprucely
sprucely requested a review from jSylvestre August 7, 2026 23:35
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sprucely, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155b8027-ef44-4eea-bd3b-8c16da9d5830

📥 Commits

Reviewing files that changed from the base of the PR and between a40fd37 and 93a6fb3.

📒 Files selected for processing (6)
  • IntegrationTests/LenientTypedCollectionConverterTests.cs
  • IntegrationTests/RosettaApiTests.cs
  • IntegrationTests/RosettaClientFixture.cs
  • README.md
  • UCD.Rosetta.Client/Core/Converters/LenientTypedCollectionConverter.cs
  • update-spec.sh
📝 Walkthrough

Walkthrough

The GraphQL schema now uses filter inputs and result wrappers, and adds group and association queries. Examples and integration tests use the new API shape. JSON collection conversion accepts broader scalar and primitive values. Specification extraction supports flexible GraphQL fences.

Changes

GraphQL client contracts and validation

Layer / File(s) Summary
GraphQL schema contracts
specs/rosetta-api.graphql
The schema adds filter inputs, result wrappers, metadata, group types, association records, and new query fields.
Integration test discovery and GraphQL execution
IntegrationTests/RosettaClientFixture.cs, IntegrationTests/RosettaApiTests.cs
Integration tests discover valid filter values from configured data or sampled People records. GraphQL tests use Results selectors and retry quota-related failures.
Updated client examples and documentation
Example/Program.cs, README.md
Examples use filter input objects and nested Results selectors. Query-variable and REST error-handling examples use the updated API arguments.

Serialization and specification support

Layer / File(s) Summary
Lenient typed collection conversion
UCD.Rosetta.Client/Core/Converters/LenientTypedCollectionConverter.cs
Collection deserialization supports scalar, primitive, nullable, string, and object values. Invalid elements are skipped without throwing.
Specification version and fence extraction
update-spec.sh
The script targets version 1.0.40 and accepts optional whitespace, labels, and carriage returns around GraphQL fences.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Example
  participant RosettaAPI
  participant Results
  Example->>RosettaAPI: send query with PeopleFilterInput
  RosettaAPI-->>Results: return PeopleResult
  Example->>Results: select Results and person fields
Loading

Possibly related PRs

Suggested reviewers: jsylvestre

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main specification update and GraphQL example alignment to v1.0.40.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch swe/OAS_1.0.40

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
specs/rosetta-api.graphql (1)

310-348: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Result wrapper naming is inconsistent.

CollegeResult is singular while MajorsResult is plural, and GroupSourceResults uses a plural Results suffix. GroupByIdResult.results returns a single object while every other wrapper returns a list. These names become generated C# types, so the inconsistency is visible to consumers. If the schema is generated from the upstream service, keep it as is and ignore this note. If the schema is authored here, align the suffixes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/rosetta-api.graphql` around lines 310 - 348, If this schema is authored
locally, align the result wrapper naming in PeopleResult and related types: use
consistent singular Result suffixes and make GroupByIdResult.results return a
list like the other wrappers, updating affected references accordingly. If the
schema is generated from the upstream service, leave these definitions
unchanged.
IntegrationTests/RosettaApiTests.cs (1)

354-359: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Detect quota errors from the errors collection, not from the serialized payload.

IsQuotaExceeded<TResponse> serializes the entire response to JSON on every typed query, including successful ones. This has two drawbacks. It allocates and copies the full result set on the success path. It also matches the quota strings against response data, so a person record or a college title that contains the text triggers a false skip.

Inspect the GraphQL errors instead of the whole response.

♻️ Suggested direction
-    private static bool IsQuotaExceeded<TResponse>(TResponse response)
-    {
-        var responseJson = JsonSerializer.Serialize(response);
-        return responseJson.Contains("status code 429", StringComparison.OrdinalIgnoreCase)
-            || responseJson.Contains("Quota has been exceeded", StringComparison.OrdinalIgnoreCase);
-    }
+    private static bool IsQuotaExceeded<TResponse>(TResponse response)
+    {
+        // Serialize only the errors, not the full data payload.
+        var errors = (response as dynamic)?.Errors;
+        if (errors is null)
+            return false;
+
+        var errorsJson = JsonSerializer.Serialize(errors);
+        return errorsJson.Contains("status code 429", StringComparison.OrdinalIgnoreCase)
+            || errorsJson.Contains("Quota has been exceeded", StringComparison.OrdinalIgnoreCase);
+    }

A stronger option is to constrain TResponse to the concrete ZeroQL result type so the Errors property is accessed without dynamic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@IntegrationTests/RosettaApiTests.cs` around lines 354 - 359, Update
IsQuotaExceeded<TResponse> to inspect the concrete ZeroQL response’s Errors
collection rather than serializing the entire response. Constrain TResponse to
the appropriate result type so Errors can be accessed statically, and detect the
existing quota indicators only within error messages while preserving the
boolean behavior for quota and non-quota responses.
IntegrationTests/RosettaClientFixture.cs (1)

18-18: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

The cached sample also caches failures.

Lazy<Task<T>> stores the returned Task. If the first PeopleAsync(limit: 25) call fails, for example with HTTP 429, the faulted task stays cached. Every later call to GetPeopleSampleAsync then rethrows the same exception, so all discovery-based tests in the class fail instead of skipping or retrying. This PR adds quota retry handling for GraphQL, so the same transient condition is expected on the REST path.

Consider returning an empty collection on failure, or resetting the Lazy when the task faults.

♻️ Option: do not cache a faulted result
-    private readonly Lazy<Task<ICollection<Person>>> _peopleSample;
+    private Lazy<Task<ICollection<Person>>> _peopleSample = null!;
-        _peopleSample = new Lazy<Task<ICollection<Person>>>(() => Client.Api.PeopleAsync(limit: 25));
+        _peopleSample = new Lazy<Task<ICollection<Person>>>(CreatePeopleSampleTask);
-    public Task<ICollection<Person>> GetPeopleSampleAsync() => _peopleSample.Value;
+    public Task<ICollection<Person>> GetPeopleSampleAsync() => _peopleSample.Value;
+
+    private async Task<ICollection<Person>> CreatePeopleSampleTask()
+    {
+        try
+        {
+            return await Client.Api.PeopleAsync(limit: 25);
+        }
+        catch
+        {
+            // Allow a later call to retry instead of replaying the cached failure.
+            _peopleSample = new Lazy<Task<ICollection<Person>>>(CreatePeopleSampleTask);
+            throw;
+        }
+    }

Also applies to: 42-42, 52-52

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@IntegrationTests/RosettaClientFixture.cs` at line 18, Update the cached
people-sample flow around _peopleSample and GetPeopleSampleAsync so transient
failures from PeopleAsync(limit: 25) are not retained by
Lazy<Task<ICollection<Person>>>. On a fault, reset the lazy before retrying or
return an empty collection, while preserving successful-result caching and the
existing discovery-test behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@IntegrationTests/RosettaApiTests.cs`:
- Around line 232-239: Replace the [Fact] attributes with [SkippableFact] on
GraphqlAsync_WithPeopleQuery_ReturnsResult,
GraphQL_TypedPeopleQuery_ReturnsResults, and
GraphQL_TypedCollegesQuery_ReturnsAllColleges so Skip.If(true) from their
quota-skipping helpers is reported as a skipped test rather than a failure.

In `@README.md`:
- Around line 216-224: Update the second example’s response variable to a
distinct name so it does not redeclare response, and revise the ❌ comment to
identify the captured _options.LoginId property access as the ZeroQL compilation
error; do not imply that inline PeopleFilterInput construction is itself
invalid.

In `@UCD.Rosetta.Client/Core/Converters/LenientTypedCollectionConverter.cs`:
- Line 24: Remove the JsonTokenType-based prefilter from
LenientTypedCollectionConverter.CanReadCurrentTokenAsElement. In the collection
parsing flow, copy the Utf8JsonReader and attempt JsonSerializer.Deserialize<T>
on the copy; on failure, skip the original reader and continue, and on success
assign the value and advance the original reader from the copy. Preserve support
for nullable primitives, object, Guid, DateTime, arrays, custom converters, and
scalar ICollection<T> values.

In `@update-spec.sh`:
- Around line 47-48: Update the fence-matching expressions in the script’s
extraction logic to allow leading blank-space before both opening and closing
Markdown fences, while preserving the existing optional graphql label and
termination behavior.

---

Nitpick comments:
In `@IntegrationTests/RosettaApiTests.cs`:
- Around line 354-359: Update IsQuotaExceeded<TResponse> to inspect the concrete
ZeroQL response’s Errors collection rather than serializing the entire response.
Constrain TResponse to the appropriate result type so Errors can be accessed
statically, and detect the existing quota indicators only within error messages
while preserving the boolean behavior for quota and non-quota responses.

In `@IntegrationTests/RosettaClientFixture.cs`:
- Line 18: Update the cached people-sample flow around _peopleSample and
GetPeopleSampleAsync so transient failures from PeopleAsync(limit: 25) are not
retained by Lazy<Task<ICollection<Person>>>. On a fault, reset the lazy before
retrying or return an empty collection, while preserving successful-result
caching and the existing discovery-test behavior.

In `@specs/rosetta-api.graphql`:
- Around line 310-348: If this schema is authored locally, align the result
wrapper naming in PeopleResult and related types: use consistent singular Result
suffixes and make GroupByIdResult.results return a list like the other wrappers,
updating affected references accordingly. If the schema is generated from the
upstream service, leave these definitions unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ff9804ca-28ac-4c0a-8081-b09e1cfe03e6

📥 Commits

Reviewing files that changed from the base of the PR and between 9309109 and a40fd37.

⛔ Files ignored due to path filters (1)
  • UCD.Rosetta.Client/Generated/RosettaApiClient.g.cs is excluded by !**/generated/**
📒 Files selected for processing (8)
  • Example/Program.cs
  • IntegrationTests/RosettaApiTests.cs
  • IntegrationTests/RosettaClientFixture.cs
  • README.md
  • UCD.Rosetta.Client/Core/Converters/LenientTypedCollectionConverter.cs
  • specs/rosetta-api.graphql
  • specs/rosetta-api.json
  • update-spec.sh

Comment thread IntegrationTests/RosettaApiTests.cs Outdated
Comment thread README.md
Comment thread update-spec.sh Outdated
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.

2 participants