Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IOPLT-321] adds io-ts model for MultiplyNumber, renameField and renameFields functions #14

Merged
merged 1 commit into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 27 additions & 1 deletion src/types/__tests__/number.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
import * as E from "fp-ts/lib/Either";
import * as E from "fp-ts/Either";
import { NumberMapping } from "../number";

describe("NumberMapping", () => {
describe("MULTIPLY_NUMBER", () => {
it('should validate when multiplier is a number and mapper is "MULTIPLY_NUMBER"', () => {
const validData = {
multiplier: 5,
mapper: "MULTIPLY_NUMBER"
};
const result = NumberMapping.decode(validData);
expect(E.isRight(result)).toBeTruthy();
});
it("should not validate when multiplier is not a number", () => {
const invalidData = {
multiplier: "invalid",
mapper: "MULTIPLY_NUMBER"
};
const result = NumberMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate when mapper is not 'MULTIPLY_NUMBER'", () => {
const invalidData = {
multiplier: 5,
mapper: "INVALID_MAPPER"
};
const result = NumberMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
});
describe("DIVIDE_NUMBER", () => {
it('should validate when divider is a number and mapper is "DIVIDE_NUMBER"', () => {
const validData = {
Expand Down
82 changes: 82 additions & 0 deletions src/types/__tests__/renameField.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as E from "fp-ts/Either";
import { RenameFieldMapping } from "../renameField";

describe("RenameFieldMapping", () => {
it("should validate", () => {
const validData = {
field: "foo",
input: { foo: "bar" },
newField: "fooo",
mapper: "RENAME_FIELD"
};
const result = RenameFieldMapping.decode(validData);
expect(E.isRight(result)).toBeTruthy();
});
it("should not validate if field is not in input", () => {
const invalidData = {
field: "foo",
input: { bar: "bar" },
newField: "fooo",
mapper: "RENAME_FIELD"
};
const result = RenameFieldMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if newField is already in input", () => {
const invalidData = {
field: "foo",
input: { foo: "bar", fooo: "bar" },
newField: "fooo",
mapper: "RENAME_FIELD"
};
const result = RenameFieldMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if mapper is not 'RENAME_FIELD'", () => {
const invalidData = {
field: "foo",
input: { foo: "bar" },
newField: "fooo",
mapper: "INVALID_MAPPER"
};
const result = RenameFieldMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if field is missing", () => {
const invalidData = {
input: { foo: "bar" },
newField: "fooo",
mapper: "RENAME_FIELD"
};
const result = RenameFieldMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if newField is missing", () => {
const invalidData = {
field: "foo",
input: { foo: "bar" },
mapper: "RENAME_FIELD"
};
const result = RenameFieldMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if input is missing", () => {
const invalidData = {
field: "foo",
newField: "fooo",
mapper: "RENAME_FIELD"
};
const result = RenameFieldMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if input is not an object", () => {
const invalidData = {
field: "foo",
input: "foo",
newField: "fooo",
mapper: "RENAME_FIELD"
};
const result = RenameFieldMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
});
101 changes: 101 additions & 0 deletions src/types/__tests__/renameFields.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as E from "fp-ts/Either";
import { RenameFieldsMapping } from "../renameFields";

describe("renameFieldsMapping", () => {
it("should validate the renameFieldsMapping", () => {
const valiData = {
input: { foo: "foo", bar: "bar" },
mapper: "RENAME_FIELDS",
renameChanges: [
{
e1: "foo",
e2: "foo2"
},
{
e1: "bar",
e2: "bar2"
}
]
};
const result = RenameFieldsMapping.decode(valiData);
expect(E.isRight(result)).toBeTruthy();
});
it("should not validate if renameChanges is missing", () => {
const invalidData = {
input: { foo: "foo", bar: "bar" },
mapper: "RENAME_FIELDS"
};
const result = RenameFieldsMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if input is missing", () => {
const invalidData = {
renameChanges: [
{
e1: "foo",
e2: "foo2"
},
{
e1: "bar",
e2: "bar2"
}
],
mapper: "RENAME_FIELDS"
};
const result = RenameFieldsMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if input is not an object", () => {
const invalidData = {
input: 2,
renameChanges: [
{
e1: "foo",
e2: "foo2"
},
{
e1: "bar",
e2: "bar2"
}
],
mapper: "RENAME_FIELDS"
};
const result = RenameFieldsMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if renameChanges is not an array of tuples2", () => {
const invalidData = {
input: { foo: "foo", bar: "bar" },
renameChanges: [
{
e1: "foo"
},
{
e1: "bar",
e2: "bar2"
}
],
mapper: "RENAME_FIELDS"
};
const result = RenameFieldsMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
it("should not validate if the value of the field e1 of a tuple2 is not a field of input", () => {
const invalidData = {
input: { foo: "foo", bar: "bar" },
renameChanges: [
{
e1: "foooooo",
e2: "foo2"
},
{
e1: "bar",
e2: "bar2"
}
],
mapper: "RENAME_FIELDS"
};
const result = RenameFieldsMapping.decode(invalidData);
expect(E.isRight(result)).toBeFalsy();
});
});
13 changes: 12 additions & 1 deletion src/types/number.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import * as t from "io-ts";

const MultiplyMapping = t.type({
mapper: t.literal("MULTIPLY_NUMBER"),
multiplier: t.number
});

type MultiplyMapping = t.TypeOf<typeof MultiplyMapping>;

const DivideMapping = t.type({
divider: t.number,
mapper: t.literal("DIVIDE_NUMBER")
Expand All @@ -14,6 +21,10 @@ const RoundMapping = t.type({

type RoundMapping = t.TypeOf<typeof RoundMapping>;

export const NumberMapping = t.union([DivideMapping, RoundMapping]);
export const NumberMapping = t.union([
DivideMapping,
RoundMapping,
MultiplyMapping
]);

export type NumberMapping = t.TypeOf<typeof NumberMapping>;
61 changes: 61 additions & 0 deletions src/types/renameField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as O from "fp-ts/Option";
import { has } from "fp-ts/Record";
import * as t from "io-ts";

import { pipe } from "fp-ts/function";

type NotInKeys<T, K extends string> = K extends keyof T ? never : K;

interface IRenameFieldMapping<T, R extends keyof T, K extends string> {
readonly input: T;
readonly newField: NotInKeys<T, K>;
readonly field: R;
readonly mapper: "RENAME_FIELD";
}
const isRenameFieldMappingType = (
input: unknown
): input is IRenameFieldMapping<unknown, never, string> =>
pipe(
input,
O.fromPredicate(
stream =>
stream instanceof Object &&
has("field", stream) &&
has("input", stream) &&
has("newField", stream) &&
has("mapper", stream)
),
O.match(
() => false,
() => true
)
);

const isMapperRenameField = (
input: IRenameFieldMapping<unknown, never, string>
): boolean => input.mapper === "RENAME_FIELD";

const isNewFieldUnique = (
input: IRenameFieldMapping<unknown, never, string>
): boolean => !Object.keys(input.input).includes(input.newField);

const isFieldInInputObject = (
input: IRenameFieldMapping<unknown, never, string>
): boolean => Object.keys(input.input).includes(input.field);

export const RenameFieldMapping = new t.Type<
IRenameFieldMapping<unknown, never, string>,
unknown,
unknown
>(
"RenameFieldMapping",
isRenameFieldMappingType,
(input, context) =>
isRenameFieldMappingType(input) &&
isMapperRenameField(input) &&
isFieldInInputObject(input) &&
isNewFieldUnique(input)
? t.success(input)
: t.failure(input, context),
t.identity
);
73 changes: 73 additions & 0 deletions src/types/renameFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ITuple2 } from "@pagopa/ts-commons/lib/tuples";
import * as O from "fp-ts/Option";
import { has } from "fp-ts/Record";
import { pipe } from "fp-ts/function";
import * as t from "io-ts";
import { renameField } from "../formatter/renameField";

type NotInKeys<T, K extends string> = K extends keyof T ? never : K;
export const renameFields = <T, R extends keyof T, K extends string>(
input: T,
renameChanges: ReadonlyArray<ITuple2<R, NotInKeys<T, K>>>
): T =>
renameChanges.reduce(
(acc, currChange) => renameField(acc, currChange.e1, currChange.e2),
input
);

interface IRenameFieldsMapping<T, R extends keyof T, K extends string> {
readonly input: T;
readonly renameChanges: ReadonlyArray<ITuple2<R, NotInKeys<T, K>>>;
readonly mapper: "RENAME_FIELDS";
}

const isRenameFieldsMappingType = (
input: unknown
): input is IRenameFieldsMapping<unknown, never, string> =>
pipe(
input,
O.fromPredicate(
stream =>
stream instanceof Object &&
has("input", stream) &&
has("renameChanges", stream) &&
has("mapper", stream)
),
O.match(
() => false,
() => true
)
);

const isMapperRenameFields = (
input: IRenameFieldsMapping<unknown, never, string>
): boolean => input.mapper === "RENAME_FIELDS";

const isInputObject = (
input: IRenameFieldsMapping<unknown, never, string>
): boolean => input.input instanceof Object;

const isRenameChangesAValidArrayOfTuples2 = (
input: IRenameFieldsMapping<unknown, never, string>
): boolean =>
Array.isArray(input.renameChanges) &&
input.renameChanges.every(
el => has("e1", el) && has("e2", el) && has(el.e1, input.input)
);

export const RenameFieldsMapping = new t.Type<
IRenameFieldsMapping<unknown, never, string>,
unknown,
unknown
>(
"RenameFieldsMapping",
isRenameFieldsMappingType,
(input, context) =>
isRenameFieldsMappingType(input) &&
isInputObject(input) &&
isMapperRenameFields(input) &&
isRenameChangesAValidArrayOfTuples2(input)
? t.success(input)
: t.failure(input, context),
t.identity
);