Skip to content

Commit ad6e993

Browse files
committed
chore: review comments
1 parent 1569308 commit ad6e993

3 files changed

Lines changed: 32 additions & 3 deletions

File tree

api/spec/packages/aip-client-javascript/src/lib/wire.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,16 @@ function walk(
147147
// Only the value is walked, and only when it is model-shaped. A null
148148
// prototype avoids the `__proto__` key silently reassigning `out`'s own
149149
// prototype instead of becoming a visible entry (user data may contain
150-
// any key, including reserved object-literal property names).
150+
// any key, including reserved object-literal property names). The
151+
// prototype is restored once every key is a plain own property, so the
152+
// returned object still behaves normally for consumers (instanceof,
153+
// template literals) — `Object.prototype` itself was never touched.
151154
const valueSchema = hasRenamableShape(d.valueType) ? d.valueType : undefined
152155
const out: Record<string, unknown> = Object.create(null)
153156
for (const [key, value] of Object.entries(record)) {
154157
out[key] = valueSchema ? walk(value, valueSchema, dir, depth + 1) : value
155158
}
159+
Object.setPrototypeOf(out, Object.prototype)
156160
return out
157161
}
158162

@@ -182,6 +186,7 @@ function walk(
182186
}
183187
out[dir.rename(key)] = walk(value, fieldSchema, dir, depth + 1)
184188
}
189+
Object.setPrototypeOf(out, Object.prototype)
185190
return out
186191
}
187192

api/spec/packages/aip-client-javascript/tests/wire.spec.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,29 @@ describe('wire mapper (toWire/fromWire over real schemas)', () => {
4343
expect(Object.getPrototypeOf({})).toBe(Object.prototype)
4444
})
4545

46+
it('restores a normal prototype on record and object outputs for consumers', () => {
47+
// The null prototype used during construction (to block __proto__/constructor
48+
// pollution) is restored before returning, so the result behaves like a normal
49+
// object for consumers — instanceof checks, template literals, etc. — rather
50+
// than staying a `[Object: null prototype]` forever.
51+
const wire = { features: { my_user_feature: { has_access: true } } }
52+
const camel = fromWire(wire, schemas.governanceQueryResult) as any
53+
expect(Object.getPrototypeOf(camel)).toBe(Object.prototype)
54+
expect(Object.getPrototypeOf(camel.features)).toBe(Object.prototype)
55+
expect(Object.getPrototypeOf(camel.features.my_user_feature)).toBe(
56+
Object.prototype,
57+
)
58+
expect(camel instanceof Object).toBe(true)
59+
expect(() => `${camel}`).not.toThrow()
60+
})
61+
4662
it('treats a data key of "constructor" as an unknown field, not a shape hit', () => {
4763
const wire = JSON.parse('{"id":"x","constructor":"evil"}')
4864
const camel = fromWire(wire, schemas.taxCode) as any
49-
expect(camel.constructor).toBeUndefined()
65+
// The wire value "evil" never becomes an own `constructor` property — since
66+
// the prototype is restored, `camel.constructor` resolves to the ordinary
67+
// inherited `Object` constructor, exactly as it would on any plain object.
68+
expect(camel.constructor).toBe(Object)
5069
expect(Object.keys(camel)).not.toContain('constructor')
5170
})
5271

api/spec/packages/typespec-typescript/src/runtime/wire.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,16 @@ function walk(
147147
// Only the value is walked, and only when it is model-shaped. A null
148148
// prototype avoids the `__proto__` key silently reassigning `out`'s own
149149
// prototype instead of becoming a visible entry (user data may contain
150-
// any key, including reserved object-literal property names).
150+
// any key, including reserved object-literal property names). The
151+
// prototype is restored once every key is a plain own property, so the
152+
// returned object still behaves normally for consumers (instanceof,
153+
// template literals) — `Object.prototype` itself was never touched.
151154
const valueSchema = hasRenamableShape(d.valueType) ? d.valueType : undefined
152155
const out: Record<string, unknown> = Object.create(null)
153156
for (const [key, value] of Object.entries(record)) {
154157
out[key] = valueSchema ? walk(value, valueSchema, dir, depth + 1) : value
155158
}
159+
Object.setPrototypeOf(out, Object.prototype)
156160
return out
157161
}
158162

@@ -182,6 +186,7 @@ function walk(
182186
}
183187
out[dir.rename(key)] = walk(value, fieldSchema, dir, depth + 1)
184188
}
189+
Object.setPrototypeOf(out, Object.prototype)
185190
return out
186191
}
187192

0 commit comments

Comments
 (0)