Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
fcd6e73
Stash: CreateDataset use case WIP. Pending validation and data access…
GPortas Jan 10, 2024
3a24289
Changed: NewDataset model properties
GPortas Jan 10, 2024
644f228
Stash: NewDatasetValidator logic WIP
GPortas Jan 12, 2024
2c35633
Added: test cases for NewDatasetValidator empty field error handling
GPortas Jan 12, 2024
1b4b29a
Stash: multiple field value validation WIP
GPortas Jan 12, 2024
32ce475
Refactor: NewDatasetValidator unit test
GPortas Jan 12, 2024
3306896
Added: test case to NewDatasetValidator and refactor
GPortas Jan 15, 2024
fa3a6c3
Added: NewDatasetValidator test case for field type error
GPortas Jan 15, 2024
7765114
Added: not multiple field validation logic to NewDatasetValidator
GPortas Jan 15, 2024
3deaac7
Added: child field validation logic and refactor to NewDatasetValidator
GPortas Jan 16, 2024
822f384
Added: empty required array field error handling and refactor to NewD…
GPortas Jan 16, 2024
19c6400
Refactor: validation methods extracted in NewDatasetValidator
GPortas Jan 16, 2024
6d5028d
Refactor: validateMetadataBlock extracted in NewDatasetValidator
GPortas Jan 16, 2024
705cd2c
Added: date format validation to NewDatasetValidator and associated u…
GPortas Jan 16, 2024
335fcec
Added: test cases for controlled vocabulary and date fields
GPortas Jan 16, 2024
b577bc8
Refactor: dataset validator resource validation errors localtion
GPortas Jan 16, 2024
9fda8b5
Added: createDataset use case export
GPortas Jan 16, 2024
b36c23c
Refactor: using NewDatasetMetadataFieldAndValueInfo to group multiple…
GPortas Jan 17, 2024
c9bdf84
Added: typeClass property to MetadataFieldInfo
GPortas Jan 17, 2024
76775e4
Added: newDatasetTransformers WIP and calculating metadatablocks befo…
GPortas Jan 18, 2024
1a1d08a
Fixed: test name
GPortas Jan 18, 2024
46222d9
Fixed: newDatasetTransformers
GPortas Jan 18, 2024
5296864
Stash: newDatasetTransformers fixes and tests WIP
GPortas Jan 18, 2024
5ad4e25
Added: newDatasetTransformers tweaks and fixes and passing unit test
GPortas Jan 19, 2024
8f09830
Stash: createDataset repository logic WIP
GPortas Jan 19, 2024
e4670cf
Added: completed createDataset repository logic with passing tests
GPortas Jan 19, 2024
0a4464e
Stash: createDataset IT WIP (failing)
GPortas Jan 19, 2024
97c47fd
Changed: returning ids when the dataset is created
GPortas Jan 19, 2024
d9aa27a
Added: missing models exported in index.ts
GPortas Jan 19, 2024
db836d8
Fixed: missing import in IDatasetsRepository
GPortas Jan 19, 2024
b86911f
Refactor: DTO convention for new dataset creation
GPortas Jan 22, 2024
53bc05f
Fixed: CreateDataset dependency calls
GPortas Jan 22, 2024
bf8fc08
Refactor: metadata field validators
GPortas Jan 22, 2024
2860270
Changed: protected methods now private
GPortas Jan 22, 2024
ce8e391
Changed: docker env vars to use docker.io and unstable
GPortas Jan 22, 2024
938ab8f
Refactor: replaced inheritance with composition in metadata field va…
GPortas Jan 23, 2024
3c5f190
Changed: NewDatasetValidator renamed to NewDatasetResourceValidator
GPortas Jan 23, 2024
32f4682
Merge branch 'develop' of github.com:IQSS/dataverse-client-javascript…
GPortas Feb 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/core/domain/useCases/validators/NewResourceValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ResourceValidationError } from './errors/ResourceValidationError';

export interface NewResourceValidator {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
validate(...args: any[]): Promise<void | ResourceValidationError>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class ResourceValidationError extends Error {
constructor(message: string) {
super(message);
}
}
21 changes: 21 additions & 0 deletions src/datasets/domain/dtos/NewDatasetDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { DatasetLicense } from '../models/Dataset';

export interface NewDatasetDTO {
license?: DatasetLicense;
metadataBlockValues: NewDatasetMetadataBlockValuesDTO[];
}

export interface NewDatasetMetadataBlockValuesDTO {
name: string;
fields: NewDatasetMetadataFieldsDTO;
}

export type NewDatasetMetadataFieldsDTO = Record<string, NewDatasetMetadataFieldValueDTO>;

export type NewDatasetMetadataFieldValueDTO =
| string
| string[]
| NewDatasetMetadataChildFieldValueDTO
| NewDatasetMetadataChildFieldValueDTO[];

export type NewDatasetMetadataChildFieldValueDTO = Record<string, string>;
4 changes: 4 additions & 0 deletions src/datasets/domain/models/CreatedDatasetIdentifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface CreatedDatasetIdentifiers {
persistentId: string;
numericId: number;
}
8 changes: 8 additions & 0 deletions src/datasets/domain/repositories/IDatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Dataset } from '../models/Dataset';
import { DatasetUserPermissions } from '../models/DatasetUserPermissions';
import { DatasetLock } from '../models/DatasetLock';
import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset';
import { NewDatasetDTO } from '../dtos/NewDatasetDTO';
import { MetadataBlock } from '../../../metadataBlocks';
import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers';

export interface IDatasetsRepository {
getDatasetSummaryFieldNames(): Promise<string[]>;
Expand All @@ -12,4 +15,9 @@ export interface IDatasetsRepository {
getDatasetUserPermissions(datasetId: number | string): Promise<DatasetUserPermissions>;
getDatasetLocks(datasetId: number | string): Promise<DatasetLock[]>;
getAllDatasetPreviews(limit?: number, offset?: number): Promise<DatasetPreviewSubset>;
createDataset(
newDataset: NewDatasetDTO,
datasetMetadataBlocks: MetadataBlock[],
collectionId: string,
): Promise<CreatedDatasetIdentifiers>;
}
39 changes: 39 additions & 0 deletions src/datasets/domain/useCases/CreateDataset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { UseCase } from '../../../core/domain/useCases/UseCase';
import { IDatasetsRepository } from '../repositories/IDatasetsRepository';
import { NewDatasetDTO, NewDatasetMetadataBlockValuesDTO } from '../dtos/NewDatasetDTO';
import { NewResourceValidator } from '../../../core/domain/useCases/validators/NewResourceValidator';
import { IMetadataBlocksRepository } from '../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository';
import { MetadataBlock } from '../../../metadataBlocks';
import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers';

export class CreateDataset implements UseCase<CreatedDatasetIdentifiers> {
private datasetsRepository: IDatasetsRepository;
private metadataBlocksRepository: IMetadataBlocksRepository;
private newDatasetValidator: NewResourceValidator;

constructor(
datasetsRepository: IDatasetsRepository,
metadataBlocksRepository: IMetadataBlocksRepository,
newDatasetValidator: NewResourceValidator,
) {
this.datasetsRepository = datasetsRepository;
this.metadataBlocksRepository = metadataBlocksRepository;
this.newDatasetValidator = newDatasetValidator;
}

async execute(newDataset: NewDatasetDTO, collectionId: string = 'root'): Promise<CreatedDatasetIdentifiers> {
const metadataBlocks = await this.getNewDatasetMetadataBlocks(newDataset);
this.newDatasetValidator.validate(newDataset, metadataBlocks);
return this.datasetsRepository.createDataset(newDataset, metadataBlocks, collectionId);
}

async getNewDatasetMetadataBlocks(newDataset: NewDatasetDTO): Promise<MetadataBlock[]> {
let metadataBlocks: MetadataBlock[] = [];
await Promise.all(
newDataset.metadataBlockValues.map(async (metadataBlockValue: NewDatasetMetadataBlockValuesDTO) => {
metadataBlocks.push(await this.metadataBlocksRepository.getMetadataBlockByName(metadataBlockValue.name));
}),
);
return metadataBlocks;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO';
import { FieldValidationError } from './errors/FieldValidationError';
import { MetadataFieldInfo } from '../../../../metadataBlocks';

export interface NewDatasetMetadataFieldAndValueInfo {
metadataFieldInfo: MetadataFieldInfo;
metadataFieldKey: string;
metadataFieldValue: NewDatasetMetadataFieldValueDTO;
metadataBlockName: string;
metadataParentFieldKey?: string;
metadataFieldPosition?: number;
}

export abstract class BaseMetadataFieldValidator {
abstract validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void;

protected createGeneralValidationError(
newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo,
reason: string,
): FieldValidationError {
return new FieldValidationError(
newDatasetMetadataFieldAndValueInfo.metadataFieldKey,
newDatasetMetadataFieldAndValueInfo.metadataBlockName,
newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey,
newDatasetMetadataFieldAndValueInfo.metadataFieldPosition,
reason,
);
}
}
51 changes: 51 additions & 0 deletions src/datasets/domain/useCases/validators/MetadataFieldValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { BaseMetadataFieldValidator, NewDatasetMetadataFieldAndValueInfo } from './BaseMetadataFieldValidator';
import { MultipleMetadataFieldValidator } from './MultipleMetadataFieldValidator';
import { SingleMetadataFieldValidator } from './SingleMetadataFieldValidator';
import { EmptyFieldError } from './errors/EmptyFieldError';
import { NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO';

export class MetadataFieldValidator extends BaseMetadataFieldValidator {
constructor(
private singleMetadataFieldValidator: SingleMetadataFieldValidator,
private multipleMetadataFieldValidator: MultipleMetadataFieldValidator,
) {
super();
}

validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void {
const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue;
const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo;
if (
metadataFieldValue == undefined ||
metadataFieldValue == null ||
this.isEmptyString(metadataFieldValue) ||
this.isEmptyArray(metadataFieldValue)
) {
if (metadataFieldInfo.isRequired) {
throw new EmptyFieldError(
newDatasetMetadataFieldAndValueInfo.metadataFieldKey,
newDatasetMetadataFieldAndValueInfo.metadataBlockName,
newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey,
newDatasetMetadataFieldAndValueInfo.metadataFieldPosition,
);
} else {
return;
}
}
if (newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.multiple) {
this.multipleMetadataFieldValidator.validate(newDatasetMetadataFieldAndValueInfo);
} else {
this.singleMetadataFieldValidator.validate(newDatasetMetadataFieldAndValueInfo);
}
}

private isEmptyString(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean {
return typeof metadataFieldValue == 'string' && metadataFieldValue.trim() === '';
}

private isEmptyArray(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean {
return (
Array.isArray(metadataFieldValue) && (metadataFieldValue as Array<NewDatasetMetadataFieldValueDTO>).length == 0
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { BaseMetadataFieldValidator, NewDatasetMetadataFieldAndValueInfo } from './BaseMetadataFieldValidator';
import { NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO';
import { SingleMetadataFieldValidator } from './SingleMetadataFieldValidator';

export class MultipleMetadataFieldValidator extends BaseMetadataFieldValidator {
constructor(private singleMetadataFieldValidator: SingleMetadataFieldValidator) {
super();
}

validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void {
const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue;
const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo;
if (!Array.isArray(metadataFieldValue)) {
throw this.createGeneralValidationError(newDatasetMetadataFieldAndValueInfo, 'Expecting an array of values.');
}
if (this.isValidArrayType(metadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') {
throw this.createGeneralValidationError(
newDatasetMetadataFieldAndValueInfo,
'Expecting an array of child fields, not strings.',
);
} else if (this.isValidArrayType(metadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') {
throw this.createGeneralValidationError(
newDatasetMetadataFieldAndValueInfo,
'Expecting an array of strings, not child fields.',
);
} else if (
!this.isValidArrayType(metadataFieldValue, 'object') &&
!this.isValidArrayType(metadataFieldValue, 'string')
) {
throw this.createGeneralValidationError(
newDatasetMetadataFieldAndValueInfo,
'The provided array of values is not valid.',
);
}

const fieldValues = metadataFieldValue as NewDatasetMetadataFieldValueDTO[];
fieldValues.forEach((value, metadataFieldPosition) => {
this.singleMetadataFieldValidator.validate({
metadataFieldInfo: metadataFieldInfo,
metadataFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey,
metadataFieldValue: value,
metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName,
metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey,
metadataFieldPosition: metadataFieldPosition,
});
});
}

private isValidArrayType(
metadataFieldValue: Array<string | NewDatasetMetadataFieldValueDTO>,
expectedType: 'string' | 'object',
): boolean {
return metadataFieldValue.every((item: string | NewDatasetMetadataFieldValueDTO) => typeof item === expectedType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NewDatasetDTO, NewDatasetMetadataBlockValuesDTO } from '../../dtos/NewDatasetDTO';
import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator';
import { MetadataBlock } from '../../../../metadataBlocks';
import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError';
import { BaseMetadataFieldValidator } from './BaseMetadataFieldValidator';

export class NewDatasetResourceValidator implements NewResourceValidator {
constructor(private metadataFieldValidator: BaseMetadataFieldValidator) {}

async validate(resource: NewDatasetDTO, metadataBlocks: MetadataBlock[]): Promise<void | ResourceValidationError> {
for (const metadataBlockValues of resource.metadataBlockValues) {
await this.validateMetadataBlock(metadataBlockValues, metadataBlocks);
}
}

private async validateMetadataBlock(
metadataBlockValues: NewDatasetMetadataBlockValuesDTO,
metadataBlocks: MetadataBlock[],
) {
const metadataBlockName = metadataBlockValues.name;
const metadataBlock: MetadataBlock = metadataBlocks.find(
(metadataBlock) => metadataBlock.name === metadataBlockName,
);
for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) {
this.metadataFieldValidator.validate({
metadataFieldInfo: metadataBlock.metadataFields[metadataFieldKey],
metadataFieldKey: metadataFieldKey,
metadataFieldValue: metadataBlockValues.fields[metadataFieldKey],
metadataBlockName: metadataBlockName,
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { BaseMetadataFieldValidator, NewDatasetMetadataFieldAndValueInfo } from './BaseMetadataFieldValidator';
import { ControlledVocabularyFieldError } from './errors/ControlledVocabularyFieldError';
import { DateFormatFieldError } from './errors/DateFormatFieldError';
import { MetadataFieldValidator } from './MetadataFieldValidator';
import { NewDatasetMetadataChildFieldValueDTO } from '../../dtos/NewDatasetDTO';
import { MultipleMetadataFieldValidator } from './MultipleMetadataFieldValidator';

export class SingleMetadataFieldValidator extends BaseMetadataFieldValidator {
validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void {
const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue;
const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo;
if (Array.isArray(metadataFieldValue)) {
throw this.createGeneralValidationError(
newDatasetMetadataFieldAndValueInfo,
'Expecting a single field, not an array.',
);
}
if (typeof metadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') {
throw this.createGeneralValidationError(
newDatasetMetadataFieldAndValueInfo,
'Expecting a string, not child fields.',
);
}
if (typeof metadataFieldValue === 'string' && metadataFieldInfo.type === 'NONE') {
throw this.createGeneralValidationError(
newDatasetMetadataFieldAndValueInfo,
'Expecting child fields, not a string.',
);
}
this.validateFieldValue(newDatasetMetadataFieldAndValueInfo);
}

private validateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) {
const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo;
if (metadataFieldInfo.isControlledVocabulary) {
this.validateControlledVocabularyFieldValue(newDatasetMetadataFieldAndValueInfo);
}

if (metadataFieldInfo.type == 'DATE') {
this.validateDateFieldValue(newDatasetMetadataFieldAndValueInfo);
}

if (metadataFieldInfo.childMetadataFields != undefined) {
this.validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo);
}
}

private validateControlledVocabularyFieldValue(
newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo,
) {
if (
!newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.controlledVocabularyValues.includes(
newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string,
)
) {
throw new ControlledVocabularyFieldError(
newDatasetMetadataFieldAndValueInfo.metadataFieldKey,
newDatasetMetadataFieldAndValueInfo.metadataBlockName,
newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey,
newDatasetMetadataFieldAndValueInfo.metadataFieldPosition,
);
}
}

private validateDateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) {
const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateFormatRegex.test(newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string)) {
throw new DateFormatFieldError(
newDatasetMetadataFieldAndValueInfo.metadataFieldKey,
newDatasetMetadataFieldAndValueInfo.metadataBlockName,
newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey,
newDatasetMetadataFieldAndValueInfo.metadataFieldPosition,
);
}
}

private validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) {
const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo;
const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields);
const metadataFieldValidator = new MetadataFieldValidator(this, new MultipleMetadataFieldValidator(this));
for (const childMetadataFieldKey of childMetadataFieldKeys) {
const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey];
metadataFieldValidator.validate({
metadataFieldInfo: childMetadataFieldInfo,
metadataFieldKey: childMetadataFieldKey,
metadataFieldValue: (
newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValueDTO
)[childMetadataFieldKey],
metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName,
metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey,
metadataFieldPosition: newDatasetMetadataFieldAndValueInfo.metadataFieldPosition,
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FieldValidationError } from './FieldValidationError';

export class ControlledVocabularyFieldError extends FieldValidationError {
constructor(
metadataFieldName: string,
citationBlockName: string,
parentMetadataFieldName?: string,
fieldPosition?: number,
) {
super(
metadataFieldName,
citationBlockName,
parentMetadataFieldName,
fieldPosition,
'The field does not have a valid controlled vocabulary value.',
);
}
}
Loading