-
Notifications
You must be signed in to change notification settings - Fork 1
feat: validate nRF Cloud API response #305
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
feat: validate nRF Cloud API response #305
Conversation
b9fffe7
to
6856831
Compare
PR Analysis
PR Feedback
How to use
|
const { track, metrics } = metricsForComponent('singleCellGeo') | ||
|
||
const trackFetch = loggingFetch({ track, log }) | ||
const trackFetch = validatedLoggingFetch({ track, log }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
There was a problem hiding this comment.
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 andContent-Type
asapplication/json
as its defaults.
|
||
if ('error' in maybeResult) { | ||
log.error('request failed', { error: maybeResult.error }) | ||
throw new Error(`Acquiring service token failed: ${maybeResult.error}`) |
There was a problem hiding this comment.
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.
nrfcloud/createAccountDevice.ts
Outdated
|
||
if ('error' in maybeResult) { | ||
console.error(`Failed to create account device:`, maybeResult.error) | ||
throw new Error(`Failed to create account device`) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
nrfcloud/validatedFetch.ts
Outdated
async <Schema extends TObject>( | ||
{ resource }: { resource: string }, | ||
schema: Schema, | ||
init?: RequestInit, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated change.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in the refactor.
lambda/fetchDeviceShadow.ts
Outdated
Math.round(Date.now() / 1000) - | ||
getShadowUpdateTime(deviceShadow.state.metadata), | ||
getShadowUpdateTime( | ||
deviceShadow.state.metadata as Record<string, any>, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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.
lambda/validatedFetch.ts
Outdated
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
lambda/fetchDeviceShadow.ts
Outdated
Math.round(Date.now() / 1000) - | ||
getShadowUpdateTime(deviceShadow.state.metadata), | ||
getShadowUpdateTime( | ||
deviceShadow.state.metadata as Record<string, any>, |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
nrfcloud/validatedFetch.spec.ts
Outdated
const res = await vf({ resource: 'foo' }, schema, { | ||
method: 'POST', | ||
headers: { | ||
Authorization: 'Bearer another-key', | ||
'Content-Type': 'application/octet-stream', | ||
}, | ||
}) |
There was a problem hiding this comment.
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.
nrfcloud/validatedFetch.ts
Outdated
async <Schema extends TObject>( | ||
{ resource }: { resource: string }, | ||
schema: Schema, | ||
init?: RequestInit, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in the refactor.
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.