Skip to content

fix: sdk exploded syntax - take into account request plugin options #3076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 9, 2025

Conversation

sdo-1A
Copy link
Contributor

@sdo-1A sdo-1A commented Apr 2, 2025

Proposed change

When we previously implemented the exploded syntax for SDK parameter serialization, we removed the possibility of taking into account the query parameters that can be updated in the Request Plugins - which needs to be fixed. Here is a possible fix, which prepares the URL using the request plugins options (which could update the query parameters).

Also, the parameter queryParamSerialization has been added to RequestOptionsParameters, which can be used in order to serialize the query parameter modifications that are potentially made. In another PR, I will add a method allowing to deserialize the parameters, which facilitates the modifications in the Request Plugins.

Related issues

- No issue associated -

Copy link

nx-cloud bot commented Apr 2, 2025

View your CI Pipeline Execution ↗ for commit f00d17a.

Command Status Duration Result
nx run-many --target=test-e2e ❌ Failed 9m 56s View ↗
nx run-many --target=test-int ✅ Succeeded 20m 54s View ↗
nx run-many --target=build --projects=eslint-pl... ✅ Succeeded 1s View ↗
nx run-many --target=publish --nx-bail --userco... ✅ Succeeded 31s View ↗
nx run-many --target=prepare-publish --exclude-... ✅ Succeeded 1m 26s View ↗
nx run-many --target=build,build-swagger ✅ Succeeded 6m 8s View ↗
nx affected --target=test --collectCoverage ✅ Succeeded 19s View ↗
nx run ama-sdk-schematics:build-swagger ✅ Succeeded 3s View ↗
Additional runs (2) ✅ Succeeded ... View ↗

☁️ Nx Cloud last updated this comment at 2025-04-09 15:13:07 UTC

Copy link

codecov bot commented Apr 2, 2025

Codecov Report

Attention: Patch coverage is 91.48936% with 4 lines in your changes missing coverage. Please review.

Project coverage is 69.98%. Comparing base (c894748) to head (f00d17a).
Report is 2 commits behind head on release/12.1.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...kages/@ama-sdk/core/src/fwk/param-serialization.ts 89.47% 0 Missing and 2 partials ⚠️
...ges/@ama-sdk/core/src/clients/api-beacon-client.ts 0.00% 1 Missing ⚠️
...ages/@ama-sdk/core/src/clients/api-fetch-client.ts 0.00% 1 Missing ⚠️

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sdo-1A sdo-1A force-pushed the fix/request-plugin-param-serialization branch from 335df01 to c3f2dee Compare April 2, 2025 14:09
@sdo-1A sdo-1A marked this pull request as ready for review April 2, 2025 15:25
@sdo-1A sdo-1A requested a review from a team as a code owner April 2, 2025 15:25
cpaulve-1A
cpaulve-1A previously approved these changes Apr 2, 2025
fpaul-1A
fpaul-1A previously approved these changes Apr 2, 2025
@sdo-1A sdo-1A dismissed stale reviews from fpaul-1A and cpaulve-1A via 3fcbd65 April 4, 2025 16:09
@sdo-1A sdo-1A force-pushed the fix/request-plugin-param-serialization branch from c3f2dee to 3fcbd65 Compare April 4, 2025 16:09
return serializedParamValue.split('&').reduce((obj: { [key: string]: PrimitiveType }, currentValue) => {
const [key, value] = currentValue.split('=');
// NOTE: The key of an object in deepObject style is surrounded by opening and closing square brackets
obj[paramSerialization.style === 'deepObject' ? key.split(OPENING_SQUARE_BRACKET_URL_CODE)[1].split(CLOSING_SQUARE_BRACKET_URL_CODE)[0] : key] = value;
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably consider also if the character is not encoded or at least document it

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
obj[paramSerialization.style === 'deepObject' ? key.split(OPENING_SQUARE_BRACKET_URL_CODE)[1].split(CLOSING_SQUARE_BRACKET_URL_CODE)[0] : key] = value;
obj[paramSerialization.style === 'deepObject' ? key.match(`^${OPENING_SQUARE_BRACKET_URL_CODE})([^${CLOSING_SQUARE_BRACKET_URL_CODE}]+)`)[1]` : key] = value;

Copy link
Contributor Author

@sdo-1A sdo-1A Apr 7, 2025

Choose a reason for hiding this comment

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

According to the OpenAPI documentation, special characters should be encoded since this is required by RFC6570 and RFC3986, but I can document this yes

Copy link
Contributor

Choose a reason for hiding this comment

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

Please document it just in case

}
return obj;
}, {} as { [key: string]: PrimitiveType });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You should log something if the combination exploded/style is not supported

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I previously added logs but we had decided these would saturate the flow, but we can rediscuss this if you find it beneficial

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After discussion, we decided to add these logs in another PR if necessary

Comment on lines 427 to 466
return splitObject.reduce((obj: { [key: string]: PrimitiveType }, currentValue, index, array) => {
if (index % 2 === 0) {
obj[currentValue] = array[index + 1];
}
return obj;
}, {} as { [key: string]: PrimitiveType });
Copy link
Contributor

Choose a reason for hiding this comment

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

since this is used several times we could extract the code in a dedicated function

export function objectFromPairwiseArray<T>(inputArray: T[]): Record<string, T> {
  return inputArray.reduce((obj: { [key: string]: T}, currentValue, index, array) => {
    if (index % 2 === 0) {
      obj[currentValue] = array[index + 1];
    }
    return obj;
  }, []);
}
Suggested change
return splitObject.reduce((obj: { [key: string]: PrimitiveType }, currentValue, index, array) => {
if (index % 2 === 0) {
obj[currentValue] = array[index + 1];
}
return obj;
}, {} as { [key: string]: PrimitiveType });
return objectFromPairwiseArray(splitObject);

or something like that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is used only twice (deserialization of object query and path parameters), I am not sure if it is worth creating another function in the code but if it makes it easier to read/understand then yes I can make the update

export function deserializeObjectPathParams(serializedParamValue: string, paramSerialization: ParamSerialization) {
const splitObject: string[] = paramSerialization.style === 'matrix' && paramSerialization.explode
? serializedParamValue.substring(1).split(';')
: deserializeArrayPathParams(serializedParamValue, paramSerialization)!;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you use the ArrayPathParams method to deserialize an object?

Copy link
Contributor

Choose a reason for hiding this comment

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

Even if this is a similar logic, it seems confusing when reading the code.

Copy link
Contributor Author

@sdo-1A sdo-1A Apr 7, 2025

Choose a reason for hiding this comment

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

Since the delimiter of the serialized object parameter is the same as the delimiter of array parameters:
https://swagger.io/specification/#style-examples
I did this to avoid code duplicates

Copy link
Contributor Author

@sdo-1A sdo-1A Apr 7, 2025

Choose a reason for hiding this comment

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

@cpaulve-1A I added a comment to explain, hoping this makes it a bit clearer, what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would rename the method to avoid confusion or duplicate code.

Copy link
Contributor

Choose a reason for hiding this comment

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

You could rename it something like splitParameterElements

return obj;
}, {} as { [key: string]: PrimitiveType });
}
return splitObject.reduce((obj: { [key: string]: PrimitiveType }, currentValue, index, array) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

We could have a comment here to explain the logic between the alternative key/value pairs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as the comment below, I added the link to the OpenAPI documentation in the function description but I can add description comments if this is not enough

Copy link
Contributor

@cpaulve-1A cpaulve-1A Apr 7, 2025

Choose a reason for hiding this comment

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

   myParam.matchAll(`${outerDelimiter}?[^${innerDelimiter}]+${innerDelimiter}[^${outerDelimiter}]+${outerDelimiter}?`)
.reduce(obj, serializedProperty => {
   const splitProperty = serializedProperty.match('^${outerDelimiter}?([^${innerDelimiter}]+)${innerDelimiter}([^${outerDelimiter}]+)${outerDelimiter}?
});
  obj[splitProperty[1]] = splitProperty[2];

But I am not sure it is clearer (it works for all the use case I think if you remove the prefix)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cpaulve-1A let me know if the added comments make it a bit easier to understand the logic

@sdo-1A sdo-1A force-pushed the fix/request-plugin-param-serialization branch 2 times, most recently from f0bc897 to baa7fc6 Compare April 7, 2025 15:37
@sdo-1A
Copy link
Contributor Author

sdo-1A commented Apr 7, 2025

After reflection, we have decided to implement the deserialization of the parameters in a separate PR since these functions are beneficial but non-essential. This will allow further time for discussions on the implementation.
Also, this deserialization feature does not need to target release/12.1 necessarily so this new PR will target the main branch.

UPDATE: The separate has been created: #3116

cpaulve-1A
cpaulve-1A previously approved these changes Apr 7, 2025
@cpaulve-1A cpaulve-1A self-requested a review April 7, 2025 16:06
@cpaulve-1A cpaulve-1A dismissed their stale review April 7, 2025 16:06

did not realize the breaking for our current user

@sdo-1A sdo-1A force-pushed the fix/request-plugin-param-serialization branch from a2b6879 to 55ee0e2 Compare April 8, 2025 15:10
data.queryParams = { ...data.queryParams, ...queryParams };
if (data.paramSerializationOptions?.enableParameterSerialization && !isParamValueRecord(queryParams)) {
data.queryParams = { ...data.queryParams, ...serializeRequestPluginQueryParams(queryParams) };
} else if (isParamValueRecord(queryParams)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we log/throw an error in case both data.paramSerializationOptions?.enableParameterSerialization and isParamValueRecord(queryParams) are false?
I assume here the params are silently ignored

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could throw an error yes if you find this helpful, I was not sure when to add error logs or not

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

@sdo-1A sdo-1A force-pushed the fix/request-plugin-param-serialization branch from 55ee0e2 to aea43f6 Compare April 9, 2025 12:04
@@ -100,33 +98,65 @@ export class {{classname}} implements Api {
body = data['{{baseName}}'] as any;
}
{{/bodyParam}}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To facilitate the review of this file, I will explain my logic here (small tip: the file is easier to read in unified mode):

  • All of the query / path parameter logic is grouped together since these params are not needed for the headers or body
  • queryParams will always be created and initialized as an empty object {}
  • paramSerializationOptions will always be created with the value of this.client.options.enableParameterSerialization since this is passed to the request options
  • If there are path parameters, we will create the if statement to allow path parameter serialization
    • Within this if statement, we check if there are query params as well to add their serialization logic if needed
  • If there are no path parameters, the variables basePath and tokenizedUrl are initialized as they were before
    • We check if there are query parameters to add the same logic as before (an if statement with the serialization logic)

@sdo-1A sdo-1A force-pushed the fix/request-plugin-param-serialization branch 3 times, most recently from 94045af to 5a3d24b Compare April 9, 2025 13:23
cpaulve-1A
cpaulve-1A previously approved these changes Apr 9, 2025
@sdo-1A sdo-1A force-pushed the fix/request-plugin-param-serialization branch from 5a3d24b to f00d17a Compare April 9, 2025 14:30
@sdo-1A sdo-1A merged commit 47e7625 into release/12.1 Apr 9, 2025
34 of 35 checks passed
@sdo-1A sdo-1A deleted the fix/request-plugin-param-serialization branch April 9, 2025 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants