Skip to content

Commit

Permalink
Extend DeciderModule to automatically decide Requests (#269)
Browse files Browse the repository at this point in the history
* feat: first draft

* feat: second draft

* feat: add checkCompatibility

* feat: improve RequestItemConfig

* feat: improve compatibility check e.g. for tags

* feat: validate responseConfig compatibility

* feat: use Results

* feat: publish event if request automatically decided

* refactor: rename function

* test: decide GeneralRequestConfig

* refactor: logging

* feat: validate automationConfig in init

* feat: improve error handling

* refactor: use containsDeep to check for RequestItems with requireManualDecision

* fix: handle requestConfigs containing general and item-specific parts

* test: RequestConfigs

* fix: consider RequestItemGroups correctly

* fix: adjust containsDeep to work with item objects

* fix: check canDecide correctly

* test: RequestItemDerivationConfigs

* feat: begin to frickle change of config using restart

* Revert "feat: begin to frickle change of config using restart"

This reverts commit 71e8514.

* test: all RequestItemDerivationConfigs

* feat: remove configs related to existing attributes

* test: validateAutomationConfig for all combinations

* test: remove unit tests that were effectively duplicated

* chore: remove todo comments

* chore: remove todo comment

* feat: hide automatically answered Request from user

* refactor: use jest's rejects.toThrow

* test: automatically decide Request from RelationshipTemplate

* feat: integrate comments on DeciderModule

* refactor: integrate comments on DeciderModule test

* feat: extend functionality of GeneralRequestConfig

* refactor: use explicit if instead of else if

* refactor: reoder RelationshipTemplateProcessResults

* feat: allow date comparisons

* refactor: variable naming

* chore: build schemas

* refactor: move functions outside of module

* refactor: make validateAutomationConfig private

* refactor: don't use Results

* refactor: split test file

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: mkuhn <magnus.kuhn@js-soft.com>
  • Loading branch information
3 people authored Oct 17, 2024
1 parent 755203c commit b521f4f
Show file tree
Hide file tree
Showing 13 changed files with 3,742 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export class RelationshipTemplateProcessedModule extends AppRuntimeModule<Relati
);
break;
}

case RelationshipTemplateProcessedResult.RequestAutomaticallyDecided: {
break;
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/runtime/src/RuntimeConfig.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { IConfigOverwrite } from "@nmshd/transport";
import { ModuleConfiguration } from "./extensibility/modules/RuntimeModule";
import { DeciderModuleConfiguration } from "./modules";

export interface RuntimeConfig {
transportLibrary: Omit<IConfigOverwrite, "supportedIdentityVersion">;

modules: Record<string, ModuleConfiguration>;
modules: Record<string, ModuleConfiguration> & {
decider: DeciderModuleConfiguration;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class MessageProcessedEvent extends DataEvent<MessageProcessedEventData>
}

export enum MessageProcessedResult {
RequestAutomaticallyDecided = "RequestAutomaticallyDecided",
ManualRequestDecisionRequired = "ManualRequestDecisionRequired",
NoRequest = "NoRequest",
Error = "Error"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class RelationshipTemplateProcessedEvent extends DataEvent<RelationshipTe
}

export enum RelationshipTemplateProcessedResult {
RequestAutomaticallyDecided = "RequestAutomaticallyDecided",
ManualRequestDecisionRequired = "ManualRequestDecisionRequired",
NonCompletedRequestExists = "NonCompletedRequestExists",
RelationshipExists = "RelationshipExists",
Expand All @@ -20,6 +21,11 @@ export enum RelationshipTemplateProcessedResult {
}

export type RelationshipTemplateProcessedEventData =
| {
template: RelationshipTemplateDTO;
result: RelationshipTemplateProcessedResult.RequestAutomaticallyDecided;
requestId: string;
}
| {
template: RelationshipTemplateDTO;
result: RelationshipTemplateProcessedResult.ManualRequestDecisionRequired;
Expand Down
326 changes: 317 additions & 9 deletions packages/runtime/src/modules/DeciderModule.ts

Large diffs are not rendered by default.

147 changes: 147 additions & 0 deletions packages/runtime/src/modules/decide/RequestConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { RelationshipAttributeConfidentiality } from "@nmshd/content";

export interface GeneralRequestConfig {
peer?: string | string[];
createdAt?: string | string[];
"source.type"?: "Message" | "RelationshipTemplate";
"content.expiresAt"?: string | string[];
"content.title"?: string | string[];
"content.description"?: string | string[];
"content.metadata"?: object | object[];
}

export interface RequestItemConfig extends GeneralRequestConfig {
"content.item.@type"?: string | string[];
"content.item.mustBeAccepted"?: boolean;
"content.item.title"?: string | string[];
"content.item.description"?: string | string[];
"content.item.metadata"?: object | object[];
}

export interface AuthenticationRequestItemConfig extends RequestItemConfig {
"content.item.@type": "AuthenticationRequestItem";
}

export interface ConsentRequestItemConfig extends RequestItemConfig {
"content.item.@type": "ConsentRequestItem";
"content.item.consent"?: string | string[];
"content.item.link"?: string | string[];
}

export interface CreateAttributeRequestItemConfig extends RequestItemConfig {
"content.item.@type": "CreateAttributeRequestItem";
"content.item.attribute.@type"?: "IdentityAttribute" | "RelationshipAttribute";
"content.item.attribute.owner"?: string | string[];
"content.item.attribute.validFrom"?: string | string[];
"content.item.attribute.validTo"?: string | string[];
"content.item.attribute.tags"?: string[];
"content.item.attribute.key"?: string | string[];
"content.item.attribute.isTechnical"?: boolean;
"content.item.attribute.confidentiality"?: RelationshipAttributeConfidentiality | RelationshipAttributeConfidentiality[];
"content.item.attribute.value.@type"?: string | string[];
"content.item.attribute.value.value"?: string | string[];
"content.item.attribute.value.title"?: string | string[];
"content.item.attribute.value.description"?: string | string[];
}

export interface DeleteAttributeRequestItemConfig extends RequestItemConfig {
"content.item.@type": "DeleteAttributeRequestItem";
}

export interface FreeTextRequestItemConfig extends RequestItemConfig {
"content.item.@type": "FreeTextRequestItem";
"content.item.freeText"?: string | string[];
}

export interface ProposeAttributeRequestItemConfig extends RequestItemConfig {
"content.item.@type": "ProposeAttributeRequestItem";
"content.item.attribute.@type"?: "IdentityAttribute" | "RelationshipAttribute";
"content.item.attribute.owner"?: string | string[];
"content.item.attribute.validFrom"?: string | string[];
"content.item.attribute.validTo"?: string | string[];
"content.item.attribute.tags"?: string[];
"content.item.attribute.key"?: string | string[];
"content.item.attribute.isTechnical"?: boolean;
"content.item.attribute.confidentiality"?: RelationshipAttributeConfidentiality | RelationshipAttributeConfidentiality[];
"content.item.attribute.value.@type"?: string | string[];
"content.item.attribute.value.value"?: string | string[];
"content.item.attribute.value.title"?: string | string[];
"content.item.attribute.value.description"?: string | string[];
"content.item.query.@type"?: "IdentityAttributeQuery" | "RelationshipAttributeQuery" | "IQLQuery";
"content.item.query.validFrom"?: string | string[];
"content.item.query.validTo"?: string | string[];
"content.item.query.valueType"?: string | string[];
"content.item.query.tags"?: string[];
"content.item.query.key"?: string | string[];
"content.item.query.owner"?: string | string[];
"content.item.query.queryString"?: string | string[];
"content.item.query.attributeCreationHints.title"?: string | string[];
"content.item.query.attributeCreationHints.description"?: string | string[];
"content.item.query.attributeCreationHints.valueType"?: string | string[];
"content.item.query.attributeCreationHints.confidentiality"?: RelationshipAttributeConfidentiality | RelationshipAttributeConfidentiality[];
"content.item.query.attributeCreationHints.tags"?: string[];
}

export interface ReadAttributeRequestItemConfig extends RequestItemConfig {
"content.item.@type": "ReadAttributeRequestItem";
"content.item.query.@type"?: "IdentityAttributeQuery" | "RelationshipAttributeQuery" | "IQLQuery";
"content.item.query.validFrom"?: string | string[];
"content.item.query.validTo"?: string | string[];
"content.item.query.valueType"?: string | string[];
"content.item.query.tags"?: string[];
"content.item.query.key"?: string | string[];
"content.item.query.owner"?: string | string[];
"content.item.query.queryString"?: string | string[];
"content.item.query.attributeCreationHints.title"?: string | string[];
"content.item.query.attributeCreationHints.description"?: string | string[];
"content.item.query.attributeCreationHints.valueType"?: string | string[];
"content.item.query.attributeCreationHints.confidentiality"?: RelationshipAttributeConfidentiality | RelationshipAttributeConfidentiality[];
"content.item.query.attributeCreationHints.tags"?: string[];
}

export interface RegisterAttributeListenerRequestItemConfig extends RequestItemConfig {
"content.item.@type": "RegisterAttributeListenerRequestItem";
"content.item.query.@type"?: "IdentityAttributeQuery";
"content.item.query.validFrom"?: string | string[];
"content.item.query.validTo"?: string | string[];
"content.item.query.valueType"?: string | string[];
"content.item.query.tags"?: string[];
}

export interface ShareAttributeRequestItemConfig extends RequestItemConfig {
"content.item.@type": "ShareAttributeRequestItem";
"content.item.attribute.@type"?: "IdentityAttribute" | "RelationshipAttribute";
"content.item.attribute.owner"?: string | string[];
"content.item.attribute.validFrom"?: string | string[];
"content.item.attribute.validTo"?: string | string[];
"content.item.attribute.tags"?: string[];
"content.item.attribute.key"?: string | string[];
"content.item.attribute.isTechnical"?: boolean;
"content.item.attribute.confidentiality"?: RelationshipAttributeConfidentiality | RelationshipAttributeConfidentiality[];
"content.item.attribute.value.@type"?: string | string[];
"content.item.attribute.value.value"?: string | string[];
"content.item.attribute.value.title"?: string | string[];
"content.item.attribute.value.description"?: string | string[];
}

export type RequestItemDerivationConfig =
| RequestItemConfig
| AuthenticationRequestItemConfig
| ConsentRequestItemConfig
| CreateAttributeRequestItemConfig
| DeleteAttributeRequestItemConfig
| FreeTextRequestItemConfig
| ProposeAttributeRequestItemConfig
| ReadAttributeRequestItemConfig
| RegisterAttributeListenerRequestItemConfig
| ShareAttributeRequestItemConfig;

export function isGeneralRequestConfig(input: any): input is GeneralRequestConfig {
return !Object.keys(input).some((key) => key.startsWith("content.item."));
}

export function isRequestItemDerivationConfig(input: any): input is RequestItemDerivationConfig {
return Object.keys(input).some((key) => key.startsWith("content.item."));
}

export type RequestConfig = GeneralRequestConfig | RequestItemDerivationConfig;
64 changes: 64 additions & 0 deletions packages/runtime/src/modules/decide/ResponseConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { IdentityAttribute, RelationshipAttribute } from "@nmshd/content";

export interface RejectResponseConfig {
accept: false;
code?: string;
message?: string;
}

export function isRejectResponseConfig(input: any): input is RejectResponseConfig {
return input.accept === false;
}

export interface AcceptResponseConfig {
accept: true;
}

export function isAcceptResponseConfig(input: any): input is AcceptResponseConfig {
return input.accept === true;
}

export function isSimpleAcceptResponseConfig(input: any): input is AcceptResponseConfig {
return input.accept === true && Object.keys(input).length === 1;
}

export interface DeleteAttributeAcceptResponseConfig extends AcceptResponseConfig {
deletionDate: string;
}

export function isDeleteAttributeAcceptResponseConfig(object: any): object is DeleteAttributeAcceptResponseConfig {
return "deletionDate" in object;
}

export interface FreeTextAcceptResponseConfig extends AcceptResponseConfig {
freeText: string;
}

export function isFreeTextAcceptResponseConfig(object: any): object is FreeTextAcceptResponseConfig {
return "freeText" in object;
}

export interface ProposeAttributeWithNewAttributeAcceptResponseConfig extends AcceptResponseConfig {
attribute: IdentityAttribute | RelationshipAttribute;
}

export function isProposeAttributeWithNewAttributeAcceptResponseConfig(object: any): object is ProposeAttributeWithNewAttributeAcceptResponseConfig {
return "attribute" in object;
}

export interface ReadAttributeWithNewAttributeAcceptResponseConfig extends AcceptResponseConfig {
newAttribute: IdentityAttribute | RelationshipAttribute;
}

export function isReadAttributeWithNewAttributeAcceptResponseConfig(object: any): object is ReadAttributeWithNewAttributeAcceptResponseConfig {
return "newAttribute" in object;
}

export type AcceptResponseConfigDerivation =
| AcceptResponseConfig
| DeleteAttributeAcceptResponseConfig
| FreeTextAcceptResponseConfig
| ProposeAttributeWithNewAttributeAcceptResponseConfig
| ReadAttributeWithNewAttributeAcceptResponseConfig;

export type ResponseConfig = AcceptResponseConfigDerivation | RejectResponseConfig;
2 changes: 2 additions & 0 deletions packages/runtime/src/modules/decide/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./RequestConfig";
export * from "./ResponseConfig";
7 changes: 7 additions & 0 deletions packages/runtime/src/useCases/common/RuntimeErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ class IdentityDeletionProcess {
}
}

class DeciderModule {
public requestConfigDoesNotMatchResponseConfig() {
return new ApplicationError("error.runtime.decide.requestConfigDoesNotMatchResponseConfig", "The RequestConfig does not match the ResponseConfig.");
}
}

export class RuntimeErrors {
public static readonly general = new General();
public static readonly serval = new Serval();
Expand All @@ -253,4 +259,5 @@ export class RuntimeErrors {
public static readonly notifications = new Notifications();
public static readonly attributes = new Attributes();
public static readonly identityDeletionProcess = new IdentityDeletionProcess();
public static readonly deciderModule = new DeciderModule();
}
2 changes: 1 addition & 1 deletion packages/runtime/src/useCases/common/Schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21978,7 +21978,7 @@ export const CreateOwnRelationshipTemplateRequest: any = {
},
"AddressString": {
"type": "string",
"pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}"
"pattern": "did:e:((([A-Za-z0-9]+(-[A-Za-z0-9]+)*)\\.)+[a-z]{2,}|localhost):dids:[0-9a-f]{22}"
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion packages/runtime/test/lib/RuntimeServiceProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import correlator from "correlation-id";
import { AnonymousServices, ConsumptionServices, DataViewExpander, RuntimeConfig, TransportServices } from "../../src";
import { AnonymousServices, ConsumptionServices, DataViewExpander, DeciderModuleConfigurationOverwrite, RuntimeConfig, TransportServices } from "../../src";
import { MockEventBus } from "./MockEventBus";
import { TestRuntime } from "./TestRuntime";

Expand All @@ -15,6 +15,7 @@ export interface TestRuntimeServices {
export interface LaunchConfiguration {
enableDatawallet?: boolean;
enableDeciderModule?: boolean;
configureDeciderModule?: DeciderModuleConfigurationOverwrite;
enableRequestModule?: boolean;
enableAttributeListenerModule?: boolean;
enableNotificationModule?: boolean;
Expand Down Expand Up @@ -87,13 +88,16 @@ export class RuntimeServiceProvider {
if (launchConfiguration.enableAttributeListenerModule) config.modules.attributeListener.enabled = true;
if (launchConfiguration.enableNotificationModule) config.modules.notification.enabled = true;

config.modules.decider.automationConfig = launchConfiguration.configureDeciderModule?.automationConfig;

const runtime = new TestRuntime(
config,
{
setDefaultRepositoryAttributes: launchConfiguration.enableDefaultRepositoryAttributes ?? false
},
launchConfiguration.useCorrelator ? correlator : undefined
);

this.runtimes.push(runtime);

await runtime.init();
Expand Down
Loading

0 comments on commit b521f4f

Please sign in to comment.