Skip to content

Commit 30050be

Browse files
authored
Merge pull request #29 from code0-tech/feat/#28
Settings and params aren't correctly merged
2 parents 8b4a478 + 000bb4d commit 30050be

7 files changed

Lines changed: 80 additions & 14 deletions

File tree

ts/scripts/build-definitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ function buildSettingDecorators(settings: any[]): string[] {
140140
return settings.flatMap(s => {
141141
const props = [
142142
`identifier: ${JSON.stringify(s.identifier)},`,
143-
...(s.unique && s.unique !== "NONE" ? [`unique: ${JSON.stringify(s.unique)} as any,`] : []),
143+
...(s.unique && s.unique !== "NONE" ? [`unique: ${JSON.stringify(s.unique)},`] : []),
144144
...(Array.isArray(s.name) && s.name.length ? [`name: [${renderTranslationsInline(s.name)}],`] : []),
145145
...(Array.isArray(s.description) && s.description.length ? [`description: [${renderTranslationsInline(s.description)}],`] : []),
146146
...(s.optional ? [`optional: true,`] : []),

ts/src/decorators/event.dec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {EventSettingProps} from "../models/event.model";
22

33
export const EventSetting = (setting: EventSettingProps): ClassDecorator =>
44
(target) => {
5-
const settings = Reflect.getMetadata('hercules:flow_settings', target) || [];
5+
// getOwnMetadata: getMetadata would return the parent class's array on subclasses,
6+
// and unshift would mutate the parent's settings.
7+
const settings = Reflect.getOwnMetadata('hercules:flow_settings', target) || [];
68
settings.unshift(setting);
79
Reflect.defineMetadata('hercules:flow_settings', settings, target);
810
}

ts/src/decorators/function.dec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export const ThrowsError = (throwsError: boolean = true): ClassDecorator =>
1111

1212
export const Parameter = (parameter: FunctionParameterProps): ClassDecorator =>
1313
(target) => {
14-
const parameters = Reflect.getMetadata('hercules:function_parameters', target) || [];
14+
// getOwnMetadata: getMetadata would return the parent class's array on subclasses,
15+
// and unshift would mutate the parent's parameters.
16+
const parameters = Reflect.getOwnMetadata('hercules:function_parameters', target) || [];
1517
parameters.unshift(parameter);
1618
Reflect.defineMetadata('hercules:function_parameters', parameters, target);
1719
}

ts/src/internal/module-builder.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {constructValue} from "@code0-tech/tucana/helpers";
2-
import {DefinitionDataType, FlowType, FlowTypeSetting, Module, ModuleConfigurationDefinition, RuntimeFlowType, RuntimeFlowTypeSetting} from "@code0-tech/tucana/shared";
2+
import {DefinitionDataType, FlowType, FlowTypeSetting, FlowTypeSetting_UniquenessScope, Module, ModuleConfigurationDefinition, RuntimeFlowType, RuntimeFlowTypeSetting} from "@code0-tech/tucana/shared";
33
import type {FunctionProps} from "../models/function.model";
44
import type {RuntimeFunctionProps} from "../models/runtime_function.model";
55
import type {ConfigurationDefinition, Translation} from "../types";
@@ -22,6 +22,18 @@ export interface ModuleBuildData {
2222
runtimeFunctions: RuntimeFunctionProps[];
2323
}
2424

25+
// The protobuf field is an enum (int32); string names like "PROJECT" would fail binary serialization.
26+
// Returns number since FlowTypeSetting and RuntimeFlowTypeSetting each declare their own (identical) enum.
27+
function toUniquenessScope(unique: FlowTypeSetting_UniquenessScope | keyof typeof FlowTypeSetting_UniquenessScope | undefined): number {
28+
if (unique == null) return FlowTypeSetting_UniquenessScope.NONE;
29+
if (typeof unique === "string") {
30+
const scope = FlowTypeSetting_UniquenessScope[unique];
31+
if (typeof scope !== "number") throw new Error(`Invalid uniqueness scope: ${JSON.stringify(unique)}`);
32+
return scope;
33+
}
34+
return unique;
35+
}
36+
2537
export function buildModule(data: ModuleBuildData): Module {
2638
return {
2739
identifier: data.identifier,
@@ -57,7 +69,7 @@ export function buildModule(data: ModuleBuildData): Module {
5769
identifier: ft.identifier,
5870
settings: (ft.settings ?? []).map(s => ({
5971
identifier: s.identifier,
60-
unique: s.unique ?? 1,
72+
unique: toUniquenessScope(s.unique),
6173
linkedDataTypeIdentifiers: s.linkedDataTypeIdentifiers ?? [],
6274
...(s.defaultValue != null ? {defaultValue: constructValue(s.defaultValue)} : {}),
6375
name: s.name ?? [],
@@ -82,7 +94,7 @@ export function buildModule(data: ModuleBuildData): Module {
8294
identifier: rft.identifier,
8395
runtimeSettings: (rft.settings ?? []).map(s => ({
8496
identifier: s.identifier,
85-
unique: s.unique ?? 1,
97+
unique: toUniquenessScope(s.unique),
8698
...(s.defaultValue != null ? {defaultValue: constructValue(s.defaultValue)} : {}),
8799
name: s.name ?? [],
88100
description: s.description ?? [],

ts/src/map/event.map.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const eventMap = <T extends RuntimeEventClass>(klass: EventClass<T>): Eve
99

1010
const identifier: string = Reflect.getMetadata('hercules:identifier', klass);
1111
const signature: string = Reflect.getMetadata('hercules:signature', klass);
12-
const settings: EventSettingProps[] = Reflect.getMetadata('hercules:flow_settings', klass) || [];
12+
const settings: EventSettingProps[] = Reflect.getOwnMetadata('hercules:flow_settings', klass) || [];
1313
const name: Translation[] = Reflect.getMetadata('hercules:name', klass);
1414
const description: Translation[] = Reflect.getMetadata('hercules:description', klass);
1515
const documentation: Translation[] = Reflect.getMetadata('hercules:documentation', klass);
@@ -26,12 +26,12 @@ export const eventMap = <T extends RuntimeEventClass>(klass: EventClass<T>): Eve
2626
}
2727
}
2828

29-
const mergedSettings: EventSettingProps[] = [...settings];
30-
for (const rs of runtimeEvent.settings ?? []) {
31-
if (!mergedSettings.find(s => s.identifier === rs.identifier)) {
32-
mergedSettings.push({...rs});
33-
}
34-
}
29+
// Settings come from the runtime event (in its order); the event class only overrides
30+
// individual properties (e.g. defaultValue, hidden) per identifier.
31+
const mergedSettings: EventSettingProps[] = (runtimeEvent.settings ?? []).map(rs => {
32+
const override = settings.find(s => s.identifier === rs.identifier);
33+
return override ? {...rs, ...override} : {...rs};
34+
});
3535

3636
return {
3737
runtimeIdentifier: runtimeEvent.identifier,

ts/src/models/event.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {RuntimeEventClass} from "./runtime_event.model";
55

66
export interface EventSettingProps {
77
identifier: string,
8-
unique?: FlowTypeSetting_UniquenessScope,
8+
unique?: FlowTypeSetting_UniquenessScope | keyof typeof FlowTypeSetting_UniquenessScope,
99
linkedDataTypeIdentifiers?: string[],
1010
defaultValue?: PlainValue,
1111
name?: Translation[],

ts/test/index.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import "reflect-metadata";
22
import { describe, expect, it } from "vitest";
33
import { Parameter } from "../src/decorators/function.dec";
44
import { EventSetting } from "../src/decorators/event.dec";
5+
import { Identifier } from "../src/decorators/meta.dec";
6+
import { eventMap } from "../src/map/event.map";
7+
import { runtimeEventMap } from "../src/map/runtime_event.map";
8+
import { Rest } from "../src/definitions/draco_rest/runtime_flow_types/rest";
59

610
describe("Parameter decorator", () => {
711
it("preserves source order across multiple decorators", () => {
@@ -27,6 +31,52 @@ describe("Parameter decorator", () => {
2731
});
2832
});
2933

34+
describe("eventMap", () => {
35+
const restSettingOrder = [
36+
"httpSchema",
37+
"httpURL",
38+
"httpMethod",
39+
"httpAuth",
40+
"httpAuthValue",
41+
"input_schema",
42+
];
43+
44+
it("keeps the runtime event's setting order regardless of override order", () => {
45+
@Identifier("ScrambledOverrides")
46+
@EventSetting({ identifier: "input_schema", hidden: true, defaultValue: {} })
47+
@EventSetting({ identifier: "httpAuthValue", hidden: true })
48+
@EventSetting({ identifier: "httpSchema", hidden: true, defaultValue: "application/json" })
49+
@EventSetting({ identifier: "httpMethod", hidden: true, defaultValue: "POST" })
50+
class ScrambledOverrides extends Rest {}
51+
52+
const def = eventMap(ScrambledOverrides);
53+
expect(def.settings?.map(s => s.identifier)).toEqual(restSettingOrder);
54+
});
55+
56+
it("merges override properties into the runtime event's setting", () => {
57+
@Identifier("MethodOverride")
58+
@EventSetting({ identifier: "httpMethod", hidden: true, defaultValue: "POST" })
59+
class MethodOverride extends Rest {}
60+
61+
const httpMethod = eventMap(MethodOverride).settings?.find(s => s.identifier === "httpMethod");
62+
expect(httpMethod?.hidden).toBe(true);
63+
expect(httpMethod?.defaultValue).toBe("POST");
64+
expect(httpMethod?.name).toEqual([{ code: "en-US", content: "Method" }]);
65+
});
66+
67+
it("does not pollute the runtime event's settings via subclass overrides", () => {
68+
@Identifier("PollutionCheck")
69+
@EventSetting({ identifier: "httpMethod", hidden: true, defaultValue: "POST" })
70+
class PollutionCheck extends Rest {}
71+
72+
eventMap(PollutionCheck);
73+
74+
const restSettings = runtimeEventMap(Rest).settings ?? [];
75+
expect(restSettings.map(s => s.identifier)).toEqual(restSettingOrder);
76+
expect(restSettings.find(s => s.identifier === "httpMethod")?.hidden).toBeUndefined();
77+
});
78+
});
79+
3080
describe("EventSetting decorator", () => {
3181
it("preserves source order across multiple decorators", () => {
3282
@EventSetting({ identifier: "first" })

0 commit comments

Comments
 (0)