Skip to content
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

Support array for serviceEndpoint #1200

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/spec/patches.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ The `add-services` _Patch Action_ describes the addition of [Service Endpoints](
3. Each service being added ****MUST**** be represented by an entry in the `services` array, and each entry must be an object composed as follows:
1. The object ****MUST**** include an `id` property, and its value ****MUST**** be a string with a length of no more than fifty (50) Base64URL encoded characters. If the value is not of the correct type or exceeds the specified length, the entire _Patch Action_ ****MUST**** be discarded, without any of it being used to modify the DID's state.
2. The object ****MUST**** include a `type` property, and its value ****MUST**** be a string with a length of no more than thirty (30) Base64URL encoded characters. If the value is not a string or exceeds the specified length, the entire _Patch Action_ ****MUST**** be discarded, without any of it being used to modify the DID's state.
3. The object ****MUST**** include a `serviceEndpoint` property, and its value ****MUST**** be either a valid URI string (including a scheme segment: i.e. http://, git://) or a JSON object with properties that describe the Service Endpoint further. If the values do not adhere to these constraints, the entire _Patch Action_ ****MUST**** be discarded, without any of it being used to modify the DID's state.
3. The object ****MUST**** include a `serviceEndpoint` property, and its value ****MUST**** be either a valid URI string (including a scheme segment: i.e. http://, git://), a JSON object with properties that describe the Service Endpoint further, or an array of such URI strings or JSON objects. If the values do not adhere to these constraints, the entire _Patch Action_ ****MUST**** be discarded, without any of it being used to modify the DID's state.


#### `remove-services`
Expand Down
34 changes: 19 additions & 15 deletions lib/core/versions/1.0/DocumentComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,22 +345,26 @@ export default class DocumentComposer {
}

// `serviceEndpoint` validations.
const serviceEndpoint = service.serviceEndpoint;
if (typeof serviceEndpoint === 'string') {
const uri = URI.parse(service.serviceEndpoint);
if (uri.error !== undefined) {
throw new SidetreeError(
ErrorCode.DocumentComposerPatchServiceEndpointStringNotValidUri,
`Service endpoint string '${serviceEndpoint}' is not a valid URI.`
);
}
} else if (typeof serviceEndpoint === 'object') {
// Allow `object` type only if it is not an array.
if (Array.isArray(serviceEndpoint)) {
throw new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointCannotBeAnArray);
// transform URI strings and JSON objects into array so that we can run validations more easily
const serviceEndpointValueAsArray = Array.isArray(service.serviceEndpoint) ? service.serviceEndpoint : [service.serviceEndpoint];
for (const serviceEndpoint of serviceEndpointValueAsArray) {
// serviceEndpoint itself must be URI string or non-array object
if (typeof serviceEndpoint === 'string') {
const uri = URI.parse(serviceEndpoint);
if (uri.error !== undefined) {
throw new SidetreeError(
ErrorCode.DocumentComposerPatchServiceEndpointStringNotValidUri,
`Service endpoint string '${serviceEndpoint}' is not a valid URI.`
);
}
} else if (typeof serviceEndpoint === 'object') {
// Allow `object` type only if it is not an array.
if (Array.isArray(serviceEndpoint)) {
throw new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointCannotBeAnArray);
}
} else {
throw new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointMustBeStringOrNonArrayObject);
}
} else {
throw new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointMustBeStringOrNonArrayObject);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/core/versions/1.0/models/ServiceModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
export default interface ServiceModel {
id: string;
type: string;
serviceEndpoint: string | object;
serviceEndpoint: string | object | Array<string | object>;
}
34 changes: 19 additions & 15 deletions lib/core/versions/latest/DocumentComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,22 +345,26 @@ export default class DocumentComposer {
}

// `serviceEndpoint` validations.
const serviceEndpoint = service.serviceEndpoint;
if (typeof serviceEndpoint === 'string') {
const uri = URI.parse(service.serviceEndpoint);
if (uri.error !== undefined) {
throw new SidetreeError(
ErrorCode.DocumentComposerPatchServiceEndpointStringNotValidUri,
`Service endpoint string '${serviceEndpoint}' is not a valid URI.`
);
}
} else if (typeof serviceEndpoint === 'object') {
// Allow `object` type only if it is not an array.
if (Array.isArray(serviceEndpoint)) {
throw new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointCannotBeAnArray);
// transform URI strings and JSON objects into array so that we can run validations more easily
nikolockenvitz marked this conversation as resolved.
Show resolved Hide resolved
const serviceEndpointValueAsArray = Array.isArray(service.serviceEndpoint) ? service.serviceEndpoint : [service.serviceEndpoint];
for (const serviceEndpoint of serviceEndpointValueAsArray) {
// serviceEndpoint itself must be URI string or non-array object
if (typeof serviceEndpoint === 'string') {
const uri = URI.parse(serviceEndpoint);
if (uri.error !== undefined) {
throw new SidetreeError(
ErrorCode.DocumentComposerPatchServiceEndpointStringNotValidUri,
`Service endpoint string '${serviceEndpoint}' is not a valid URI.`
);
}
} else if (typeof serviceEndpoint === 'object') {
// Allow `object` type only if it is not an array.
if (Array.isArray(serviceEndpoint)) {
throw new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointCannotBeAnArray);
}
} else {
throw new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointMustBeStringOrNonArrayObject);
Comment on lines +350 to +366
Copy link
Collaborator

Choose a reason for hiding this comment

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

We will need the same change in ion-sdk. Totally get it if you don't have time to make those changes in ion-sdk, so the PR will suffice to be approved if you can file an issue in the ion-sdk repo to make the same changes! Thanks!

Copy link
Author

Choose a reason for hiding this comment

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

Happy to implement this in ion-sdk as well. I will still create an issue as well, as I am not yet 100% sure when I will find time for the implementation. So I will open the issue, once this PR is merged, right?

Copy link
Author

Choose a reason for hiding this comment

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

Hi @thehenrytsai, I updated ion-sdk (decentralized-identity/ion-sdk#30) as well

}
} else {
throw new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointMustBeStringOrNonArrayObject);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/core/versions/latest/models/ServiceModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
export default interface ServiceModel {
id: string;
type: string;
serviceEndpoint: string | object;
serviceEndpoint: string | object | Array<string | object>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably no need for this change, array is an object.

Copy link
Author

Choose a reason for hiding this comment

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

That's true, I just thought it might be better to understand if it's explicit. But I can also revert this change if you like.

}
45 changes: 44 additions & 1 deletion tests/core/DocumentComposer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ describe('DocumentComposer', async () => {
DocumentComposer['validateAddServicesPatch'](patch);
});

it('should throw error if `serviceEndpoint` is an array.', () => {
it('should allow an array as `serviceEndpoint`.', () => {
const patch = {
action: PatchAction.AddServices,
services: [{
Expand All @@ -478,6 +478,20 @@ describe('DocumentComposer', async () => {
serviceEndpoint: []
}]
};

// Expecting this call to succeed without errors.
DocumentComposer['validateAddServicesPatch'](patch);
});

it('should throw error if `serviceEndpoint` is an array that includes an array.', () => {
const patch = {
action: PatchAction.AddServices,
services: [{
id: 'someId',
type: 'someType',
serviceEndpoint: [[]] // array must contain URI strings or objects but not arrays
}]
};
const expectedError = new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointCannotBeAnArray);
expect(() => { DocumentComposer['validateAddServicesPatch'](patch); }).toThrow(expectedError);
});
Expand All @@ -495,6 +509,19 @@ describe('DocumentComposer', async () => {
expect(() => { DocumentComposer['validateAddServicesPatch'](patch); }).toThrow(expectedError);
});

it('should throw error if `serviceEndpoint` has an invalid type (inside an array).', () => {
const patch = {
action: PatchAction.AddServices,
services: [{
id: 'someId',
type: 'someType',
serviceEndpoint: [123] // Invalid serviceEndpoint type.
}]
};
const expectedError = new SidetreeError(ErrorCode.DocumentComposerPatchServiceEndpointMustBeStringOrNonArrayObject);
expect(() => { DocumentComposer['validateAddServicesPatch'](patch); }).toThrow(expectedError);
});

it('Should throw if `serviceEndpoint` is not valid URI.', () => {
const patch = {
action: PatchAction.AddServices,
Expand All @@ -510,6 +537,22 @@ describe('DocumentComposer', async () => {
ErrorCode.DocumentComposerPatchServiceEndpointStringNotValidUri
);
});

it('Should throw if `serviceEndpoint` is not valid URI (inside an array).', () => {
const patch = {
action: PatchAction.AddServices,
services: [{
id: 'someId',
type: 'someType',
serviceEndpoint: ['http://'] // Invalid URI.
}]
};

JasmineSidetreeErrorValidator.expectSidetreeErrorToBeThrown(
() => DocumentComposer['validateAddServicesPatch'](patch),
ErrorCode.DocumentComposerPatchServiceEndpointStringNotValidUri
);
});
});

describe('validateDocumentPatches()', async () => {
Expand Down