Skip to content

Conversation

pudkrong
Copy link
Contributor

@pudkrong pudkrong commented Oct 11, 2023

PR Type:

Enhancement


PR Description:

This PR introduces validation for the nRF Cloud API responses. It uses the AJV (Another JSON Schema Validator) library and the TypeBox library to define and validate the schema of the responses. In case of validation errors, a ValidationError is thrown. The PR affects multiple files, mainly focusing on implementing the validation in the fetch functions and updating the function calls accordingly.


PR Main Files Walkthrough:

files:

lambda/loggingFetch.ts: Added a ValidationError class and a validate function to validate the schema of the responses. Also, introduced a new function validatedLoggingFetch which validates the response before returning it.
lambda/resolveSingleCellGeoLocation.ts: Replaced loggingFetch with validatedLoggingFetch and updated the function calls to handle the validation errors.
nrfcloud/createAccountDevice.ts: Replaced the fetch call with a call to validatedFetch and handled the validation errors.
nrfcloud/devices.ts: Replaced the fetch call with a call to validatedFetch and handled the validation errors.
nrfcloud/getDeviceShadowFromnRFCloud.ts: Replaced the fetch call with a call to validatedFetch and handled the validation errors.
nrfcloud/validatedFetch.ts: Introduced a new function validatedFetch which validates the response before returning it.

@pudkrong pudkrong linked an issue Oct 11, 2023 that may be closed by this pull request
5 tasks
@pudkrong pudkrong force-pushed the 291-implement-nrf-cloud-api-response-validation branch from b9fffe7 to 6856831 Compare October 11, 2023 14:05
@pudkrong pudkrong temporarily deployed to ci October 11, 2023 14:05 — with GitHub Actions Inactive
@pudkrong pudkrong marked this pull request as ready for review October 11, 2023 14:20
@codiumai-pr-agent-free codiumai-pr-agent-free bot added the enhancement New feature or request label Oct 11, 2023
@codiumai-pr-agent-free
Copy link

PR Analysis

  • 🎯 Main theme: Implementing validation for nRF Cloud API responses
  • 📝 PR summary: This PR introduces validation for the nRF Cloud API responses using the AJV (Another JSON Schema Validator) library and the TypeBox library. The validation is implemented in the fetch functions and the function calls are updated accordingly to handle validation errors.
  • 📌 Type of PR: Enhancement
  • 🧪 Relevant tests added: No
  • ⏱️ Estimated effort to review [1-5]: 4, because the PR introduces significant changes to the codebase, including the addition of new functions and modifications to existing ones. The changes are spread across multiple files, which increases the complexity of the review.
  • 🔒 Security concerns: No security concerns found

PR Feedback

  • 💡 General suggestions: The PR is well-structured and the changes are logically grouped. The use of AJV and TypeBox for validation is a good approach. However, it would be beneficial to add tests to ensure the validation works as expected and does not introduce any regressions.

  • 🤖 Code feedback:

    • relevant file: lambda/loggingFetch.ts
      suggestion: Consider handling the case where 'errors' is not in 'maybeData' in the 'validate' function. This will ensure that the function behaves as expected even when the validation does not return any errors. [important]
      relevant line: '+ if ('errors' in maybeData) {'

    • relevant file: lambda/resolveSingleCellGeoLocation.ts
      suggestion: It would be better to handle the case where 'error' is not in 'maybeResult' in the 'serviceToken' function. This will ensure that the function behaves as expected even when the validation does not return any errors. [important]
      relevant line: '+ if ('error' in maybeResult) {'

    • relevant file: nrfcloud/createAccountDevice.ts
      suggestion: Consider handling the case where 'error' is not in 'maybeResult' in the 'createAccountDevice' function. This will ensure that the function behaves as expected even when the validation does not return any errors. [important]
      relevant line: '+ if ('error' in maybeResult) {'

    • relevant file: nrfcloud/devices.ts
      suggestion: It would be better to handle the case where 'error' is not in 'maybeResult' in the 'devices' function. This will ensure that the function behaves as expected even when the validation does not return any errors. [important]
      relevant line: '+ if ('error' in maybeResult) {'

How to use

To invoke the PR-Agent, add a comment using one of the following commands:
/review [-i]: Request a review of your Pull Request. For an incremental review, which only considers changes since the last review, include the '-i' option.
/describe: Modify the PR title and description based on the contents of the PR.
/improve [--extended]: Suggest improvements to the code in the PR. Extended mode employs several calls, and provides a more thorough feedback.
/ask <QUESTION>: Pose a question about the PR.
/update_changelog: Update the changelog based on the PR's contents.

To edit any configuration parameter from configuration.toml, add --config_path=new_value
For example: /review --pr_reviewer.extra_instructions="focus on the file: ..."
To list the possible configuration parameters, use the /config command.

const { track, metrics } = metricsForComponent('singleCellGeo')

const trackFetch = loggingFetch({ track, log })
const trackFetch = validatedLoggingFetch({ track, log })
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const trackFetch = validatedLoggingFetch({ track, log })
const trackFetch = validatedFetch(loggingFetch({ track, log }))

Since loggingFetch returns a fetch compatible function, validatedLoggingFetch should not care about the fact that it deals with a fetch that is augmented with logging capabilites. Instead make it accept a fetch instance as parameter. (Unix command design principle)

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 will use the existing function, validatedFetch, because

  • It accepts fetch instance
  • However, I need to modify the function to accept 3rd parameter because the existing function will use GET method and Content-Type as application/json as its defaults.


if ('error' in maybeResult) {
log.error('request failed', { error: maybeResult.error })
throw new Error(`Acquiring service token failed: ${maybeResult.error}`)
Copy link
Member

Choose a reason for hiding this comment

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

comment: this swallows the validation errors (because the error is stringified). But OK in this case because error is logged and function is only used in lambda.


if ('error' in maybeResult) {
console.error(`Failed to create account device:`, maybeResult.error)
throw new Error(`Failed to create account device`)
Copy link
Member

Choose a reason for hiding this comment

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

problem: this swallows the error details, change API and return error

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 will re-throw the error from validatedFetch. The error can be either Error object or ValidationError.

async <Schema extends TObject>(
{ resource }: { resource: string },
schema: Schema,
init?: RequestInit,
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated change.

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 change is necessary due to the default behavior uses GET method and Content-Type as application/json

Copy link
Member

Choose a reason for hiding this comment

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

Changed in the refactor.

Math.round(Date.now() / 1000) -
getShadowUpdateTime(deviceShadow.state.metadata),
getShadowUpdateTime(
deviceShadow.state.metadata as Record<string, any>,
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 need to cast deviceShadow.state.metadata to Record<string, any> because getShadowUpdateTime requires

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, recursive typing is not really possible with TypeBox (and JSON schema)

({ track, log }: { track: AddMetricsFn; log: Logger }) =>
async (url: URL, init?: RequestInit): ReturnType<typeof fetch> => {
async (
url: URL | RequestInfo,
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 required to allow loggingFetch to be able to pass as fetchImplementation

Copy link
Member

Choose a reason for hiding this comment

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

I've addressed this in a refactor.

@pudkrong pudkrong requested a review from coderbyheart October 11, 2023 17:37
@pudkrong pudkrong temporarily deployed to ci October 11, 2023 20:07 — with GitHub Actions Inactive
This updates the validateFetch API and integrates
the fetch configuration in the first parameter.

This way there are not two parameters (that
are separated by the schema parameter) that control
what fetch sends.

It also adds convenience functions to provide
payloads and sends the correct content-type header
only if needed.
Copy link
Member

Choose a reason for hiding this comment

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

File is empty.

const res = await trackFetch(
new URL(`${slashless(apiEndpoint)}/v1/location/ground-fix`),

const vf = validatedFetch({ endpoint: apiEndpoint, apiKey }, trackFetch)
Copy link
Member

Choose a reason for hiding this comment

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

locationServiceToken can be passed here.

Math.round(Date.now() / 1000) -
getShadowUpdateTime(deviceShadow.state.metadata),
getShadowUpdateTime(
deviceShadow.state.metadata as Record<string, any>,
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, recursive typing is not really possible with TypeBox (and JSON schema)

/**
* @link https://api.nrfcloud.com/v1/#tag/All-Devices/operation/ListDevices
*/
const DeviceShadow = Type.Object({
Copy link
Member

Choose a reason for hiding this comment

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

Should have been integrated in DeviceShadow.ts

({ track, log }: { track: AddMetricsFn; log: Logger }) =>
async (url: URL, init?: RequestInit): ReturnType<typeof fetch> => {
async (
url: URL | RequestInfo,
Copy link
Member

Choose a reason for hiding this comment

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

I've addressed this in a refactor.

Comment on lines 71 to 77
const res = await vf({ resource: 'foo' }, schema, {
method: 'POST',
headers: {
Authorization: 'Bearer another-key',
'Content-Type': 'application/octet-stream',
},
})
Copy link
Member

Choose a reason for hiding this comment

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

If refactored this, because this mixes two parameters to configure the fetch behaviour. Those two (the first and the third) are also split around the schema.

async <Schema extends TObject>(
{ resource }: { resource: string },
schema: Schema,
init?: RequestInit,
Copy link
Member

Choose a reason for hiding this comment

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

Changed in the refactor.

@coderbyheart coderbyheart temporarily deployed to ci October 12, 2023 09:50 — with GitHub Actions Inactive
@coderbyheart coderbyheart merged commit 6781fbb into saga Oct 12, 2023
@coderbyheart coderbyheart deleted the 291-implement-nrf-cloud-api-response-validation branch October 12, 2023 10:44
coderbyheart added a commit that referenced this pull request Oct 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement nRF Cloud API response validation
2 participants