Skip to content

Commit 3828c20

Browse files
IvanGoncharovmjmahone
authored andcommitted
Scalar coercion cleanup (graphql#1414)
* Cleanup + restrict Boolean serialize coercion to numbers * Make coercion code more explicit. * Address review comments * fixing const num that's not const
1 parent f373fed commit 3828c20

File tree

3 files changed

+80
-56
lines changed

3 files changed

+80
-56
lines changed

src/execution/__tests__/schema-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe('Execute: Handles execution with a complex schema', () => {
9191
function article(id) {
9292
return {
9393
id,
94-
isPublished: 'true',
94+
isPublished: true,
9595
author: johnSmith,
9696
title: 'My Article ' + id,
9797
body: 'This is a post',

src/type/__tests__/serialization-test.js

+31-17
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe('Type System: Scalar coercion', () => {
6969
'Int cannot represent non-integer value: Infinity',
7070
);
7171
expect(() => GraphQLInt.serialize([5])).to.throw(
72-
'Int cannot represent an array value: [5]',
72+
'Int cannot represent non-integer value: [5]',
7373
);
7474
});
7575

@@ -98,7 +98,7 @@ describe('Type System: Scalar coercion', () => {
9898
'Float cannot represent non numeric value: ""',
9999
);
100100
expect(() => GraphQLFloat.serialize([5])).to.throw(
101-
'Float cannot represent an array value: [5]',
101+
'Float cannot represent non numeric value: [5]',
102102
);
103103
});
104104

@@ -109,15 +109,6 @@ describe('Type System: Scalar coercion', () => {
109109
expect(GraphQLString.serialize(true)).to.equal('true');
110110
expect(GraphQLString.serialize(false)).to.equal('false');
111111

112-
expect(() => GraphQLString.serialize([1])).to.throw(
113-
'String cannot represent value: [1]',
114-
);
115-
116-
const badObjValue = {};
117-
expect(() => GraphQLString.serialize(badObjValue)).to.throw(
118-
'String cannot represent value: {}',
119-
);
120-
121112
const stringableObjValue = {
122113
valueOf() {
123114
return 'something useful';
@@ -126,19 +117,41 @@ describe('Type System: Scalar coercion', () => {
126117
expect(GraphQLString.serialize(stringableObjValue)).to.equal(
127118
'something useful',
128119
);
120+
121+
expect(() => GraphQLString.serialize(NaN)).to.throw(
122+
'String cannot represent value: NaN',
123+
);
124+
125+
expect(() => GraphQLString.serialize([1])).to.throw(
126+
'String cannot represent value: [1]',
127+
);
128+
129+
const badObjValue = {};
130+
expect(() => GraphQLString.serialize(badObjValue)).to.throw(
131+
'String cannot represent value: {}',
132+
);
129133
});
130134

131135
it('serializes output as Boolean', () => {
132-
expect(GraphQLBoolean.serialize('string')).to.equal(true);
133-
expect(GraphQLBoolean.serialize('false')).to.equal(true);
134-
expect(GraphQLBoolean.serialize('')).to.equal(false);
135136
expect(GraphQLBoolean.serialize(1)).to.equal(true);
136137
expect(GraphQLBoolean.serialize(0)).to.equal(false);
137138
expect(GraphQLBoolean.serialize(true)).to.equal(true);
138139
expect(GraphQLBoolean.serialize(false)).to.equal(false);
139140

141+
expect(() => GraphQLBoolean.serialize(NaN)).to.throw(
142+
'Boolean cannot represent a non boolean value: NaN',
143+
);
144+
expect(() => GraphQLBoolean.serialize('')).to.throw(
145+
'Boolean cannot represent a non boolean value: ""',
146+
);
147+
expect(() => GraphQLBoolean.serialize('true')).to.throw(
148+
'Boolean cannot represent a non boolean value: "true"',
149+
);
140150
expect(() => GraphQLBoolean.serialize([false])).to.throw(
141-
'Boolean cannot represent an array value: [false]',
151+
'Boolean cannot represent a non boolean value: [false]',
152+
);
153+
expect(() => GraphQLBoolean.serialize({})).to.throw(
154+
'Boolean cannot represent a non boolean value: {}',
142155
);
143156
});
144157

@@ -148,6 +161,7 @@ describe('Type System: Scalar coercion', () => {
148161
expect(GraphQLID.serialize('')).to.equal('');
149162
expect(GraphQLID.serialize(123)).to.equal('123');
150163
expect(GraphQLID.serialize(0)).to.equal('0');
164+
expect(GraphQLID.serialize(-1)).to.equal('-1');
151165

152166
const objValue = {
153167
_id: 123,
@@ -171,8 +185,8 @@ describe('Type System: Scalar coercion', () => {
171185
'ID cannot represent value: true',
172186
);
173187

174-
expect(() => GraphQLID.serialize(-1.1)).to.throw(
175-
'ID cannot represent value: -1.1',
188+
expect(() => GraphQLID.serialize(3.14)).to.throw(
189+
'ID cannot represent value: 3.14',
176190
);
177191

178192
expect(() => GraphQLID.serialize({})).to.throw(

src/type/scalars.js

+48-38
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ const MAX_INT = 2147483647;
2222
const MIN_INT = -2147483648;
2323

2424
function serializeInt(value: mixed): number {
25-
if (Array.isArray(value)) {
26-
throw new TypeError(
27-
`Int cannot represent an array value: ${inspect(value)}`,
28-
);
25+
if (typeof value === 'boolean') {
26+
return value ? 1 : 0;
2927
}
30-
const num = Number(value);
31-
if (value === '' || !isInteger(num)) {
28+
29+
let num = value;
30+
if (typeof value === 'string' && value !== '') {
31+
num = Number(value);
32+
}
33+
34+
if (!isInteger(num)) {
3235
throw new TypeError(
3336
`Int cannot represent non-integer value: ${inspect(value)}`,
3437
);
@@ -74,13 +77,15 @@ export const GraphQLInt = new GraphQLScalarType({
7477
});
7578

7679
function serializeFloat(value: mixed): number {
77-
if (Array.isArray(value)) {
78-
throw new TypeError(
79-
`Float cannot represent an array value: ${inspect(value)}`,
80-
);
80+
if (typeof value === 'boolean') {
81+
return value ? 1 : 0;
82+
}
83+
84+
let num = value;
85+
if (typeof value === 'string' && value !== '') {
86+
num = Number(value);
8187
}
82-
const num = Number(value);
83-
if (value === '' || !isFinite(num)) {
88+
if (!isFinite(num)) {
8489
throw new TypeError(
8590
`Float cannot represent non numeric value: ${inspect(value)}`,
8691
);
@@ -118,16 +123,18 @@ function serializeString(value: mixed): string {
118123
// (ex: MongoDB id objects).
119124
const result =
120125
value && typeof value.valueOf === 'function' ? value.valueOf() : value;
121-
// Serialize string, number, and boolean values to a string, but do not
126+
// Serialize string, boolean and number values to a string, but do not
122127
// attempt to coerce object, function, symbol, or other types as strings.
123-
if (
124-
typeof result !== 'string' &&
125-
typeof result !== 'number' &&
126-
typeof result !== 'boolean'
127-
) {
128-
throw new TypeError(`String cannot represent value: ${inspect(result)}`);
129-
}
130-
return String(result);
128+
if (typeof result === 'string') {
129+
return result;
130+
}
131+
if (typeof result === 'boolean') {
132+
return result ? 'true' : 'false';
133+
}
134+
if (isFinite(result)) {
135+
return result.toString();
136+
}
137+
throw new TypeError(`String cannot represent value: ${inspect(value)}`);
131138
}
132139

133140
function coerceString(value: mixed): string {
@@ -153,12 +160,15 @@ export const GraphQLString = new GraphQLScalarType({
153160
});
154161

155162
function serializeBoolean(value: mixed): boolean {
156-
if (Array.isArray(value)) {
157-
throw new TypeError(
158-
`Boolean cannot represent an array value: ${inspect(value)}`,
159-
);
163+
if (typeof value === 'boolean') {
164+
return value;
160165
}
161-
return Boolean(value);
166+
if (isFinite(value)) {
167+
return value !== 0;
168+
}
169+
throw new TypeError(
170+
`Boolean cannot represent a non boolean value: ${inspect(value)}`,
171+
);
162172
}
163173

164174
function coerceBoolean(value: mixed): boolean {
@@ -185,23 +195,23 @@ function serializeID(value: mixed): string {
185195
// to represent an object identifier (ex. MongoDB).
186196
const result =
187197
value && typeof value.valueOf === 'function' ? value.valueOf() : value;
188-
if (
189-
typeof result !== 'string' &&
190-
(typeof result !== 'number' || !isInteger(result))
191-
) {
192-
throw new TypeError(`ID cannot represent value: ${inspect(value)}`);
198+
if (typeof result === 'string') {
199+
return result;
193200
}
194-
return String(result);
201+
if (isInteger(result)) {
202+
return String(result);
203+
}
204+
throw new TypeError(`ID cannot represent value: ${inspect(value)}`);
195205
}
196206

197207
function coerceID(value: mixed): string {
198-
if (
199-
typeof value !== 'string' &&
200-
(typeof value !== 'number' || !isInteger(value))
201-
) {
202-
throw new TypeError(`ID cannot represent value: ${inspect(value)}`);
208+
if (typeof value === 'string') {
209+
return value;
210+
}
211+
if (isInteger(value)) {
212+
return value.toString();
203213
}
204-
return String(value);
214+
throw new TypeError(`ID cannot represent value: ${inspect(value)}`);
205215
}
206216

207217
export const GraphQLID = new GraphQLScalarType({

0 commit comments

Comments
 (0)