Skip to content

Commit a3d933f

Browse files
209: API V2: Return patches for each affected resource (#104)
refs eclipse-emfcloud/emfcloud-modelserver#209 Contributed on behalf of STMicroelectronics. Signed-off-by: Camille Letavernier <cletavernier@eclipsesource.com>
1 parent 2c509b6 commit a3d933f

4 files changed

Lines changed: 84 additions & 9 deletions

File tree

packages/modelserver-client/src/model-server-client-api-v2.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,21 @@ export namespace ModelServerClientApiV2 {
131131
export const API_ENDPOINT = '/api/v2';
132132
}
133133

134+
/**
135+
* A patch affecting a specific model.
136+
*/
137+
export interface ModelPatch {
138+
/**
139+
* The uri of the patched model.
140+
*/
141+
modelUri: string;
142+
143+
/**
144+
* The patch describing the changes applied to the model.
145+
*/
146+
patch: Operation[];
147+
}
148+
134149
/**
135150
* Result sent to client after requesting a model update.
136151
*/
@@ -149,13 +164,24 @@ export interface ModelUpdateResult {
149164
* @param copy by default, the patch will be directly applied to the oldModel, modifying
150165
* it in-place. If copy is true, the patch will be applied on a copy of the model, leaving
151166
* the original model unchanged.
167+
* @param modelUri the uri of the model to patch. This can be used when the model is split in multiple
168+
* resources, to identify the patch to apply. The modelUri should correspond to the oldModel object.
169+
* It can be omitted when patching the main model (or in single-model cases).
152170
* @return the patched model.
153171
*/
154-
patchModel?(oldModel: ModelServerElement, copy?: boolean): ModelServerElement;
172+
patchModel?(oldModel: ModelServerElement, copy?: boolean, modelUri?: string): ModelServerElement;
155173

156174
/**
157175
* The Json Patch describing the changes that were applied to the model. Only present if
158176
* the edit request was successful.
159177
*/
160178
patch?: Operation[];
179+
180+
/**
181+
* The list of Json Patches describing the changes that were applied to the models. Only present if
182+
* the edit request was successful.
183+
*
184+
* The list contains one entry per modified model. Unmodified models will not contain any entry.
185+
*/
186+
allPatches?: ModelPatch[];
161187
}

packages/modelserver-client/src/model-server-client-v2-integration.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,5 +355,37 @@ describe('Integration tests for ModelServerClientV2', () => {
355355

356356
await testUndoRedo(modeluri, machine, model);
357357
});
358+
359+
it('check all patch replies', async () => {
360+
const modeluri = 'SuperBrewer3000.coffee';
361+
const newName = 'Super Brewer 6000';
362+
const machine = await client.get(modeluri, ModelServerObjectV2.is);
363+
364+
const patchedMachine = deepClone(machine);
365+
366+
// Directly change the model
367+
patchedMachine.name = newName;
368+
patchedMachine.children[1].processor.clockSpeed = 6;
369+
370+
// Generate patch by diffing the original model and the patched one
371+
const patch = jsonpatch.compare(machine, patchedMachine);
372+
373+
const result = await client.edit(modeluri, patch);
374+
expect(result.success).to.be.true;
375+
376+
expect(result.patch).to.not.be.undefined;
377+
expect(result.allPatches).to.not.be.undefined;
378+
expect(result.allPatches).to.be.an('array').of.length(1);
379+
380+
// Patch the main resource
381+
const updatedMachineMainPatch = result.patchModel!(machine, true);
382+
expect(patchedMachine).to.deep.equal(updatedMachineMainPatch);
383+
384+
// Patch the first resource
385+
const updatedMachineFirstPatch = result.patchModel!(machine, true, result.allPatches![0].modelUri);
386+
expect(patchedMachine).to.deep.equal(updatedMachineFirstPatch);
387+
388+
await testUndoRedo(modeluri, machine, updatedMachineFirstPatch);
389+
});
358390
});
359391
});

packages/modelserver-client/src/model-server-client-v2.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* SPDX-License-Identifier: EPL-2.0 OR MIT
1010
*******************************************************************************/
1111
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
12-
import { Operation } from 'fast-json-patch';
12+
import { deepClone, Operation } from 'fast-json-patch';
1313
import WebSocket from 'isomorphic-ws';
1414

1515
import { ModelServerCommand } from './model/command-model';
@@ -228,6 +228,14 @@ export class ModelServerClientV2 implements ModelServerClientApiV2 {
228228
type: 'modelserver.patch',
229229
data: fullPatch
230230
};
231+
if (fullPatch.length === 0) {
232+
// No-op
233+
return Promise.resolve({
234+
success: true,
235+
patchModel: (oldModel, copy, _modelUri) => (copy ? deepClone(oldModel) : oldModel),
236+
patch: []
237+
});
238+
}
231239
}
232240
return this.process(
233241
this.restClient.patch(ModelServerPaths.MODEL_CRUD, encodeRequestBody(this.defaultFormat)(patchMessage), {
@@ -257,7 +265,7 @@ export class ModelServerClientV2 implements ModelServerClientApiV2 {
257265
const errorMsg = `${modeluri} : Cannot open new socket, already subscribed!'`;
258266
console.warn(errorMsg);
259267
if (options.errorWhenUnsuccessful) {
260-
throw new Error('errorMsg');
268+
throw new Error(errorMsg);
261269
}
262270
}
263271
const path = this.createSubscriptionPath(modeluri, options);

packages/modelserver-client/src/model-server-message.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
* SPDX-License-Identifier: EPL-2.0 OR MIT
1010
*******************************************************************************/
1111
import * as jsonpatch from 'fast-json-patch';
12-
import { deepClone } from 'fast-json-patch';
12+
import { deepClone, Operation } from 'fast-json-patch';
1313

1414
import { ModelServerElement } from './model/base-model';
15-
import { ModelUpdateResult } from './model-server-client-api-v2';
15+
import { ModelPatch, ModelUpdateResult } from './model-server-client-api-v2';
1616
import { Operations } from './utils/patch-utils';
1717
import * as Type from './utils/type-util';
1818

@@ -199,14 +199,19 @@ export namespace MessageDataMapper {
199199
if (isSuccess(message)) {
200200
const data = message.data as any;
201201
const patch = data ? data.patch : undefined;
202-
if (patch && Operations.isPatch(patch)) {
202+
const allPatches = data ? data.allPatches : undefined;
203+
if (patch || allPatches) {
203204
return {
204205
success: isSuccess(message),
205206
patch,
206-
patchModel: (oldModel, copy) => {
207+
patchModel: (oldModel, copy, modelUri) => {
207208
const modelToPatch = copy ? deepClone(oldModel) : oldModel;
208-
return jsonpatch.applyPatch(modelToPatch, patch).newDocument as ModelServerElement;
209-
}
209+
const patchToApply = modelUri ? getPatch(allPatches, modelUri) : Operations.isPatch(patch) ? patch : undefined;
210+
return patchToApply
211+
? (jsonpatch.applyPatch(modelToPatch, patchToApply).newDocument as ModelServerElement)
212+
: modelToPatch;
213+
},
214+
allPatches
210215
};
211216
} else {
212217
return { success: true };
@@ -215,4 +220,8 @@ export namespace MessageDataMapper {
215220
return { success: false };
216221
}
217222
}
223+
224+
function getPatch(patches: ModelPatch[], modelUri: string): Operation[] | undefined {
225+
return patches.find(mp => mp.modelUri === modelUri)?.patch;
226+
}
218227
}

0 commit comments

Comments
 (0)