Skip to content

Commit a7a5485

Browse files
benny1hknodkz
andauthored
feat: add EMPTY_STRING & NULL to mongoose Enums (#384)
* New feature: Allow using null and empty String and number as the first char in Enum keys, which make easier for legacy mongoose project to migrate. * test: add test cases * test: fix script `test-prev-vers` Co-authored-by: nodkz <[email protected]>
1 parent d94007b commit a7a5485

File tree

4 files changed

+140
-2
lines changed

4 files changed

+140
-2
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,6 @@
6565
"link": "yarn build && yarn link graphql-compose && yarn link graphql-compose-connection && yarn link graphql-compose-pagination && yarn link mongoose && yarn link",
6666
"unlink": "rimraf node_modules && yarn install",
6767
"semantic-release": "semantic-release",
68-
"test-prev-vers": "yarn add [email protected] @types/[email protected] --dev && yarn lint && git checkout HEAD -- package.json yarn.lock"
68+
"test-prev-vers": "yarn add [email protected] --dev --ignore-scripts && yarn coverage && git checkout HEAD -- package.json yarn.lock"
6969
}
7070
}

src/__tests__/fieldConverter-test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,21 @@ describe('fieldConverter', () => {
220220
const ocEnum = enumToGraphQL(modelFields.oc, '', schemaComposer);
221221
expect(ocEnum.getFieldNames()).toEqual(['ocpp1_6', 'ocpp2_0']);
222222
});
223+
224+
it('should work with EMPTY_STRING & NULL', () => {
225+
const model = mongoose.model(
226+
'EnumTestEmpty',
227+
new mongoose.Schema({
228+
some: {
229+
type: String,
230+
enum: ['', null, 'val1'],
231+
},
232+
})
233+
);
234+
const modelFields = getFieldsFromModel(model);
235+
const SomeEnum = enumToGraphQL(modelFields.some, '', schemaComposer);
236+
expect(SomeEnum.getFieldNames()).toEqual(['EMPTY_STRING', 'NULL', 'val1']);
237+
});
223238
});
224239

225240
describe('embeddedToGraphQL()', () => {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { SchemaComposer, graphql, EnumTypeComposer } from 'graphql-compose';
2+
import { composeMongoose } from '../../index';
3+
import { mongoose } from '../../__mocks__/mongooseCommon';
4+
import { Schema } from 'mongoose';
5+
6+
const schemaComposer = new SchemaComposer<{ req: any }>();
7+
8+
// mongoose.set('debug', true);
9+
10+
const ArticleSchema = new Schema(
11+
{
12+
name: {
13+
type: String,
14+
index: true,
15+
},
16+
label: {
17+
type: String,
18+
enum: ['', null, 'val1'],
19+
},
20+
},
21+
{
22+
collection: 'article',
23+
}
24+
);
25+
const ArticleModel = mongoose.model<any>('Article', ArticleSchema);
26+
const ArticleTC = composeMongoose(ArticleModel, { schemaComposer });
27+
28+
let lastResolverInputArg = [] as any;
29+
const enumTC = ArticleTC.getFieldTC('label') as EnumTypeComposer;
30+
schemaComposer.Query.addFields({
31+
articles: ArticleTC.mongooseResolvers.findMany(),
32+
test: {
33+
type: enumTC.List,
34+
args: { input: enumTC.List },
35+
resolve: (_, { input }) => {
36+
lastResolverInputArg = [...input];
37+
return input;
38+
},
39+
},
40+
});
41+
42+
const schema = schemaComposer.buildSchema();
43+
44+
beforeAll(async () => {
45+
await ArticleModel.base.createConnection();
46+
await ArticleModel.create([
47+
{ name: 'A1', label: null },
48+
{ name: 'A2', label: '' },
49+
{ name: 'A3', label: 'val1' },
50+
]);
51+
});
52+
afterAll(() => {
53+
ArticleModel.base.disconnect();
54+
});
55+
56+
describe('issue #384 - New feature Request: To allow null, string in Enum', () => {
57+
it('check SDL', async () => {
58+
expect(ArticleTC.toSDL({ omitDescriptions: true, deep: true, omitScalars: true }))
59+
.toMatchInlineSnapshot(`
60+
"type Article {
61+
name: String
62+
label: EnumArticleLabel
63+
_id: MongoID!
64+
}
65+
66+
enum EnumArticleLabel {
67+
EMPTY_STRING
68+
NULL
69+
val1
70+
}"
71+
`);
72+
});
73+
74+
it('check runtime output', async () => {
75+
const result = await graphql.graphql({
76+
schema,
77+
contextValue: {},
78+
source: `
79+
{
80+
articles(sort: NAME_ASC) {
81+
name
82+
label
83+
}
84+
}
85+
`,
86+
});
87+
expect(result).toEqual({
88+
data: {
89+
articles: [
90+
{ label: null, name: 'A1' }, // <-- special `null` case. It cannot be converted to NULL string
91+
{ label: 'EMPTY_STRING', name: 'A2' }, // <-- has correct ENUM key
92+
{ label: 'val1', name: 'A3' },
93+
],
94+
},
95+
});
96+
});
97+
98+
it('check runtime input', async () => {
99+
const result = await graphql.graphql({
100+
schema,
101+
contextValue: {},
102+
source: `
103+
{
104+
test(input: [val1, NULL, EMPTY_STRING])
105+
}
106+
`,
107+
});
108+
109+
// inside resolvers should be provided real values for null & string
110+
expect(lastResolverInputArg).toEqual(['val1', null, '']);
111+
112+
// in output JSON should be provided keys
113+
// BUT be aware `null` does not converted back to `NULL` string
114+
expect(result).toEqual({ data: { test: ['val1', null, 'EMPTY_STRING'] } });
115+
});
116+
});

src/fieldsConverter.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,14 @@ export function enumToGraphQL(
386386
if (desc) etc.setDescription(desc);
387387

388388
const fields = valueList.reduce((result, value) => {
389-
const key = value.replace(/[^_a-zA-Z0-9]/g, '_');
389+
let key;
390+
if (value === null) {
391+
key = 'NULL';
392+
} else if (value === '') {
393+
key = 'EMPTY_STRING';
394+
} else {
395+
key = value.replace(/[^_a-zA-Z0-9]/g, '_').replace(/(^[0-9])(.*)/g, 'a_$1$2');
396+
}
390397
result[key] = { value };
391398
return result;
392399
}, {} as Record<string, { value: any }>);

0 commit comments

Comments
 (0)