Skip to content

Commit 46c6d2a

Browse files
committed
Made (Generic)CollectionEndpoint.create() return undefined when server returns neither "Location" header nor entity with ID in response body
1 parent 77f3305 commit 46c6d2a

File tree

2 files changed

+34
-9
lines changed

2 files changed

+34
-9
lines changed

endpoints/generic/CollectionEndpoint.test.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ test('create', async () => {
129129
}
130130
);
131131

132-
const element = await endpoint.create(new MockEntity(0, 'test'));
133-
expect(element.response).toEqual(new MockEntity(5, 'test'));
132+
const element = (await endpoint.create(new MockEntity(0, 'test')))!;
133+
expect(element!.response).toEqual(new MockEntity(5, 'test'));
134134
expect(element.uri).toEqual(new URL('http://localhost/endpoint/5'));
135135
});
136136

@@ -149,11 +149,25 @@ test('createLocation', async () => {
149149
}
150150
);
151151

152-
const element = await endpoint.create(new MockEntity(0, 'test'));
152+
const element = (await endpoint.create(new MockEntity(0, 'test')))!;
153153
expect(element.response).toEqual(new MockEntity(5, 'test'));
154154
expect(element.uri).toEqual(new URL('http://localhost/endpoint/new'));
155155
});
156156

157+
test('createUndefined', async () => {
158+
fetchMock.mockOnceIf(
159+
req => req.method === HttpMethod.Post && req.url === 'http://localhost/endpoint',
160+
async req => {
161+
expect(req.headers.get(HttpHeader.ContentType)).toBe('application/json');
162+
expect(await req.text()).toBe('{"id":0,"name":"test"}');
163+
return { status: HttpStatusCode.Accepted };
164+
}
165+
);
166+
167+
const element = (await endpoint.create(new MockEntity(0, 'test')));
168+
expect(element).toBeUndefined();
169+
});
170+
157171
test('createAll', async () => {
158172
fetchMock.mockOnceIf(
159173
req => req.method === HttpMethod.Patch && req.url === 'http://localhost/endpoint',

endpoints/generic/GenericCollectionEndpoint.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,33 @@ export class GenericCollectionEndpoint<TEntity, TElementEndpoint extends Element
7070
* Adds a `TEntity` as a new element to the collection.
7171
* @param entity The new `TEntity`.
7272
* @param signal Used to cancel the request.
73-
* @returns The `TEntity` as returned by the server, possibly with additional fields set. undefined if the server does not respond with a result entity.
73+
* @returns An endpoint for the newly created entity; `undefined` if the server returned neither a "Location" header nor an entity with an ID in the response body.
7474
* @throws {@link BadRequestError}: {@link HttpStatusCode.BadRequest}
7575
* @throws {@link AuthenticationError}: {@link HttpStatusCode.Unauthorized}
7676
* @throws {@link AuthorizationError}: {@link HttpStatusCode.Forbidden}
7777
* @throws {@link ConflictError}: {@link HttpStatusCode.Conflict}
7878
* @throws {@link HttpError}: Other non-success status code
7979
*/
80-
async create(entity: TEntity, signal?: AbortSignal): Promise<TElementEndpoint> {
80+
async create(entity: TEntity, signal?: AbortSignal): Promise<TElementEndpoint | undefined> {
8181
const response = await this.send(HttpMethod.Post, signal, {
8282
[HttpHeader.ContentType]: this.serializer.supportedMediaTypes[0]
8383
}, this.serializer.serialize(entity));
84-
8584
const location = response.headers.get(HttpHeader.Location);
86-
const elementEndpoint = location
87-
? new this.elementEndpoint(this, this.join(location))
88-
: this.get(this.serializer.deserialize(await response.clone().text()));
85+
86+
let elementEndpoint: TElementEndpoint;
87+
if (location) {
88+
// Explicit element endpoint URL from "Location" header
89+
elementEndpoint = new this.elementEndpoint(this, this.join(location));
90+
} else {
91+
try {
92+
// Infer URL from entity ID in response body
93+
elementEndpoint = this.get(this.serializer.deserialize(await response.clone().text()));
94+
} catch {
95+
// No element endpoint
96+
return undefined;
97+
}
98+
}
99+
89100
(elementEndpoint as CachingEndpoint).responseCache = await ResponseCache.from(response);
90101
return elementEndpoint;
91102
}

0 commit comments

Comments
 (0)