Skip to content

Commit

Permalink
fix(facade): ignore null data in pre- and postProcessGQL resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerald Baulig committed Aug 29, 2024
1 parent ff9ccda commit f7ddb3a
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 50 deletions.
2 changes: 1 addition & 1 deletion packages/cart/test/bench.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ describe('benchmarks', () => {
height: undefined,
human: {offer: 'Package up to 20kg'},
maxWeight: 20000,
price: new Decimal(11.8),
price: new Decimal(12.95),
type: 'package',
width: undefined
}],
Expand Down
59 changes: 41 additions & 18 deletions packages/facade/src/gql/protos/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ import { type ServiceDescriptorProto } from 'ts-proto-descriptors';

export const Mutate = ['Create', 'Update', 'Upsert'];

export const preprocessGQLInput = async (data: any, model: GraphQLInputObjectType | GraphQLEnumType | GraphQLInputField | GraphQLInputType): Promise<any> => {
export const preProcessGQLInput = async (
data: any,
model: GraphQLInputObjectType | GraphQLEnumType | GraphQLInputField | GraphQLInputType
): Promise<any> => {
if (data === undefined) {
return undefined;
}

if (model instanceof GraphQLEnumType) {
return data;
}
Expand All @@ -40,11 +47,17 @@ export const preprocessGQLInput = async (data: any, model: GraphQLInputObjectTyp
};
} else {
const fields = model.getFields();
for (let key of Object.keys(fields)) {
if (data && key in data) {
data[key] = await preprocessGQLInput(data[key], fields[key].type);
}
}
const converted = await Promise.all(
Object.keys(fields).filter(
key => key in data
).map(
key => preProcessGQLInput(data[key], fields[key].type)
)
);
return {
...data,
...converted,
};
}
}

Expand All @@ -55,13 +68,15 @@ export const preprocessGQLInput = async (data: any, model: GraphQLInputObjectTyp
}

if (model instanceof GraphQLNonNull) {
return await preprocessGQLInput(data, model.ofType);
return await preProcessGQLInput(data, model.ofType);
}

if (model instanceof GraphQLList) {
for (let i = 0; i < data.length; i++) {
data[i] = await preprocessGQLInput(data[i], model.ofType);
}
return await Promise.all(
data.map(
(d: any) => preProcessGQLInput(d, model.ofType)
)
);
}

if (model instanceof GraphQLScalarType) {
Expand All @@ -82,12 +97,16 @@ export const preprocessGQLInput = async (data: any, model: GraphQLInputObjectTyp
};

export const postProcessGQLValue = (data: any, model: GraphQLOutputType): any => {
if (data === undefined) {
return undefined;
}

if (model instanceof GraphQLEnumType) {
return data;
}

if (model instanceof GraphQLObjectType) {
if (model.name === 'GoogleProtobufAny' && data?.value) {
if (model.name === 'GoogleProtobufAny' && data.value) {
// TODO Use encoded once resource base supports it

const decoded = JSON.parse((data.value as Buffer).toString());
Expand All @@ -98,10 +117,14 @@ export const postProcessGQLValue = (data: any, model: GraphQLOutputType): any =>
};
} else {
const fields = model.getFields();
for (let key of Object.keys(fields)) {
if (data && key in data) {
data[key] = postProcessGQLValue(data[key], fields[key].type);
}
const converted = Object.keys(fields).filter(
key => key in data
).map(
key => postProcessGQLValue(data[key], fields[key].type)
);
return {
...data,
...converted,
}
}
}
Expand All @@ -111,9 +134,9 @@ export const postProcessGQLValue = (data: any, model: GraphQLOutputType): any =>
}

if (model instanceof GraphQLList) {
for (let i = 0; i < data.length; i++) {
data[i] = postProcessGQLValue(data[i], model.ofType);
}
return data.map(
(d: any) => postProcessGQLValue(d, model.ofType)
);
}

return data;
Expand Down
64 changes: 38 additions & 26 deletions packages/facade/src/gql/protos/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import flat from 'array.prototype.flat';
import { getTyping } from './registry.js';
import {
getWhitelistBlacklistConfig, Mutate, postProcessGQLValue,
preprocessGQLInput,
preProcessGQLInput,
} from './graphql.js';
import {
camelCase,
Expand Down Expand Up @@ -75,9 +75,18 @@ const fetchUnauthenticatedUserToken = async (ctx: any, domain: string) => {
return response?.token;
};

export const getGQLResolverFunctions =
<T extends Record<string, any>, CTX extends ServiceClient<CTX, keyof CTX, T>, SRV = any, R = ResolverFn<any, any, ServiceClient<CTX, keyof CTX, T>, any>, B extends keyof T = any, NS extends keyof CTX = any>
(service: ServiceDescriptorProto, key: NS, serviceKey: B, cfg: ServiceConfig): { [key in keyof SRV]: R } => {
export const getGQLResolverFunctions = <
T extends Record<string, any>,
CTX extends ServiceClient<CTX, keyof CTX, T>,
SRV = any,
R = ResolverFn<any, any, ServiceClient<CTX, keyof CTX, T>, any>,
B extends keyof T = any, NS extends keyof CTX = any
> (
service: ServiceDescriptorProto,
key: NS,
serviceKey: B,
cfg: ServiceConfig
): { [key in keyof SRV]: R } => {
if (!service.method) {
return {} as { [key in keyof SRV]: R };
}
Expand Down Expand Up @@ -132,14 +141,11 @@ export const getGQLResolverFunctions =
const client = context[key].client;
const service = client[serviceKey];
try {
const converted = await preprocessGQLInput(args.input, typing.input);
const converted = await preProcessGQLInput(args.input, typing.input);
const scope = args?.input?.scope;

let req = typing.processor.fromPartial(converted);

// convert enum strings to integers
// req = convertEnumToInt(typing, req);

req.subject = getTyping(authSubjectType)!.processor.fromPartial({});
if (subjectField !== null) {
const authToken = (context as any).request!.req.headers['authorization'];
Expand All @@ -151,7 +157,11 @@ export const getGQLResolverFunctions =
}
}

if (!req.subject.token && 'origin' in (context as any).request!.req.headers) {
if (
cfg?.disableUnauthenticatedUserTenant?.toString() != 'true'
&& !req.subject.token
&& 'origin' in (context as any).request!.req.headers
) {
req.subject.token = await fetchUnauthenticatedUserToken(context, (context as any).request!.req.headers['origin']);
}

Expand All @@ -170,13 +180,11 @@ export const getGQLResolverFunctions =
}
}

const methodFunc = service[camelCase(realMethod)] || service[realMethod];
const methodFunc = service[camelCase(realMethod)] ?? service[realMethod];
if (method.clientStreaming) {
const readableStreamKey = Object.keys(req).filter((key) => {
if (req[key] instanceof stream.Stream.Readable) {
return key;
}
});
const readableStreamKey = Object.keys(req).filter(
(key) => req[key] instanceof stream.Stream.Readable
);
if (readableStreamKey.length > 0) {
req = streamToAsyncIterable(req, readableStreamKey[0]);
}
Expand Down Expand Up @@ -521,7 +529,7 @@ export const generateSubServiceResolvers = <
for (const key of Object.keys(meta.options.messages)) {
const message = meta.options.messages[key];
if (message.fields) {
const typing = getTyping('.' + meta.fileDescriptor.package + '.' + key);
const typing = getTyping(`.${meta.fileDescriptor.package}.${key}`);
if (typing) {
const result: any = {};
for (const fieldName of Object.keys(message.fields)) {
Expand All @@ -531,26 +539,26 @@ export const generateSubServiceResolvers = <
const resolver = field['resolver'] as Resolver;

// TODO This creates an N+1 problem!
result[resolver.fieldName as string] = async (parent: any, _: any, ctx: any) => {
result[resolver.fieldName] = async (parent: any, _: any, ctx: any) => {
if (!parent || !(fieldJsonName in parent) || parent[fieldJsonName] === undefined) {
return undefined;
}

resolver.targetService = config?.namespace ?? resolver.targetService
const client = ctx[resolver.targetService].client;
const service = client[resolver.targetSubService];
const idList: string[] = Array.isArray(parent[fieldJsonName]) ? parent[fieldJsonName] : [parent[fieldJsonName]];
const ids: string[] = Array.isArray(parent[fieldJsonName]) ? parent[fieldJsonName] : [parent[fieldJsonName]];

// TODO Support custom input messages
const req = ReadRequest.fromPartial({
filters: [{
filters: idList.map(id => ({
filters: {
field: 'id',
operation: Filter_Operation.eq,
value: id,
type: Filter_ValueType.STRING
})),
operator: FilterOp_Operator.or
operation: Filter_Operation.in,
value: JSON.stringify(ids),
type: Filter_ValueType.ARRAY
},
limit: ids.length
}]
} as any);

Expand All @@ -561,14 +569,18 @@ export const generateSubServiceResolvers = <
req.subject!.token = authToken.split(' ')[1];
}

if (!req.subject!.token && 'origin' in (ctx as any).request!.req.headers) {
if (
config?.disableUnauthenticatedUserTenant?.toString() !== 'true'
&& !req.subject!.token
&& 'origin' in (ctx as any).request!.req.headers
) {
req.subject!.token = await fetchUnauthenticatedUserToken(ctx, (ctx as any).request!.req.headers['origin']);
}

const methodFunc = service[camelCase(resolver.targetMethod)] || service[resolver.targetMethod];
const result = await methodFunc(req);

if (result && result.items && result.items.length) {
if (result?.items?.length) {
if (Array.isArray(parent[fieldJsonName])) {
return result.items.map((item: any) => item.payload);
} else {
Expand Down
8 changes: 4 additions & 4 deletions packages/facade/src/modules/identity/oauth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const createOAuth = (): KoaRouter<{}, IdentityContext> => {
token
});

if (!user || !user.payload) {
if (!user?.payload) {
ctx.body = 'user not logged in';
return next();
}
Expand Down Expand Up @@ -180,11 +180,11 @@ export const createOAuth = (): KoaRouter<{}, IdentityContext> => {
const ids = ctx.identitySrvClient as IdentitySrvGrpcClient;
const user = await ids.o_auth.exchangeCode({
service: ctx.params.service,
code: ctx.request.query['code'] as string,
state: ctx.request.query['state'] as string
code: ctx.request.query.code?.toString(),
state: ctx.request.query.state?.toString()
});

if (!user.user || !user.user.payload || !user.token || (user.user.status && user.user.status.code !== 200)) {
if (!user.user || !user.user.payload || !user.token || user.user.status?.code !== 200) {
ctx.type = 'html';
ctx.body = await register(user.email || '');
return next();
Expand Down
2 changes: 1 addition & 1 deletion packages/facade/tests/custom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('extend', () => {
beforeAll(async () => {
facade = createTestFacade();
await facade.start();
request = agent(facade.server);
request = agent(facade.server) as any;
// await new Promise(resolve => setTimeout(resolve, 20000))
});

Expand Down

0 comments on commit f7ddb3a

Please sign in to comment.