Skip to content

Commit

Permalink
JNG-5598 add create update mask support (#58)
Browse files Browse the repository at this point in the history
* JNG-5598 add create update mask support

* JNG-5598 add create update mask support
  • Loading branch information
noherczeg authored Mar 8, 2024
1 parent f199bb0 commit e420177
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export interface QueryCustomizer<T> {
_orderBy?: Array<OrderingType>;
_identifier?: string;
}

export type CommandQueryCustomizer = Pick<QueryCustomizer<any>, '_mask'>;
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ export const X_JUDO_COUNT_RECORDS = 'X-Judo-CountRecords';
* Received in responses if requested via `X_JUDO_COUNT_RECORDS`.
*/
export const X_JUDO_COUNT = 'x-judo-count';

/**
* Apply mask, generally used by command type requests to explicitly define what should be in the response payload.
*/
export const X_JUDO_MASK = 'X-Judo-Mask';
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
{{> fragment.header.hbs }}

import type { JudoIdentifiable } from '../data-api/common';
import { X_JUDO_SIGNED_IDENTIFIER } from '../data-api/rest/headers';
import { CommandQueryCustomizer } from '../data-api/common';
import { X_JUDO_SIGNED_IDENTIFIER, X_JUDO_MASK } from '../data-api/rest/headers';
import { JudoAxiosService } from './JudoAxiosService';
import type { {{ joinedTokensForApiImportClassService classType }} } from '../data-api';
import type { {{ serviceClassName classType }} } from '../data-service';

const DEFAULT_COMMAND_MASK = '{}';

/**
* Class Service Implementation for {{ classDataName classType "" }}
*/
Expand Down Expand Up @@ -53,11 +56,12 @@ export class {{ serviceClassName classType }}Impl extends JudoAxiosService imple
/**
* @throws {AxiosError} With data containing {@link Array<FeedbackItem>} for status codes: 400, 401, 403.
*/
async update(target: Partial<{{ classDataName classType "Stored" }}>): Promise<JudoRestResponse<{{ classDataName classType "Stored" }}>> {
async update(target: Partial<{{ classDataName classType "Stored" }}>, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName classType "Stored" }}>> {
const path = '{{ restPath classType '/~update' '' '' }}';
return this.axios.post(this.getPathForActor(path), target, {
headers: {
[X_JUDO_SIGNED_IDENTIFIER]: target.__signedIdentifier!,
[X_JUDO_MASK]: queryCustomizer?._mask ?? DEFAULT_COMMAND_MASK,
},
});
}
Expand Down Expand Up @@ -88,11 +92,12 @@ export class {{ serviceClassName classType }}Impl extends JudoAxiosService imple
/**
* @throws {AxiosError} With data containing {@link Array<FeedbackItem>} for status codes: 400, 401, 403.
*/
async create{{ firstToUpper relation.name }}(owner: JudoIdentifiable<{{ classDataName classType "" }}>, target: JudoIdentifiable<{{ classDataName relation.target "" }}>): Promise<JudoRestResponse<{{ classDataName relation.target "Stored" }}>> {
async create{{ firstToUpper relation.name }}(owner: JudoIdentifiable<{{ classDataName classType "" }}>, target: JudoIdentifiable<{{ classDataName relation.target "" }}>, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName relation.target "Stored" }}>> {
const path = '{{ restPath classType "/~update/" relation.name "/~create" }}';
return this.axios.post(this.getPathForActor(path), target, {
headers: {
[X_JUDO_SIGNED_IDENTIFIER]: owner.__signedIdentifier!,
[X_JUDO_MASK]: queryCustomizer?._mask ?? DEFAULT_COMMAND_MASK,
},
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
{{> fragment.header.hbs }}

import type { JudoIdentifiable } from '../data-api/common';
import { X_JUDO_SIGNED_IDENTIFIER } from '../data-api/rest/headers';
import { CommandQueryCustomizer } from '../data-api/common';
import { X_JUDO_SIGNED_IDENTIFIER, X_JUDO_MASK } from '../data-api/rest/headers';
import { JudoAxiosService } from './JudoAxiosService';
import type { {{ joinedTokensForApiImport relation }} } from '../data-api';
import type { {{ serviceRelationName relation }} } from '../data-service';

const DEFAULT_COMMAND_MASK = '{}';

/**
* Relation Service Implementation for {{ classDataName (getRelationOwnerAsClassType relation) "" }}.{{ relation.name }}
*/
Expand Down Expand Up @@ -105,17 +108,20 @@ export class {{ serviceRelationName relation }}Impl extends JudoAxiosService imp
* From: relation.isCreatable
* @throws {AxiosError} With data containing {@link Array<FeedbackItem>} for status codes: 400, 401, 403.
*/
async create({{# unless relation.isAccess }}owner: JudoIdentifiable<{{ classDataName (getRelationOwnerAsClassType relation) "" }}>, {{/ unless }}target: {{ classDataName relation.target "" }}): Promise<JudoRestResponse<{{ classDataName relation.target "Stored" }}>> {
async create({{# unless relation.isAccess }}owner: JudoIdentifiable<{{ classDataName (getRelationOwnerAsClassType relation) "" }}>, {{/ unless }}target: {{ classDataName relation.target "" }}, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName relation.target "Stored" }}>> {
{{# unless relation.isAccess }}
const path = '{{ restPath (getRelationOwnerAsClassType relation) "/~update/" relation.name "/~create" }}';
{{ else }}
const path = '{{ restPath (getRelationOwnerAsClassType relation) "/" relation.name "/~create" }}';
{{/ unless }}
return this.axios.post(this.getPathForActor(path), target{{# unless relation.isAccess }}, {
return this.axios.post(this.getPathForActor(path), target, {
headers: {
{{# unless relation.isAccess }}
[X_JUDO_SIGNED_IDENTIFIER]: owner.__signedIdentifier,
{{/ unless }}
[X_JUDO_MASK]: queryCustomizer?._mask ?? DEFAULT_COMMAND_MASK,
},
} {{/ unless }});
});
}
{{/ if }}

Expand All @@ -130,11 +136,13 @@ export class {{ serviceRelationName relation }}Impl extends JudoAxiosService imp
{{ else }}
const path = '{{ restPath (getRelationOwnerAsClassType relation) "/" relation.name "/~validate" }}';
{{/ unless }}
return this.axios.post(this.getPathForActor(path), target{{# unless relation.isAccess }}, {
return this.axios.post(this.getPathForActor(path), target, {
headers: {
{{# unless relation.isAccess }}
[X_JUDO_SIGNED_IDENTIFIER]: owner.__signedIdentifier,
{{/ unless }}
},
} {{/ unless }});
});
}
{{/ if }}

Expand All @@ -158,11 +166,12 @@ export class {{ serviceRelationName relation }}Impl extends JudoAxiosService imp
* From: relation.isUpdatable
* @throws {AxiosError} With data containing {@link Array<FeedbackItem>} for status codes: 400, 401, 403.
*/
async update(target: Partial<{{ classDataName relation.target "Stored" }}>): Promise<JudoRestResponse<{{ classDataName relation.target "Stored" }}>> {
async update(target: Partial<{{ classDataName relation.target "Stored" }}>, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName relation.target "Stored" }}>> {
const path = '{{ restPath relation.target "/~update" "" "" }}';
return this.axios.post(this.getPathForActor(path), target, {
headers: {
[X_JUDO_SIGNED_IDENTIFIER]: target.__signedIdentifier,
[X_JUDO_MASK]: queryCustomizer?._mask ?? DEFAULT_COMMAND_MASK,
},
});
}
Expand Down Expand Up @@ -310,11 +319,12 @@ export class {{ serviceRelationName relation }}Impl extends JudoAxiosService imp
{{/ if }}

{{# if targetRelation.isCreatable }}
async create{{ firstToUpper targetRelation.name }}(owner: JudoIdentifiable<{{ classDataName relation.target "" }}>, target: {{ classDataName targetRelation.target "" }}): Promise<JudoRestResponse<{{ classDataName targetRelation.target "Stored" }}>> {
async create{{ firstToUpper targetRelation.name }}(owner: JudoIdentifiable<{{ classDataName relation.target "" }}>, target: {{ classDataName targetRelation.target "" }}, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName targetRelation.target "Stored" }}>> {
const path = '{{ restPath relation.target "/~update/" targetRelation.name "/~create" }}';
return this.axios.post(this.getPathForActor(path), target, {
headers: {
[X_JUDO_SIGNED_IDENTIFIER]: owner.__signedIdentifier,
[X_JUDO_MASK]: queryCustomizer?._mask ?? DEFAULT_COMMAND_MASK,
},
});
}
Expand Down Expand Up @@ -343,11 +353,12 @@ export class {{ serviceRelationName relation }}Impl extends JudoAxiosService imp
{{/ if }}

{{# if targetRelation.isUpdatable }}
async update{{ firstToUpper targetRelation.name }}(owner: JudoIdentifiable<{{ classDataName relation.target "" }}>, target: Partial<{{ classDataName targetRelation.target "Stored" }}>): Promise<JudoRestResponse<{{ classDataName targetRelation.target "Stored" }}>> {
async update{{ firstToUpper targetRelation.name }}(owner: JudoIdentifiable<{{ classDataName relation.target "" }}>, target: Partial<{{ classDataName targetRelation.target "Stored" }}>, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName targetRelation.target "Stored" }}>> {
const path = '{{ restPath relation.target "/~update/" targetRelation.name "/~update" }}';
return this.axios.post(this.getPathForActor(path), target, {
headers: {
[X_JUDO_SIGNED_IDENTIFIER]: owner.__signedIdentifier,
[X_JUDO_MASK]: queryCustomizer?._mask ?? DEFAULT_COMMAND_MASK,
},
});
}
Expand Down
44 changes: 42 additions & 2 deletions judo-ui-typescript-rest-itest/src/test/resources/tests/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { expect, beforeEach, test, suite, vi, Mock } from 'vitest';
import type { AxiosInstance } from 'axios';
import { type Resource, fetchWadlContent, extractResourceRoot } from './process-wadl';
import { JudoAxiosProvider } from '../src/services/data-axios';
import { X_JUDO_SIGNED_IDENTIFIER, X_JUDO_COUNT_RECORDS } from '../src/services/data-api/rest';
import { X_JUDO_SIGNED_IDENTIFIER, X_JUDO_COUNT_RECORDS, X_JUDO_MASK } from '../src/services/data-api/rest';
import { GodServiceForGalaxiesImpl } from '../src/services/data-axios/GodServiceForGalaxiesImpl';
import { GodServiceForEarthImpl } from '../src/services/data-axios/GodServiceForEarthImpl';
import { ViewGalaxyServiceImpl } from '../src/services/data-axios/ViewGalaxyServiceImpl';
import type { JudoIdentifiable, ViewCreature, ViewGalaxy, ViewGalaxyQueryCustomizer } from '../src/services/data-api';
import type { JudoIdentifiable, ViewCreature, ViewGalaxy, ViewGalaxyQueryCustomizer, ViewGalaxyStored } from '../src/services/data-api';

const ORIGIN = 'http://tatami-tests.judo.technology';
const API_DEFAULT_BASE_URL = ORIGIN;
Expand Down Expand Up @@ -119,6 +119,46 @@ suite('API Tests', () => {
});
});

test('Default create Access POST request is mapped properly', async () => {
const resource: Resource = mappedXml.find(r => r.path === 'God/galaxies/~create');
const godServiceForGalaxiesImpl = new GodServiceForGalaxiesImpl(judoAxiosProvider);
const target: ViewGalaxy = {
name: 'abc',
magnitude: 11,
constellation: 'test',
};

assertSinglePostResource(resource);

await godServiceForGalaxiesImpl.create(target);

assertSinglePostCall(resource, target, {
'headers': {
[X_JUDO_MASK]: '{}',
},
});
});

test('Modified update Access POST request is mapped properly', async () => {
const resource: Resource = mappedXml.find(r => r.path === 'View/Galaxy/~update');
const viewGalaxyServiceImpl = new ViewGalaxyServiceImpl(judoAxiosProvider);
const target: Partial<ViewGalaxyStored> = {
__signedIdentifier: 'abc',
magnitude: 222,
};

assertSinglePostResource(resource);

await viewGalaxyServiceImpl.update(target, { _mask: '{magnitude}' });

assertSinglePostCall(resource, target, {
'headers': {
[X_JUDO_SIGNED_IDENTIFIER]: target.__signedIdentifier,
[X_JUDO_MASK]: '{magnitude}',
},
});
});

function assertSinglePostResource(resource: Resource): void {
expect(resource).toBeDefined();
expect(resource.methods.length).toBe(1);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{> fragment.header.hbs }}

import type { JudoIdentifiable } from '../data-api/common';
import type { JudoIdentifiable, CommandQueryCustomizer } from '../data-api/common';
import { {{ joinedTokensForApiImportClassService classType }} } from '../data-api';

/**
Expand All @@ -17,20 +17,20 @@ export interface {{ serviceClassName classType }} {
delete(target: JudoIdentifiable<{{ classDataName classType "" }}>): Promise<JudoRestResponse<void>>;
{{/ if }}
{{# if classType.isUpdatable }}
update(target: Partial<{{ classDataName classType "Stored" }}>): Promise<JudoRestResponse<{{ classDataName classType "Stored" }}>>;
update(target: Partial<{{ classDataName classType "Stored" }}>, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName classType "Stored" }}>>;
{{/ if }}
{{# if classType.isUpdateValidatable }}
validateUpdate(target: Partial<{{ classDataName classType "Stored" }}>): Promise<JudoRestResponse<{{ classDataName classType "Stored" }}>>;
validateUpdate(target: Partial<{{ classDataName classType "Stored" }}>, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName classType "Stored" }}>>;
{{/ if }}
{{# each classType.relationsOrderedByName as | relation | }}
{{# if relation.target.isTemplateable }}
getTemplateFor{{ firstToUpper relation.name }}(): Promise<JudoRestResponse<{{ classDataName relation.target "" }}>>;
{{/ if }}
{{# if relation.isCreatable }}
create{{ firstToUpper relation.name }}(owner: JudoIdentifiable<{{ classDataName classType "" }}>, target: JudoIdentifiable<{{ classDataName relation.target "" }}>): Promise<JudoRestResponse<{{ classDataName relation.target "Stored" }}>>;
create{{ firstToUpper relation.name }}(owner: JudoIdentifiable<{{ classDataName classType "" }}>, target: JudoIdentifiable<{{ classDataName relation.target "" }}>, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName relation.target "Stored" }}>>;
{{/ if }}
{{# if relation.isCreateValidatable }}
validateCreate{{ firstToUpper relation.name }}(owner: JudoIdentifiable<{{ classDataName classType "" }}>, target: {{ classDataName relation.target "" }}): Promise<JudoRestResponse<{{ classDataName relation.target "" }}>>;
validateCreate{{ firstToUpper relation.name }}(owner: JudoIdentifiable<{{ classDataName classType "" }}>, target: {{ classDataName relation.target "" }}, queryCustomizer?: CommandQueryCustomizer): Promise<JudoRestResponse<{{ classDataName relation.target "" }}>>;
{{/ if }}
{{# if relation.isRefreshable }}
{{# if relation.isCollection }}list{{ else }}get{{/ if }}{{ firstToUpper relation.name }}(target: JudoIdentifiable<{{ classDataName classType "" }}>, queryCustomizer?: {{ classDataName relation.target "QueryCustomizer" }}, headers?: Record<string, string>): Promise<JudoRestResponse<{{# if relation.isCollection }}Array<{{/ if }}{{ classDataName relation.target "Stored" }}{{# if relation.isCollection }}>{{/ if }}>>
Expand Down
Loading

0 comments on commit e420177

Please sign in to comment.