From ca046e06623e0207de376274c9ea13f3ef98ffa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9da=20Housni=20Alaoui?= Date: Tue, 14 Nov 2023 18:42:18 +0100 Subject: [PATCH 1/2] Add a 'submitFollow' method to 'Action' --- src/action.ts | 66 ++++++++++++++++++++++++++++++------ test/unit/state/hal-forms.ts | 2 +- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/action.ts b/src/action.ts index c17e75e..bb54ff1 100644 --- a/src/action.ts +++ b/src/action.ts @@ -2,6 +2,7 @@ import { State } from './state'; import * as qs from 'querystring'; import Client from './client'; import { Field } from './field'; +import Resource from './resource'; export interface ActionInfo { @@ -51,6 +52,16 @@ export interface Action = Record> ext */ submit(formData: T): Promise; + /** + * Execute the action or submit the form, then return the next resource. + * + * If a server responds with a 201 Status code and a Location header, + * it will automatically return the newly created resource. + * + * If the server responded with a 204 or 205, this function will return + * `this`. + */ + submitFollow(formData: T): Promise; } export class SimpleAction> implements Action { @@ -110,6 +121,44 @@ export class SimpleAction> implements Acti const uri = new URL(this.uri); + const newFormData = this.validateForm(formData); + + if (this.method === 'GET') { + uri.search = qs.stringify(newFormData); + const resource = this.client.go(uri.toString()); + return resource.get(); + } + const response = await this.fetchOrThrowWithBody(uri, newFormData); + const state = this.client.getStateForResponse(uri.toString(), response); + return state; + } + + async submitFollow(formData: TFormData): Promise { + const uri = new URL(this.uri); + + const newFormData = this.validateForm(formData); + + if (this.method === 'GET') { + uri.search = qs.stringify(newFormData); + return this.client.go(uri.toString()); + } + + const response = await this.fetchOrThrowWithBody(uri, newFormData); + switch (response.status) { + case 201: + if (response.headers.has('location')) { + return this.client.go(response.headers.get('location')!); + } + throw new Error('Could not follow after a 201 request, because the server did not reply with a Location header. If you sent a Location header, check if your service is returning "Access-Control-Expose-Headers: Location".'); + case 204 : + case 205 : + return this.client.go(uri.toString()); + default: + throw new Error('Did not receive a 201, 204 or 205 status code so we could not follow to the next resource'); + } + } + + private validateForm(formData: TFormData): TFormData { const newFormData: TFormData = { ...formData }; @@ -129,33 +178,28 @@ export class SimpleAction> implements Acti } } + return newFormData; + } - if (this.method === 'GET') { - uri.search = qs.stringify(newFormData); - const resource = this.client.go(uri.toString()); - return resource.get(); - } + private fetchOrThrowWithBody(uri: URL, formData: TFormData): Promise { let body; switch (this.contentType) { case 'application/x-www-form-urlencoded' : - body = qs.stringify(newFormData); + body = qs.stringify(formData); break; case 'application/json': - body = JSON.stringify(newFormData); + body = JSON.stringify(formData); break; default : throw new Error(`Serializing mimetype ${this.contentType} is not yet supported in actions`); } - const response = await this.client.fetcher.fetchOrThrow(uri.toString(), { + return this.client.fetcher.fetchOrThrow(uri.toString(), { method: this.method, body, headers: { 'Content-Type': this.contentType } }); - const state = this.client.getStateForResponse(uri.toString(), response); - return state; - } } diff --git a/test/unit/state/hal-forms.ts b/test/unit/state/hal-forms.ts index 8495950..24f7e2b 100644 --- a/test/unit/state/hal-forms.ts +++ b/test/unit/state/hal-forms.ts @@ -3,7 +3,7 @@ import { factory } from '../../../src/state/hal'; import { Action, Client, Field } from '../../../src'; import { HalFormsProperty } from 'hal-types'; -type CompareAction = Omit; +type CompareAction = Omit, 'submitFollow'>; describe('HAL forms', () => { From ca57c519647c0f912e88d51bcc122cb081832592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9da=20Housni=20Alaoui?= Date: Thu, 19 Sep 2024 08:41:49 +0200 Subject: [PATCH 2/2] Update test/unit/state/hal-forms.ts Co-authored-by: Evert Pot --- test/unit/state/hal-forms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/state/hal-forms.ts b/test/unit/state/hal-forms.ts index 24f7e2b..bd688cf 100644 --- a/test/unit/state/hal-forms.ts +++ b/test/unit/state/hal-forms.ts @@ -3,7 +3,7 @@ import { factory } from '../../../src/state/hal'; import { Action, Client, Field } from '../../../src'; import { HalFormsProperty } from 'hal-types'; -type CompareAction = Omit, 'submitFollow'>; +type CompareAction = Omit; describe('HAL forms', () => {