@dcluttr/dclare-mcp 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -17,12 +17,19 @@ function buildJoinedCubeMap(dataset, allDatasets) {
|
|
|
17
17
|
}
|
|
18
18
|
return map;
|
|
19
19
|
}
|
|
20
|
-
function validateMember(member, allowedLocal, joinedCubes, label) {
|
|
20
|
+
function validateMember(member, allowedLocal, joinedCubes, label, currentCubeName) {
|
|
21
21
|
if (isJoinedMember(member)) {
|
|
22
22
|
const dotIndex = member.indexOf(".");
|
|
23
23
|
const cubeName = member.slice(0, dotIndex);
|
|
24
24
|
const columnName = member.slice(dotIndex + 1);
|
|
25
25
|
const stripped = normalizeMemberName(columnName);
|
|
26
|
+
// If the prefix matches the current cube, treat as a local member
|
|
27
|
+
if (currentCubeName && cubeName === currentCubeName) {
|
|
28
|
+
if (allowedLocal.has(stripped)) {
|
|
29
|
+
return stripped;
|
|
30
|
+
}
|
|
31
|
+
throw new AppError("INVALID_QUERY", `${label} '${member}' is not allowed for this dataset.`, 400);
|
|
32
|
+
}
|
|
26
33
|
const joinedColumns = joinedCubes.get(cubeName);
|
|
27
34
|
if (joinedColumns && joinedColumns.has(stripped)) {
|
|
28
35
|
return `${cubeName}.${stripped}`;
|
|
@@ -41,7 +48,7 @@ export class QueryGuardrails {
|
|
|
41
48
|
const allowedMetrics = new Set(dataset.metrics.map((metric) => metric.name));
|
|
42
49
|
const allowedSegments = new Set((dataset.segments ?? []).map((segment) => segment.name));
|
|
43
50
|
const joinedCubes = buildJoinedCubeMap(dataset, allDatasets ?? []);
|
|
44
|
-
const validatedDimensions = (input.dimensions ?? []).map((d) => validateMember(d, allowedColumns, joinedCubes, "Dimension"));
|
|
51
|
+
const validatedDimensions = (input.dimensions ?? []).map((d) => validateMember(d, allowedColumns, joinedCubes, "Dimension", dataset.cubeName));
|
|
45
52
|
for (const metric of input.metrics ?? []) {
|
|
46
53
|
const normalized = normalizeMemberName(metric);
|
|
47
54
|
if (!allowedMetrics.has(normalized)) {
|
|
@@ -50,15 +57,23 @@ export class QueryGuardrails {
|
|
|
50
57
|
}
|
|
51
58
|
for (const segment of input.segments ?? []) {
|
|
52
59
|
const normalized = normalizeMemberName(segment);
|
|
60
|
+
// Strip prefix if it matches current cube name
|
|
61
|
+
if (isJoinedMember(segment)) {
|
|
62
|
+
const dotIndex = segment.indexOf(".");
|
|
63
|
+
const cubeName = segment.slice(0, dotIndex);
|
|
64
|
+
if (cubeName !== dataset.cubeName) {
|
|
65
|
+
throw new AppError("INVALID_QUERY", `Segment '${segment}' is not allowed for this dataset.`, 400);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
53
68
|
if (!allowedSegments.has(normalized)) {
|
|
54
69
|
throw new AppError("INVALID_QUERY", `Segment '${segment}' is not allowed for this dataset.`, 400);
|
|
55
70
|
}
|
|
56
71
|
}
|
|
57
72
|
const validatedTimeDimensions = (input.timeDimensions ?? []).map((item) => {
|
|
58
|
-
const resolved = validateMember(item.member, allowedColumns, joinedCubes, "Time dimension");
|
|
73
|
+
const resolved = validateMember(item.member, allowedColumns, joinedCubes, "Time dimension", dataset.cubeName);
|
|
59
74
|
return { ...item, member: resolved };
|
|
60
75
|
});
|
|
61
|
-
const validFilters = this.validateFilters(input.filters ?? [], allowedColumns, joinedCubes);
|
|
76
|
+
const validFilters = this.validateFilters(input.filters ?? [], allowedColumns, joinedCubes, dataset.cubeName);
|
|
62
77
|
const allFilters = [...validFilters];
|
|
63
78
|
const order = this.validateOrder(input.order ?? [], allowedColumns, allowedMetrics, joinedCubes, dataset.cubeName);
|
|
64
79
|
const normalizedSegments = (input.segments ?? []).map((segment) => normalizeMemberName(segment));
|
|
@@ -81,9 +96,9 @@ export class QueryGuardrails {
|
|
|
81
96
|
limit
|
|
82
97
|
};
|
|
83
98
|
}
|
|
84
|
-
validateFilters(filters, allowedColumns, joinedCubes) {
|
|
99
|
+
validateFilters(filters, allowedColumns, joinedCubes, currentCubeName) {
|
|
85
100
|
return filters.map((filter) => {
|
|
86
|
-
const resolved = validateMember(filter.member, allowedColumns, joinedCubes, "Filter member");
|
|
101
|
+
const resolved = validateMember(filter.member, allowedColumns, joinedCubes, "Filter member", currentCubeName);
|
|
87
102
|
if (!Array.isArray(filter.values) || filter.values.length === 0) {
|
|
88
103
|
throw new AppError("INVALID_QUERY", `Filter '${filter.member}' must include at least one value.`, 400);
|
|
89
104
|
}
|
|
@@ -98,7 +113,7 @@ export class QueryGuardrails {
|
|
|
98
113
|
for (const item of order) {
|
|
99
114
|
let member;
|
|
100
115
|
try {
|
|
101
|
-
member = validateMember(item.member, allowedColumns, joinedCubes, "Order member");
|
|
116
|
+
member = validateMember(item.member, allowedColumns, joinedCubes, "Order member", cubeName);
|
|
102
117
|
}
|
|
103
118
|
catch {
|
|
104
119
|
const normalized = normalizeMemberName(item.member);
|