Validation
In your service code, you can arrange for validation to take place on the client and at the gateway. Early validation improves the user's experience, and makes it more likely that client requests are in the correct form before you attempt to act on them.
Consult the troubleshooting guide if you need it during this task.
Client-side validation
Validation on the client can perform simple checks on input data, in the hope of avoiding faulty requests to a service. For example, you can ensure the presence of values for mandatory fields, or verify that input values are in the correct format.
Client-side validation is configured through service settings, in the same way as parameterized searches.
For example, you can instruct a service to validate that the input values for the Offence
field start with two letters and two digits, as follows:
addService(
{
id: 'searchTerm',
name: 'NYPD Connector: Search Term',
description: 'A service for conditional searches.',
form: {
offence: {
label: 'Offence',
logicalType: 'singleLineString',
isMandatory: true,
validation: {
regex: '[A-Za-z]{2}\\d{2}',
message: 'Case number must start with two letters, then two digits',
},
},
},
},
() => {
// TODO: Implement acquire
}
);
The isMandatory
field specifies whether a value is required, while the validation
field allows regular expression validation to be performed, with custom error messages to be sent back to the client when a value does not comply with the rule.
The documentation for the ICondition
type contains more information about condition properties.
Server-side validation
Validation on the server can handle more complex requirements. For example, your form might have three input fields that are individually optional, but at least one of them must be set. In your service code, you can check that the user has set at least one condition in the request.
As another example, if you have two date fields and want to support searching a range of dates, you can validate that the start date is before the end date. In your service code, you can implement the validation logic using the optional validate()
function on the service callback, as described in the documentation for IServiceCallbacks
.
If you find a problem, you can throw a DetailedError from within validate()
.
The following is an illustration of the first example:
addService(
{
id: 'findComplaintBy',
name: 'NYPD Connector: Search By',
description: 'A service for conditional searches.',
formIsMandatory: true,
form: {
sourceId: {
label: 'Source id',
logicalType: 'multipleLineString',
description: 'A service that has some validation',
isMandatory: false,
},
borough: {
label: 'Borough name',
logicalType: 'multipleLineString',
isMandatory: false,
},
lawCategory: {
label: 'Law category',
logicalType: 'multipleLineString',
isMandatory: false,
},
},
},
{
acquire: () => {
// TODO: Implement acquire
},
validate: ({ conditions: { borough, lawCategory, sourceId } }) => {
if (!borough && !lawCategory && !sourceId) {
throw new DetailedError({ title: 'One of the conditions must be set.' });
}
},
}
);
In this code, the Boolean formIsMandatory
property means that users must always open the form, even though it contains no individually mandatory conditions. The implementation of validate()
then ensures that at least one of the conditions is set.
Note: To use DetailedError
, you must update the import from @i2analyze/i2connect
to include it, as follows:
import { startConnector, addService, DetailedError } from '@i2analyze/i2connect';
Handling errors
If your service code encounters problems despite your efforts to validate requests, you can also handle errors within the acquire()
function itself.
For example, even though errors are handled by service calls, you might want to display your own, more descriptive error message to the user.
In another case, you might want to throw an error if you call a third party system and it fails. You can set up a try-catch
structure around the service call, and throw a DetailedError
if required.
The following code shows how you can update the NYPD Connector: Get all service to throw a DetailedError
:
addService(
{
id: 'getAll',
name: 'NYPD Connector: Get all',
description: 'A service that retrieves all data.',
},
async ({ result }) => {
const brokenUrl = 'This is a broken URL';
let data: IComplaint[] = [];
try {
const response = await fetch(brokenUrl);
if (response.status === 200) {
data = (await response.json()) as IComplaint[];
}
} catch {
throw new DetailedError({ title: 'Failed to connect to the NYPD data source.' });
}
for (const datum of data) {
const locationEntity = addLocation(datum, result);
const complaintEntity = addComplaint(datum, result);
const suspectEntity = addSuspect(datum, result);
const victimEntity = addVictim(datum, result);
addLink(Locatedat, datum.cmplnt_num, complaintEntity, locationEntity, result);
addLink(Victimof, datum.cmplnt_num, victimEntity, complaintEntity, result);
addLink(Suspectof, datum.cmplnt_num, suspectEntity, complaintEntity, result);
}
}
);
Note: To run this code, you also need to update the imports in index.ts
, as follows:
import fetch from 'node-fetch';
import { requestData, IComplaint } from './data-service';
As a result of a DetailedError
, the user sees the title
or the detail
if only one is present, or a composition in the form ${title}: ${detail}
when both are present.
Next steps
You're now ready to package and distribute your connector.