From 885b81cfe90d6bf98403be6d646dfb56463bbb14 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Mon, 9 Mar 2020 12:25:35 +0100 Subject: [PATCH 1/2] Autoupdate service interface --- .../autoupdate-service-examples.txt | 371 ++++++++++++++++++ docs/interfaces/autoupdate-service.txt | 102 +++++ docs/interfaces/how-to.txt | 2 +- 3 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 docs/interfaces/autoupdate-service-examples.txt create mode 100644 docs/interfaces/autoupdate-service.txt diff --git a/docs/interfaces/autoupdate-service-examples.txt b/docs/interfaces/autoupdate-service-examples.txt new file mode 100644 index 000000000..4979ae28a --- /dev/null +++ b/docs/interfaces/autoupdate-service-examples.txt @@ -0,0 +1,371 @@ +These are 5 examples for ModelRequests on a given dataset with the expected +results. There are 6 models: +- A, B, C, D: some models with a common `title` attribute and an attribute per + model (a, b, c, d). +- G1, G2: Some models with generic relations. + +Relations: +- A <-1---1-> B (B_id <-> A_id) +- A <-1---n-> C (C_ids <-> A_id) +- B <-n---m-> C (C_ids <-> B_ids) +- B <-1---n-> B selfreference modeling a tree (B_children_ids <-> B_parent_id) +- B <-n---m-> D with structured keys on D's side. The numbers are not related to + any ids. (D_ids <-> B__ids) +- G1 <-n---m-> A,C (content_object_ids <-> G1_ids) +- G2 <-1---1-> A,B (content_object_id <-> G2_id) + +Notes about the value `null`: If a field is not present, its value is implicit +`null`. Fields with `null` as value are included in the ModelResponse but not +the dataset. This means, that there might be references without a reverse part in +the dataset, but this is fine, as the missing part is implicitly `null`. If a +field is deleted in the dataset, there is an update with `null` as the value of +the field. + +Dataset: { + A: [{ + id: 1, + a: "a1", + title: "a1", + B_id: 1, + C_ids: [], + G1_ids: [1, 2], + }, { + id: 2, + a: "a2", + title: "a2", + C_ids: [1, 2], + G1_ids: [], + }], + B: [{ + id: 1, + b: "b1", + title: "b1"; + A_id: 1, + C_ids: [1], + G2_id: 1, + B_children_ids: [2], + D_ids: [1], + }, { + id: 2, + b: "b2", + title: "b2", + C_ids: [1, 2], + B_parent_id: 1, + B_children_ids: [], + D_ids: [1, 2], + }], + C: [{ + id: 1, + c: "c1", + title: "c1", + A_id: 2, + B_ids: [1, 2], + G1_ids: [2, 3], + }, { + id: 2, + c: "c2", + title: "c2", + A_id: 2, + B_ids: [2], + G1_ids: [2, 3], + }], + D: [{ + id: 1, + d: "d1", + B_1_ids: [1, 2], + B_2_ids: [1], + B_3_ids: [], + }, { + id: 2, + d: "d2", + B_1_ids: [], + B_4_ids: [2], + }], + G1: [{ + id: 1, + g1: "g1.1", + content_object_ids: ["A/1"], + }, { + id: 2, + g1: "g1.2", + content_object_ids: ["A/1", "C/1", "C/2"], + }, { + id: 3, + g1: "g1.3", + content_object_ids: ["C/1", "C/2"], + }], + G2: [{ + id: 1, + g2: "g2.1", + content_object_id: "B/1", + }] +} + +I: Basic +Note: + - The basic structure + - The empty model B/1 in the response + - The provided references, e.g. A/2/C_ids in the response + +ModelRequest: { + collection: "A", + ids: [1, 2], + fields: { + a: null, + C_ids: { + collection: "C", + fields: { + c: null, + G1_ids: { + collection: "G1", + fields: { + g1: null, + } + } + } + }, + B_id: { + collection: "B", + fields: {}, + }, + G1_ids: { + collection: "G1" + fields: { + g1: null, + } + } + } +} +ModelData: { + A: { + 1: { + a: "a1", + C_ids: [], + B_id: 1, + }, + 2: { + a: "a2", + C_ids: [1, 2], + } + }, + B: { + 1: {} + } + C: { + 1: { + c: "c1", + G1_ids: [2, 3], + }, + 2: { + c: "c2", + G1_ids: [2, 3], + } + }, + G1: { + 1: { + g1: "g1.1", + }, + 2: { + g1: "g1.2", + }, + 3: { + g1: "g1.3", + } + } +} + +II: Partial merged fields, generic lookup +Note: + - different fields for B/1 and B/2 in the response + - different fields for C/1 and C/2 in the response + - The generic lookup from G2/1 to B/1 + +ModelRequest: { + collection: "G2" + ids: [1], + fields: { + content_object_id: { + fields: { + B_children_ids: { + collection: "B", + fields: { + C_ids: { + collection: "C", + fields: { + c: null, + } + }, + B_parent_id: null, + } + }, + C_ids: { + collection: "C", + fields: { + c: null, + title: null, + } + }, + G2_id: null + } + } + } +} +ModelData: { + B: { + 1: { + B_children: [2], + C_ids: [1], + G1_id: 1, + }, + 2: { + C_ids: [1, 2], + B_parent_id: 1, + } + G2: { + 1: { + content_object_id: "B/1", + } + }, + C: { + 1: { + c: "c1", + title: "c1", + }, + 2: { + c: "c2", + } + } +} + + +III: non-existent ids, fields, fqids, references, generic relations and felder ohne referenz +Note: + - id 4 of G1 does not exist + - some fields does not exist + - some fields with indicated references does not exist (fine, until there is + no data. If there is data, this will cause an parsing-error at runtime) + - All these cases are fine + +ModelRequest: { + collection: "G1", + ids: [2, 4], # 4 does not exists + fields: { + content_object_ids: { + fields: { + a: null, + b: null, + not_existent: { + key: null, + }, + title: null, + G1_ids: null, + A_id: null, + } + } + } +} +ModelData: { + G1: { + 2: { + content_object_ids: ["A/1", "C/1", "C/2"], + } + }, + A: { + 1: { + a: "a1", + title: "a1", + G1_ids: [1, 2], + } + }, + C: { + 1: { + title: "c1", + A_id: 2, + G1_ids: [2, 3], + }, + 2: { + title: "c2", + A_id: 2, + G1_ids: [2, 3], + } + } +} + +IV: structured fields without references +Note: + - `B_` is handled as a prefix + +ModelRequest: { + collection: "D", + ids: [1, 2], + fields: { + d: null, + B_: null, + } +} +ModelData: { + D: { + 1: { + d: "d1", + B_1_ids: [1, 2], + B_2_ids: [1], + B_3_ids: [], + }, + 2: { + d: "d2", + B_1_ids: [], + B_4_ids: [2], + } + } +} + + +V: structed references +Note: + - `B_` and `B_4_ids` are handled as prefixes (`B_4_ids` has only one match) + - All references are followed + - The different fields for B/1 and B/2 in the response + +ModelRequest: { + collection: "D", + ids: [1, 2], + fields: { + B_: { + collection: "B", + fields: { + b: null, + } + }, + B_4_ids: { + collection: "B", + fields: { + title: null, + } + } + } +} +ModelData: { + D: { + 1: { + d: "d1", + B_1_ids: [1, 2], + B_2_ids: [1], + B_3_ids: [], + }, + 2: { + d: "d2", + B_1_ids: [], + B_4_ids: [2], + } + }, + B: { + 1: { + b: "b1", + }, + 2: { + b: "b2", + title: "b2", + } + } +} + diff --git a/docs/interfaces/autoupdate-service.txt b/docs/interfaces/autoupdate-service.txt new file mode 100644 index 000000000..4341c9e4a --- /dev/null +++ b/docs/interfaces/autoupdate-service.txt @@ -0,0 +1,102 @@ +/** + * This is a chatch-all exception for some invalid parameters + */ +Exception InvalidFormat(msg: string); + +/** + * This exception is thrown, when parsing model data. E.g. there is an indicated + * relation to a key, but the data are no foreign ids/fqids. The exception may + * happen, if the stream is used at runtime, because this cannot be detected + * when the caller makes the request. + */ +Exception ParsingError(msg: string); + +/** + * This methods subscribes to the given request. The response is a stream + * (language dependent) updating all models according to the ModelRequest if new + * data is available. On subscription, initial data must be pushed to the caller + * as soon as possible. The stream can be closed by closing the stream (e.g. the + * underlying network connection). + * + * @throws InvalidFormat + * @throws ParsingError + */ +subscribe(request: ModelRequest): stream; + +/** + * This is the main interface for requesting models in a structured, nested way. + * The initial request targets some models as the root models of one collection + * with all the same fields. This build a tree of dependencies, because the + * value of some fields may be a GenericModelDescriptor again. + * + * For a description of `fields` and `collection`, see GenericModelDescriptor + * and ModelDescriptor. + * + * `ids`: This is a list of ids for a collection, that should be provided. All + * models, that the user can see, must be included in the response. The model + * fields are handled according to `GenericModelDescriptor`. + */ +Interface ModelRequest { + ids: ID[]; + collection: Collection; + fields: { + [field: Field]: GenericModelDecriptor | null; + } +} + +/** + * For an overview, see `ModelRequest`. + * + * `fields`: + * All fields are handled as prefixes being aware of strutured fields. If a + * field is given, all fields with the given field as a prefix are included. + * E.g. if the field is `my_field`, the fields `my_field`, `my_field_3` and + * `my_fields` are included. + * + * Regardless of the value of a field, the restricted values are given in the + * response (multiple for structured fields). If the restricted value is null, + * the field must be included here. + * + * Note that the fields may be empty in the request or empty after restriction. + * The client gets an empty model, but *gets* a model. Only if the model does + * not exist or the user cannot see the model, there will be no model in the + * response. + * + * If the value is not null, it is indicated, that there is a reference to + * follow. There are two types of values: + * - GenericModelDescriptor: The reference is a generic one. This means, that + * the actual value from the model is a fqid or an array of fqids. + * - ModelDescriptor: A collection is given, so it can be expected, that the + * actual model value is an id or an array of ids. + */ +Interface GenericModelDecriptor { + fields: { + [field: Field]: GenericModelDecriptor | null; + } +} + +/** + * For an overview, see `ModelRequest`. + * + * `collection`: + * This is the collection, the ids are associated to. The ids are provided with + * two different ways: + * - In a ModelRequest, the ids are given. + * - If this interface is used in a field indication a relation, the id(s) are + * given by the actual model data. + */ +Interface ModelDescriptor extends GenericModelDescriptor { + collection: Collection; +} + +/** + * This structure holds all data given by the called service structured by + * fqids. Each fqid contains the (partial, restricted) data for the fqid. + */ +Interface ModelData { + [collection: Collection]: { + [id: Id]: { + [field: Field]: Value; + }; + }; +} diff --git a/docs/interfaces/how-to.txt b/docs/interfaces/how-to.txt index 8c3b74c2c..ff9532f6d 100644 --- a/docs/interfaces/how-to.txt +++ b/docs/interfaces/how-to.txt @@ -19,7 +19,7 @@ Example: * @throws ObjectNotFound do_it(data: MyRequest): void publishes MyEvent; - interface MyRequest { + Interface MyRequest { id: Id; text: string; some_other_value: number; From 0a7557329cfe3123bbf7d852b9c2ed74cd53d436 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Mon, 16 Mar 2020 10:41:41 +0100 Subject: [PATCH 2/2] Interface and example changes --- .../autoupdate-service-examples.txt | 371 ------------------ docs/interfaces/autoupdate-service.txt | 79 ++-- 2 files changed, 53 insertions(+), 397 deletions(-) delete mode 100644 docs/interfaces/autoupdate-service-examples.txt diff --git a/docs/interfaces/autoupdate-service-examples.txt b/docs/interfaces/autoupdate-service-examples.txt deleted file mode 100644 index 4979ae28a..000000000 --- a/docs/interfaces/autoupdate-service-examples.txt +++ /dev/null @@ -1,371 +0,0 @@ -These are 5 examples for ModelRequests on a given dataset with the expected -results. There are 6 models: -- A, B, C, D: some models with a common `title` attribute and an attribute per - model (a, b, c, d). -- G1, G2: Some models with generic relations. - -Relations: -- A <-1---1-> B (B_id <-> A_id) -- A <-1---n-> C (C_ids <-> A_id) -- B <-n---m-> C (C_ids <-> B_ids) -- B <-1---n-> B selfreference modeling a tree (B_children_ids <-> B_parent_id) -- B <-n---m-> D with structured keys on D's side. The numbers are not related to - any ids. (D_ids <-> B__ids) -- G1 <-n---m-> A,C (content_object_ids <-> G1_ids) -- G2 <-1---1-> A,B (content_object_id <-> G2_id) - -Notes about the value `null`: If a field is not present, its value is implicit -`null`. Fields with `null` as value are included in the ModelResponse but not -the dataset. This means, that there might be references without a reverse part in -the dataset, but this is fine, as the missing part is implicitly `null`. If a -field is deleted in the dataset, there is an update with `null` as the value of -the field. - -Dataset: { - A: [{ - id: 1, - a: "a1", - title: "a1", - B_id: 1, - C_ids: [], - G1_ids: [1, 2], - }, { - id: 2, - a: "a2", - title: "a2", - C_ids: [1, 2], - G1_ids: [], - }], - B: [{ - id: 1, - b: "b1", - title: "b1"; - A_id: 1, - C_ids: [1], - G2_id: 1, - B_children_ids: [2], - D_ids: [1], - }, { - id: 2, - b: "b2", - title: "b2", - C_ids: [1, 2], - B_parent_id: 1, - B_children_ids: [], - D_ids: [1, 2], - }], - C: [{ - id: 1, - c: "c1", - title: "c1", - A_id: 2, - B_ids: [1, 2], - G1_ids: [2, 3], - }, { - id: 2, - c: "c2", - title: "c2", - A_id: 2, - B_ids: [2], - G1_ids: [2, 3], - }], - D: [{ - id: 1, - d: "d1", - B_1_ids: [1, 2], - B_2_ids: [1], - B_3_ids: [], - }, { - id: 2, - d: "d2", - B_1_ids: [], - B_4_ids: [2], - }], - G1: [{ - id: 1, - g1: "g1.1", - content_object_ids: ["A/1"], - }, { - id: 2, - g1: "g1.2", - content_object_ids: ["A/1", "C/1", "C/2"], - }, { - id: 3, - g1: "g1.3", - content_object_ids: ["C/1", "C/2"], - }], - G2: [{ - id: 1, - g2: "g2.1", - content_object_id: "B/1", - }] -} - -I: Basic -Note: - - The basic structure - - The empty model B/1 in the response - - The provided references, e.g. A/2/C_ids in the response - -ModelRequest: { - collection: "A", - ids: [1, 2], - fields: { - a: null, - C_ids: { - collection: "C", - fields: { - c: null, - G1_ids: { - collection: "G1", - fields: { - g1: null, - } - } - } - }, - B_id: { - collection: "B", - fields: {}, - }, - G1_ids: { - collection: "G1" - fields: { - g1: null, - } - } - } -} -ModelData: { - A: { - 1: { - a: "a1", - C_ids: [], - B_id: 1, - }, - 2: { - a: "a2", - C_ids: [1, 2], - } - }, - B: { - 1: {} - } - C: { - 1: { - c: "c1", - G1_ids: [2, 3], - }, - 2: { - c: "c2", - G1_ids: [2, 3], - } - }, - G1: { - 1: { - g1: "g1.1", - }, - 2: { - g1: "g1.2", - }, - 3: { - g1: "g1.3", - } - } -} - -II: Partial merged fields, generic lookup -Note: - - different fields for B/1 and B/2 in the response - - different fields for C/1 and C/2 in the response - - The generic lookup from G2/1 to B/1 - -ModelRequest: { - collection: "G2" - ids: [1], - fields: { - content_object_id: { - fields: { - B_children_ids: { - collection: "B", - fields: { - C_ids: { - collection: "C", - fields: { - c: null, - } - }, - B_parent_id: null, - } - }, - C_ids: { - collection: "C", - fields: { - c: null, - title: null, - } - }, - G2_id: null - } - } - } -} -ModelData: { - B: { - 1: { - B_children: [2], - C_ids: [1], - G1_id: 1, - }, - 2: { - C_ids: [1, 2], - B_parent_id: 1, - } - G2: { - 1: { - content_object_id: "B/1", - } - }, - C: { - 1: { - c: "c1", - title: "c1", - }, - 2: { - c: "c2", - } - } -} - - -III: non-existent ids, fields, fqids, references, generic relations and felder ohne referenz -Note: - - id 4 of G1 does not exist - - some fields does not exist - - some fields with indicated references does not exist (fine, until there is - no data. If there is data, this will cause an parsing-error at runtime) - - All these cases are fine - -ModelRequest: { - collection: "G1", - ids: [2, 4], # 4 does not exists - fields: { - content_object_ids: { - fields: { - a: null, - b: null, - not_existent: { - key: null, - }, - title: null, - G1_ids: null, - A_id: null, - } - } - } -} -ModelData: { - G1: { - 2: { - content_object_ids: ["A/1", "C/1", "C/2"], - } - }, - A: { - 1: { - a: "a1", - title: "a1", - G1_ids: [1, 2], - } - }, - C: { - 1: { - title: "c1", - A_id: 2, - G1_ids: [2, 3], - }, - 2: { - title: "c2", - A_id: 2, - G1_ids: [2, 3], - } - } -} - -IV: structured fields without references -Note: - - `B_` is handled as a prefix - -ModelRequest: { - collection: "D", - ids: [1, 2], - fields: { - d: null, - B_: null, - } -} -ModelData: { - D: { - 1: { - d: "d1", - B_1_ids: [1, 2], - B_2_ids: [1], - B_3_ids: [], - }, - 2: { - d: "d2", - B_1_ids: [], - B_4_ids: [2], - } - } -} - - -V: structed references -Note: - - `B_` and `B_4_ids` are handled as prefixes (`B_4_ids` has only one match) - - All references are followed - - The different fields for B/1 and B/2 in the response - -ModelRequest: { - collection: "D", - ids: [1, 2], - fields: { - B_: { - collection: "B", - fields: { - b: null, - } - }, - B_4_ids: { - collection: "B", - fields: { - title: null, - } - } - } -} -ModelData: { - D: { - 1: { - d: "d1", - B_1_ids: [1, 2], - B_2_ids: [1], - B_3_ids: [], - }, - 2: { - d: "d2", - B_1_ids: [], - B_4_ids: [2], - } - }, - B: { - 1: { - b: "b1", - }, - 2: { - b: "b2", - title: "b2", - } - } -} - diff --git a/docs/interfaces/autoupdate-service.txt b/docs/interfaces/autoupdate-service.txt index 4341c9e4a..23f69d97d 100644 --- a/docs/interfaces/autoupdate-service.txt +++ b/docs/interfaces/autoupdate-service.txt @@ -27,56 +27,52 @@ subscribe(request: ModelRequest): stream; * This is the main interface for requesting models in a structured, nested way. * The initial request targets some models as the root models of one collection * with all the same fields. This build a tree of dependencies, because the - * value of some fields may be a GenericModelDescriptor again. + * value of some fields may be a GenericRelationFieldDescriptor again. * - * For a description of `fields` and `collection`, see GenericModelDescriptor - * and ModelDescriptor. + * For a description of `fields` and `collection`, see GenericRelationFieldDescriptor + * and RelationFieldDescriptor. * * `ids`: This is a list of ids for a collection, that should be provided. All * models, that the user can see, must be included in the response. The model - * fields are handled according to `GenericModelDescriptor`. + * fields are handled according to `GenericRelationFieldDescriptor`. */ -Interface ModelRequest { +Interface ModelRequest extends Fields { ids: ID[]; collection: Collection; +} + +Interface Fields { fields: { - [field: Field]: GenericModelDecriptor | null; + [field: Field]: GenericRelationFieldDescriptor + | RelationFieldDescriptor + | StructuredFieldDescriptor + | null; } } /** * For an overview, see `ModelRequest`. * - * `fields`: - * All fields are handled as prefixes being aware of strutured fields. If a - * field is given, all fields with the given field as a prefix are included. - * E.g. if the field is `my_field`, the fields `my_field`, `my_field_3` and - * `my_fields` are included. - * + * `fields` (inherited from `Fields`, see above): * Regardless of the value of a field, the restricted values are given in the * response (multiple for structured fields). If the restricted value is null, * the field must be included here. * - * Note that the fields may be empty in the request or empty after restriction. - * The client gets an empty model, but *gets* a model. Only if the model does - * not exist or the user cannot see the model, there will be no model in the - * response. - * * If the value is not null, it is indicated, that there is a reference to - * follow. There are two types of values: - * - GenericModelDescriptor: The reference is a generic one. This means, that + * follow. There are three types of values: + * - GenericRelationFieldDescriptor: The reference is a generic one. This means, that * the actual value from the model is a fqid or an array of fqids. - * - ModelDescriptor: A collection is given, so it can be expected, that the + * - RelationFieldDescriptor: A collection is given, so it can be expected, that the * actual model value is an id or an array of ids. + * - StructuredFieldDescriptor: The field is a template field and should be + * handled in this way. */ -Interface GenericModelDecriptor { - fields: { - [field: Field]: GenericModelDecriptor | null; - } +Interface GenericRelationFieldDecriptor extends Fields { + type: 'generic-relation' | 'generic-relation-list'; } /** - * For an overview, see `ModelRequest`. + * For an overview, see `ModelRequest`. For `fields`, see GenericRelationFieldDescriptor. * * `collection`: * This is the collection, the ids are associated to. The ids are provided with @@ -85,10 +81,41 @@ Interface GenericModelDecriptor { * - If this interface is used in a field indication a relation, the id(s) are * given by the actual model data. */ -Interface ModelDescriptor extends GenericModelDescriptor { +Interface RelationFieldDescriptor extends Fields { + type: 'relation' | 'relation-list'; collection: Collection; } +/** + * Structured Fields: A field with `$` as a placeholder can be structured. The + * `$` is a template placeholder. The `template field` holds a list of strings + * with options to insert into `$`. All these fields are the `structured + * fields`. Example: + * B_$_ids is the template field. Its value may be `["1", "test"]`, so the + * structured fields are B_1_ids and B_test_ids. + * + * This interface indicates, that there are structured fields related to the + * given template field, that should be resolved. If one would give `B_$_ids: + * null` without using this interface, the response would be just the template + * field `B_$_ids` and no structured fields. + * + * For just resolving values, leav `values` out (hence the optional parameter). + * + * If the values of all structured fields are references, use the `values` + * parameter analog to the `fields` paramter as documented in + * `GenericRelationFieldDescriptor`. + */ +Interface StructuredFieldDecriptor extends fields { + type: 'template', + values?: { + [field: Field]: GenericRelationFieldDescriptor + | RelationFieldDescriptor + | StructuredFieldDescriptor + | null; + } +} + + /** * This structure holds all data given by the called service structured by * fqids. Each fqid contains the (partial, restricted) data for the fqid.