Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/RewriteHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ export default class RewriteHandler {
let rewrittenResponse = response;
this.matches.reverse().forEach(({ rewriter, paths }) => {
paths.forEach(path => {
rewrittenResponse = rewriteResultsAtPath(rewrittenResponse, path, (parentResponse, key) =>
rewriter.rewriteResponse(parentResponse, key)
rewrittenResponse = rewriteResultsAtPath(
rewrittenResponse,
path,
(parentResponse, key, index) => rewriter.rewriteResponse(parentResponse, key, index)
);
});
});
Expand Down
12 changes: 5 additions & 7 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ interface ResultObj {
export const rewriteResultsAtPath = (
results: ResultObj,
path: ReadonlyArray<string>,
callback: (parentResult: any, key: string | number) => any
callback: (parentResult: any, key: string, position?: number) => any
): ResultObj => {
if (path.length === 0) return results;

Expand All @@ -271,12 +271,10 @@ export const rewriteResultsAtPath = (

if (path.length === 1) {
if (Array.isArray(curResults)) {
newResults[curPathElm] = curResults.map((_, index) => {
const newValue = callback(curResults, index);
return newValue;
});

return newResults;
return curResults.reduce(
(reducedResults, _, index) => callback(reducedResults, curPathElm, index),
Copy link
Collaborator

@chanind chanind Nov 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, I see what you're doing here. We don't have enough info to rewrite child array just on the parent object and the key. I think this is the best fix we can do while keeping the style as close to the current style as possible. 👍

results
);
}

return callback(results, curPathElm);
Expand Down
24 changes: 9 additions & 15 deletions src/rewriters/NestFieldOutputsRewriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,17 @@ class NestFieldOutputsRewriter extends Rewriter {
} as NodeAndVarDefs;
}

public rewriteResponse(response: any, key: string | number) {
const pathResponse = response[key];
public rewriteResponse(response: any, key: string, index?: number) {
// Extract the element we are working on
const element = super.extractReponseElement(response, key, index);
if (element === null || typeof element !== 'object') return response;

if (typeof pathResponse === 'object') {
// undo the nesting in the response so it matches the original query
if (
pathResponse[this.newOutputName] &&
typeof pathResponse[this.newOutputName] === 'object'
) {
const rewrittenResponse = { ...pathResponse, ...pathResponse[this.newOutputName] };
delete rewrittenResponse[this.newOutputName];
// Undo the nesting in the response so it matches the original query
if (element[this.newOutputName] && typeof element[this.newOutputName] === 'object') {
const newElement = { ...element, ...element[this.newOutputName] };
delete newElement[this.newOutputName];

return {
...response,
[key]: rewrittenResponse
};
}
return super.rewriteResponseElement(response, newElement, key, index);
}

return response;
Expand Down
52 changes: 51 additions & 1 deletion src/rewriters/Rewriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,57 @@ abstract class Rewriter {
return variables;
}

public rewriteResponse(response: any, key: string | number): any {
/*
* Receives the parent object of the matched field with the key of the matched field.
* For arrays, the index of the element is also present.
*/
public rewriteResponse(response: any, key: string, index?: number): any {
return response;
}

/*
* Helper that extracts the element from the response if possible otherwise returns null.
*/
protected extractReponseElement(response: any, key: string, index?: number): any {
// Verify the response format
let element = null;
if (response === null || typeof response !== 'object') return element;

// Extract the key
element = response[key] || null;

// Extract the position
if (Array.isArray(element)) {
element = element[index!] || null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the ! for?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

return element;
}

/*
* Helper that rewrite the element from the response if possible and returns the response.
*/
protected rewriteResponseElement(
response: any,
newElement: any,
key: string,
index?: number
): any {
// Verify the response format
if (response === null || typeof response !== 'object') return response;

// Extract the key
let element = response[key];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getting a linting error here, this should be const

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right can you fix it since the PR is merge?


// Extract the position
// NOTE: We might eventually want to create an array if one is not present at the key
// and we receive an index in input
if (Array.isArray(element)) {
element[index!] = newElement;
} else {
response[key] = newElement;
}

return response;
}
}
Expand Down
20 changes: 8 additions & 12 deletions src/rewriters/ScalarFieldToObjectFieldRewriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface ScalarFieldToObjectFieldRewriterOpts extends RewriterOpts {
}

/**
* Rewriter which nests output fields inside of a new output object
* Rewriter which nests a scalar field inside of a new output object
* ex: change from `field { subField }` to `field { subField { objectfield } }`
*/
class ScalarFieldToObjectFieldRewriter extends Rewriter {
Expand Down Expand Up @@ -48,18 +48,14 @@ class ScalarFieldToObjectFieldRewriter extends Rewriter {
} as NodeAndVarDefs;
}

public rewriteResponse(response: any, key: string | number) {
if (typeof response === 'object') {
const pathResponse = response[key];
public rewriteResponse(response: any, key: string, index?: number) {
// Extract the element we are working on
const element = super.extractReponseElement(response, key, index);
if (element === null) return response;

// undo the nesting in the response so it matches the original query
return {
...response,
[key]: pathResponse[this.objectFieldName]
};
}

return response;
// Undo the nesting in the response so it matches the original query
const newElement = element[this.objectFieldName];
return super.rewriteResponseElement(response, newElement, key, index);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/rewriters/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { default as Rewriter } from './Rewriter';
export { default as Rewriter, RewriterOpts } from './Rewriter';
export { default as FieldArgNameRewriter } from './FieldArgNameRewriter';
export { default as FieldArgsToInputTypeRewriter } from './FieldArgsToInputTypeRewriter';
export { default as FieldArgTypeRewriter } from './FieldArgTypeRewriter';
Expand Down
8 changes: 4 additions & 4 deletions test/ast.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ describe('ast utils', () => {
]
});
expect(
rewriteResultsAtPath(obj, ['things', 'moreThings'], (elm, path) => ({
...elm[path],
meh: '7'
}))
rewriteResultsAtPath(obj, ['things', 'moreThings'], (elm, path, index) => {
elm[path][index!] = { ...elm[path][index!], meh: '7' };
return elm;
})
).toEqual({
things: [
{
Expand Down
56 changes: 55 additions & 1 deletion test/functional/rewriteNestFieldOutputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import RewriteHandler from '../../src/RewriteHandler';
import NestFieldOutputsRewriter from '../../src/rewriters/NestFieldOutputsRewriter';
import { gqlFmt } from '../testUtils';

describe('Rewrite field args to input type', () => {
describe('Rewrite output fields inside of a new output object', () => {
it('allows nesting the args provided into an input type', () => {
const handler = new RewriteHandler([
new NestFieldOutputsRewriter({
Expand Down Expand Up @@ -102,4 +102,58 @@ describe('Rewrite field args to input type', () => {
}
});
});

it('allows nesting the args provided in an array', () => {
const handler = new RewriteHandler([
new NestFieldOutputsRewriter({
fieldName: 'createCats',
newOutputName: 'cat',
outputsToNest: ['name', 'color', 'id']
})
]);
const query = gqlFmt`
mutation createManyCats {
createCats {
id
name
color
}
}
`;
const expectedRewritenQuery = gqlFmt`
mutation createManyCats {
createCats {
cat {
id
name
color
}
}
}
`;
expect(handler.rewriteRequest(query)).toEqual({
query: expectedRewritenQuery
});
expect(
handler.rewriteResponse({
createCats: [
{
cat: {
id: 1,
name: 'jack',
color: 'blue'
}
}
]
})
).toEqual({
createCats: [
{
id: 1,
name: 'jack',
color: 'blue'
}
]
});
});
});
46 changes: 45 additions & 1 deletion test/functional/rewriteScalarFieldToObjectField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ScalarFieldToObjectFieldRewriter from '../../src/rewriters/ScalarFieldToO
import { gqlFmt } from '../testUtils';

describe('Rewrite scalar field to be a nested object with a single scalar field', () => {
it('rewrites a scalar field to be an objet field with 1 scalar subfield', () => {
it('rewrites a scalar field to be an object field with 1 scalar subfield', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅 thanks for fixing!

const handler = new RewriteHandler([
new ScalarFieldToObjectFieldRewriter({
fieldName: 'title',
Expand Down Expand Up @@ -226,4 +226,48 @@ describe('Rewrite scalar field to be a nested object with a single scalar field'
}
});
});

it('rewrites a scalar field array to be an array of object fields with 1 scalar subfield', () => {
const handler = new RewriteHandler([
new ScalarFieldToObjectFieldRewriter({
fieldName: 'titles',
objectFieldName: 'text'
})
]);

const query = gqlFmt`
query getThing {
thing {
titles
}
}
`;
const expectedRewritenQuery = gqlFmt`
query getThing {
thing {
titles {
text
}
}
}
`;
expect(handler.rewriteRequest(query)).toEqual({
query: expectedRewritenQuery
});
expect(
handler.rewriteResponse({
thing: {
titles: [
{
text: 'THING'
}
]
}
})
).toEqual({
thing: {
titles: ['THING']
}
});
});
});
86 changes: 86 additions & 0 deletions test/functional/rewriter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import Rewriter, { RewriterOpts } from '../../src/rewriters/Rewriter';

describe('rewriter', () => {
class TestRewriter extends Rewriter {
constructor(options: RewriterOpts) {
super(options);
}

public extractReponseElement(response: any, key: string, index?: number): any {
return super.extractReponseElement(response, key, index);
}

public rewriteResponseElement(
response: any,
newElement: any,
key: string,
index?: number
): any {
return super.rewriteResponseElement(response, newElement, key, index);
}
}

describe('extractResponseElement', () => {
const rewriter = new TestRewriter({ fieldName: 'test' });

it('can extract element in object', () => {
const key = 'key';
const element = { a: 1 };
const response = { [key]: element };

expect(rewriter.extractReponseElement(response, key)).toEqual(element);
});

it('can extract element in array', () => {
const key = 'key';
const element = { a: 1 };
const response = { [key]: [element] };

expect(rewriter.extractReponseElement(response, key, 0)).toEqual(element);
});

it('does not fail on null, empty or malformed response', () => {
const key = 'key';

expect(rewriter.extractReponseElement(null, key)).toEqual(null);
expect(rewriter.extractReponseElement('string', key)).toEqual(null);
expect(rewriter.extractReponseElement({ a: 1 }, key)).toEqual(null);
});
});

describe('rewriteResponseElement', () => {
const rewriter = new TestRewriter({ fieldName: 'test' });

it('can replace element in object', () => {
const key = 'key';
const newElement = { a: 1 };
const response = { [key]: 1 };

expect(rewriter.rewriteResponseElement(response, newElement, key)).toEqual({
[key]: newElement
});
});

it('can replace element in array', () => {
const key = 'key';
const newElement = { a: 1 };
const response = { [key]: [1] };

expect(rewriter.rewriteResponseElement(response, newElement, key, 0)).toEqual({
[key]: [newElement]
});
});

it('does not fail on null, empty or malformed response', () => {
const key = 'key';
const newElement = { a: 1 };

expect(rewriter.rewriteResponseElement(null, newElement, key)).toEqual(null);
expect(rewriter.rewriteResponseElement('string', newElement, key)).toEqual('string');
expect(rewriter.rewriteResponseElement({ a: 1 }, newElement, key)).toEqual({
a: 1,
[key]: newElement
});
});
});
});