Skip to content

Commit 40a48c9

Browse files
authored
Rest api sparse fieldsets (#2252)
1 parent 1c4cb73 commit 40a48c9

File tree

2 files changed

+566
-8
lines changed

2 files changed

+566
-8
lines changed

packages/server/src/api/rest/index.ts

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ class RequestHandler extends APIHandlerBase {
177177
status: 400,
178178
title: 'Invalid value for type',
179179
},
180+
duplicatedFieldsParameter: {
181+
status: 400,
182+
title: 'Fields Parameter Duplicated',
183+
},
180184
forbidden: {
181185
status: 403,
182186
title: 'Operation is forbidden',
@@ -185,6 +189,7 @@ class RequestHandler extends APIHandlerBase {
185189
status: 422,
186190
title: 'Operation is unprocessable due to validation errors',
187191
},
192+
188193
unknownError: {
189194
status: 400,
190195
title: 'Unknown error',
@@ -511,7 +516,7 @@ class RequestHandler extends APIHandlerBase {
511516
// handle "include" query parameter
512517
let include: string[] | undefined;
513518
if (query?.include) {
514-
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include);
519+
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include, query);
515520
if (error) {
516521
return error;
517522
}
@@ -521,6 +526,20 @@ class RequestHandler extends APIHandlerBase {
521526
include = allIncludes;
522527
}
523528

529+
// handle partial results for requested type
530+
const { select, error } = this.buildPartialSelect(type, query);
531+
if (error) return error;
532+
if (select) {
533+
args.select = { ...select, ...args.select };
534+
if (args.include) {
535+
args.select = {
536+
...args.select,
537+
...args.include,
538+
};
539+
args.include = undefined;
540+
}
541+
}
542+
524543
const entity = await prisma[type].findUnique(args);
525544

526545
if (entity) {
@@ -555,7 +574,7 @@ class RequestHandler extends APIHandlerBase {
555574
// handle "include" query parameter
556575
let include: string[] | undefined;
557576
if (query?.include) {
558-
const { select: relationSelect, error, allIncludes } = this.buildRelationSelect(type, query.include);
577+
const { select: relationSelect, error, allIncludes } = this.buildRelationSelect(type, query.include, query);
559578
if (error) {
560579
return error;
561580
}
@@ -566,7 +585,14 @@ class RequestHandler extends APIHandlerBase {
566585
select = relationSelect;
567586
}
568587

569-
select = select ?? { [relationship]: true };
588+
// handle partial results for requested type
589+
if (!select) {
590+
const { select: partialFields, error } = this.buildPartialSelect(lowerCaseFirst(relationInfo.type), query);
591+
if (error) return error;
592+
593+
select = partialFields ? { [relationship]: { select: { ...partialFields } } } : { [relationship]: true };
594+
}
595+
570596
const args: any = {
571597
where: this.makePrismaIdFilter(typeInfo.idFields, resourceId),
572598
select,
@@ -710,7 +736,7 @@ class RequestHandler extends APIHandlerBase {
710736
// handle "include" query parameter
711737
let include: string[] | undefined;
712738
if (query?.include) {
713-
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include);
739+
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include, query);
714740
if (error) {
715741
return error;
716742
}
@@ -720,6 +746,20 @@ class RequestHandler extends APIHandlerBase {
720746
include = allIncludes;
721747
}
722748

749+
// handle partial results for requested type
750+
const { select, error } = this.buildPartialSelect(type, query);
751+
if (error) return error;
752+
if (select) {
753+
args.select = { ...select, ...args.select };
754+
if (args.include) {
755+
args.select = {
756+
...args.select,
757+
...args.include,
758+
};
759+
args.include = undefined;
760+
}
761+
}
762+
723763
const { offset, limit } = this.getPagination(query);
724764
if (offset > 0) {
725765
args.skip = offset;
@@ -738,6 +778,7 @@ class RequestHandler extends APIHandlerBase {
738778
};
739779
} else {
740780
args.take = limit;
781+
741782
const [entities, count] = await Promise.all([
742783
prisma[type].findMany(args),
743784
prisma[type].count({ where: args.where ?? {} }),
@@ -762,6 +803,33 @@ class RequestHandler extends APIHandlerBase {
762803
}
763804
}
764805

806+
private buildPartialSelect(type: string, query: Record<string, string | string[]> | undefined) {
807+
const selectFieldsQuery = query?.[`fields[${type}]`];
808+
if (!selectFieldsQuery) {
809+
return { select: undefined, error: undefined };
810+
}
811+
812+
if (Array.isArray(selectFieldsQuery)) {
813+
return {
814+
select: undefined,
815+
error: this.makeError('duplicatedFieldsParameter', `duplicated fields query for type ${type}`),
816+
};
817+
}
818+
819+
const typeInfo = this.typeMap[lowerCaseFirst(type)];
820+
if (!typeInfo) {
821+
return { select: undefined, error: this.makeUnsupportedModelError(type) };
822+
}
823+
824+
const selectFieldNames = selectFieldsQuery.split(',').filter((i) => i);
825+
826+
const fields = selectFieldNames.reduce((acc, curr) => ({ ...acc, [curr]: true }), {});
827+
828+
return {
829+
select: { ...this.makeIdSelect(typeInfo.idFields), ...fields },
830+
};
831+
}
832+
765833
private addTotalCountToMeta(meta: any, total: any) {
766834
return meta ? Object.assign(meta, { total }) : Object.assign({}, { total });
767835
}
@@ -1790,7 +1858,11 @@ class RequestHandler extends APIHandlerBase {
17901858
return { sort: result, error: undefined };
17911859
}
17921860

1793-
private buildRelationSelect(type: string, include: string | string[]) {
1861+
private buildRelationSelect(
1862+
type: string,
1863+
include: string | string[],
1864+
query: Record<string, string | string[]> | undefined
1865+
) {
17941866
const typeInfo = this.typeMap[lowerCaseFirst(type)];
17951867
if (!typeInfo) {
17961868
return { select: undefined, error: this.makeUnsupportedModelError(type) };
@@ -1820,11 +1892,24 @@ class RequestHandler extends APIHandlerBase {
18201892
return { select: undefined, error: this.makeUnsupportedModelError(relationInfo.type) };
18211893
}
18221894

1895+
// handle partial results for requested type
1896+
const { select, error } = this.buildPartialSelect(lowerCaseFirst(relationInfo.type), query);
1897+
if (error) return { select: undefined, error };
1898+
18231899
if (i !== parts.length - 1) {
1824-
currPayload[relation] = { include: { ...currPayload[relation]?.include } };
1825-
currPayload = currPayload[relation].include;
1900+
if (select) {
1901+
currPayload[relation] = { select: { ...select } };
1902+
currPayload = currPayload[relation].select;
1903+
} else {
1904+
currPayload[relation] = { include: { ...currPayload[relation]?.include } };
1905+
currPayload = currPayload[relation].include;
1906+
}
18261907
} else {
1827-
currPayload[relation] = true;
1908+
currPayload[relation] = select
1909+
? {
1910+
select: { ...select },
1911+
}
1912+
: true;
18281913
}
18291914
}
18301915
}

0 commit comments

Comments
 (0)