@fintekkers/ledger-models 0.4.17 → 0.4.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node/wrappers/models/errors.d.ts +19 -0
- package/node/wrappers/models/errors.js +48 -0
- package/node/wrappers/models/errors.js.map +1 -0
- package/node/wrappers/models/errors.ts +48 -0
- package/node/wrappers/models/security/security.tbillCoupon.test.d.ts +1 -0
- package/node/wrappers/models/security/security.tbillCoupon.test.js +74 -0
- package/node/wrappers/models/security/security.tbillCoupon.test.js.map +1 -0
- package/node/wrappers/models/security/security.tbillCoupon.test.ts +80 -0
- package/node/wrappers/models/security/security_rules.d.ts +19 -0
- package/node/wrappers/models/security/security_rules.js +72 -0
- package/node/wrappers/models/security/security_rules.js.map +1 -0
- package/node/wrappers/models/security/security_rules.ts +62 -0
- package/node/wrappers/services/security-service/SecurityService.js +5 -0
- package/node/wrappers/services/security-service/SecurityService.js.map +1 -1
- package/node/wrappers/services/security-service/SecurityService.ts +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FieldViolationProto } from '../../fintekkers/requests/util/errors/field_violation_pb';
|
|
2
|
+
import { UUID } from './utils/uuid';
|
|
3
|
+
/**
|
|
4
|
+
* Typed input error: the object handed to ledger-models has one or more bad
|
|
5
|
+
* or missing fields. JS counterpart of Java's ModelValidationException
|
|
6
|
+
* (docs/adr/typed-input-errors.md). Services map it to gRPC INVALID_ARGUMENT
|
|
7
|
+
* and can put `violations` as-is into ErrorProto.violations.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ModelValidationError extends Error {
|
|
10
|
+
readonly violations: ReadonlyArray<FieldViolationProto>;
|
|
11
|
+
/** @param violations non-empty list of the field-level problems found */
|
|
12
|
+
constructor(violations: FieldViolationProto[]);
|
|
13
|
+
/** Field path of the first violation, e.g. `bond_details.coupon_rate`. */
|
|
14
|
+
get field(): string;
|
|
15
|
+
/** UUID string of the object that owns the first bad field, or undefined if unset. */
|
|
16
|
+
get objectId(): string | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** Builds one violation. `objectId` may be undefined when the input had no UUID. */
|
|
19
|
+
export declare function violation(field: string, objectId: UUID | undefined, message: string): FieldViolationProto;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.violation = exports.ModelValidationError = void 0;
|
|
4
|
+
const field_violation_pb_1 = require("../../fintekkers/requests/util/errors/field_violation_pb");
|
|
5
|
+
const uuid_1 = require("./utils/uuid");
|
|
6
|
+
/**
|
|
7
|
+
* Typed input error: the object handed to ledger-models has one or more bad
|
|
8
|
+
* or missing fields. JS counterpart of Java's ModelValidationException
|
|
9
|
+
* (docs/adr/typed-input-errors.md). Services map it to gRPC INVALID_ARGUMENT
|
|
10
|
+
* and can put `violations` as-is into ErrorProto.violations.
|
|
11
|
+
*/
|
|
12
|
+
class ModelValidationError extends Error {
|
|
13
|
+
/** @param violations non-empty list of the field-level problems found */
|
|
14
|
+
constructor(violations) {
|
|
15
|
+
if (violations.length === 0) {
|
|
16
|
+
throw new Error('ModelValidationError needs at least one violation');
|
|
17
|
+
}
|
|
18
|
+
super(violations
|
|
19
|
+
.map(v => { var _a; return `${v.getField()}: ${v.getMessage()} (id ${(_a = objectIdOf(v)) !== null && _a !== void 0 ? _a : 'null'})`; })
|
|
20
|
+
.join('; '));
|
|
21
|
+
this.name = 'ModelValidationError';
|
|
22
|
+
this.violations = Object.freeze([...violations]);
|
|
23
|
+
}
|
|
24
|
+
/** Field path of the first violation, e.g. `bond_details.coupon_rate`. */
|
|
25
|
+
get field() {
|
|
26
|
+
return this.violations[0].getField();
|
|
27
|
+
}
|
|
28
|
+
/** UUID string of the object that owns the first bad field, or undefined if unset. */
|
|
29
|
+
get objectId() {
|
|
30
|
+
return objectIdOf(this.violations[0]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.ModelValidationError = ModelValidationError;
|
|
34
|
+
/** Builds one violation. `objectId` may be undefined when the input had no UUID. */
|
|
35
|
+
function violation(field, objectId, message) {
|
|
36
|
+
const v = new field_violation_pb_1.FieldViolationProto();
|
|
37
|
+
v.setField(field);
|
|
38
|
+
v.setMessage(message);
|
|
39
|
+
if (objectId)
|
|
40
|
+
v.setObjectId(objectId.toUUIDProto());
|
|
41
|
+
return v;
|
|
42
|
+
}
|
|
43
|
+
exports.violation = violation;
|
|
44
|
+
function objectIdOf(v) {
|
|
45
|
+
const id = v.getObjectId();
|
|
46
|
+
return id ? uuid_1.UUID.fromU8Array(id.getRawUuid_asU8()).toString() : undefined;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["errors.ts"],"names":[],"mappings":";;;AAAA,iGAA+F;AAC/F,uCAAoC;AAEpC;;;;;GAKG;AACH,MAAa,oBAAqB,SAAQ,KAAK;IAG3C,yEAAyE;IACzE,YAAY,UAAiC;QACzC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;YACzB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;SACxE;QACD,KAAK,CAAC,UAAU;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,WAAC,OAAA,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,UAAU,EAAE,QAAQ,MAAA,UAAU,CAAC,CAAC,CAAC,mCAAI,MAAM,GAAG,CAAA,EAAA,CAAC;aAC9E,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,0EAA0E;IAC1E,IAAI,KAAK;QACL,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACzC,CAAC;IAED,sFAAsF;IACtF,IAAI,QAAQ;QACR,OAAO,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC;CACJ;AAxBD,oDAwBC;AAED,oFAAoF;AACpF,SAAgB,SAAS,CAAC,KAAa,EAAE,QAA0B,EAAE,OAAe;IAChF,MAAM,CAAC,GAAG,IAAI,wCAAmB,EAAE,CAAC;IACpC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACtB,IAAI,QAAQ;QAAE,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,CAAC;AACb,CAAC;AAND,8BAMC;AAED,SAAS,UAAU,CAAC,CAAsB;IACtC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3B,OAAO,EAAE,CAAC,CAAC,CAAC,WAAI,CAAC,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { FieldViolationProto } from '../../fintekkers/requests/util/errors/field_violation_pb';
|
|
2
|
+
import { UUID } from './utils/uuid';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Typed input error: the object handed to ledger-models has one or more bad
|
|
6
|
+
* or missing fields. JS counterpart of Java's ModelValidationException
|
|
7
|
+
* (docs/adr/typed-input-errors.md). Services map it to gRPC INVALID_ARGUMENT
|
|
8
|
+
* and can put `violations` as-is into ErrorProto.violations.
|
|
9
|
+
*/
|
|
10
|
+
export class ModelValidationError extends Error {
|
|
11
|
+
readonly violations: ReadonlyArray<FieldViolationProto>;
|
|
12
|
+
|
|
13
|
+
/** @param violations non-empty list of the field-level problems found */
|
|
14
|
+
constructor(violations: FieldViolationProto[]) {
|
|
15
|
+
if (violations.length === 0) {
|
|
16
|
+
throw new Error('ModelValidationError needs at least one violation');
|
|
17
|
+
}
|
|
18
|
+
super(violations
|
|
19
|
+
.map(v => `${v.getField()}: ${v.getMessage()} (id ${objectIdOf(v) ?? 'null'})`)
|
|
20
|
+
.join('; '));
|
|
21
|
+
this.name = 'ModelValidationError';
|
|
22
|
+
this.violations = Object.freeze([...violations]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Field path of the first violation, e.g. `bond_details.coupon_rate`. */
|
|
26
|
+
get field(): string {
|
|
27
|
+
return this.violations[0].getField();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** UUID string of the object that owns the first bad field, or undefined if unset. */
|
|
31
|
+
get objectId(): string | undefined {
|
|
32
|
+
return objectIdOf(this.violations[0]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Builds one violation. `objectId` may be undefined when the input had no UUID. */
|
|
37
|
+
export function violation(field: string, objectId: UUID | undefined, message: string): FieldViolationProto {
|
|
38
|
+
const v = new FieldViolationProto();
|
|
39
|
+
v.setField(field);
|
|
40
|
+
v.setMessage(message);
|
|
41
|
+
if (objectId) v.setObjectId(objectId.toUUIDProto());
|
|
42
|
+
return v;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function objectIdOf(v: FieldViolationProto): string | undefined {
|
|
46
|
+
const id = v.getObjectId();
|
|
47
|
+
return id ? UUID.fromU8Array(id.getRawUuid_asU8()).toString() : undefined;
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// LM-258: a TBILL has no coupon. coupon_rate must be unset or 0 when
|
|
3
|
+
// product_type == TBILL. See docs/adr/tbill_coupon_validation.md.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
const product_type_pb_1 = require("../../../fintekkers/models/security/product_type_pb");
|
|
6
|
+
const security_pb_1 = require("../../../fintekkers/models/security/security_pb");
|
|
7
|
+
const decimal_value_pb_1 = require("../../../fintekkers/models/util/decimal_value_pb");
|
|
8
|
+
const errors_1 = require("../errors");
|
|
9
|
+
const uuid_1 = require("../utils/uuid");
|
|
10
|
+
const security_rules_1 = require("./security_rules");
|
|
11
|
+
const SECURITY_ID = '1f0e8a4c-58b1-4d0e-9a3c-7d2b6e5f4a10';
|
|
12
|
+
function decimal(value) {
|
|
13
|
+
return new decimal_value_pb_1.DecimalValueProto().setArbitraryPrecisionValue(value);
|
|
14
|
+
}
|
|
15
|
+
/** A security of `productType`; `coupon` undefined leaves coupon_rate unset. */
|
|
16
|
+
function security(productType, coupon) {
|
|
17
|
+
const bond = new security_pb_1.BondDetailsProto().setFaceValue(decimal('1000'));
|
|
18
|
+
if (coupon !== undefined)
|
|
19
|
+
bond.setCouponRate(decimal(coupon));
|
|
20
|
+
return new security_pb_1.SecurityProto()
|
|
21
|
+
.setUuid(new uuid_1.UUID(uuid_1.UUID.fromString(SECURITY_ID)).toUUIDProto())
|
|
22
|
+
.setProductType(productType)
|
|
23
|
+
.setBondDetails(bond);
|
|
24
|
+
}
|
|
25
|
+
function expectRejected(coupon) {
|
|
26
|
+
const proto = security(product_type_pb_1.ProductTypeProto.TBILL, coupon);
|
|
27
|
+
let err;
|
|
28
|
+
try {
|
|
29
|
+
(0, security_rules_1.requireValid)(proto);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
err = e;
|
|
33
|
+
}
|
|
34
|
+
expect(err).toBeInstanceOf(errors_1.ModelValidationError);
|
|
35
|
+
const error = err;
|
|
36
|
+
expect(error.name).toBe('ModelValidationError');
|
|
37
|
+
expect(error.violations).toHaveLength(1);
|
|
38
|
+
expect(error.field).toBe(security_rules_1.COUPON_RATE);
|
|
39
|
+
expect(error.field).toBe('bond_details.coupon_rate');
|
|
40
|
+
expect(error.objectId).toBe(SECURITY_ID);
|
|
41
|
+
expect(error.message).toContain(SECURITY_ID);
|
|
42
|
+
expect(error.message).toContain('coupon_rate');
|
|
43
|
+
}
|
|
44
|
+
describe('TBILL coupon rule', () => {
|
|
45
|
+
test('rejects a TBILL with coupon 6.0', () => {
|
|
46
|
+
expectRejected('6.0');
|
|
47
|
+
});
|
|
48
|
+
test('rejects a TBILL with coupon -1.0', () => {
|
|
49
|
+
expectRejected('-1.0');
|
|
50
|
+
});
|
|
51
|
+
test('accepts a TBILL with no coupon_rate message', () => {
|
|
52
|
+
const proto = security(product_type_pb_1.ProductTypeProto.TBILL);
|
|
53
|
+
expect(proto.getBondDetails().hasCouponRate()).toBe(false);
|
|
54
|
+
expect((0, security_rules_1.validate)(proto)).toEqual([]);
|
|
55
|
+
expect(() => (0, security_rules_1.requireValid)(proto)).not.toThrow();
|
|
56
|
+
});
|
|
57
|
+
test('accepts a TBILL with an empty coupon_rate value', () => {
|
|
58
|
+
const proto = security(product_type_pb_1.ProductTypeProto.TBILL, '');
|
|
59
|
+
expect(proto.getBondDetails().hasCouponRate()).toBe(true);
|
|
60
|
+
expect((0, security_rules_1.validate)(proto)).toEqual([]);
|
|
61
|
+
expect(() => (0, security_rules_1.requireValid)(proto)).not.toThrow();
|
|
62
|
+
});
|
|
63
|
+
test.each(['0', '0.00'])('accepts a TBILL with coupon %s', (zero) => {
|
|
64
|
+
const proto = security(product_type_pb_1.ProductTypeProto.TBILL, zero);
|
|
65
|
+
expect((0, security_rules_1.validate)(proto)).toEqual([]);
|
|
66
|
+
expect(() => (0, security_rules_1.requireValid)(proto)).not.toThrow();
|
|
67
|
+
});
|
|
68
|
+
test('accepts a TREASURY_NOTE with coupon 6.0', () => {
|
|
69
|
+
const proto = security(product_type_pb_1.ProductTypeProto.TREASURY_NOTE, '6.0');
|
|
70
|
+
expect((0, security_rules_1.validate)(proto)).toEqual([]);
|
|
71
|
+
expect(() => (0, security_rules_1.requireValid)(proto)).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=security.tbillCoupon.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.tbillCoupon.test.js","sourceRoot":"","sources":["security.tbillCoupon.test.ts"],"names":[],"mappings":";AAAA,qEAAqE;AACrE,kEAAkE;;AAElE,yFAAuF;AACvF,iFAAkG;AAClG,uFAAqF;AACrF,sCAAiD;AACjD,wCAAqC;AACrC,qDAAuE;AAEvE,MAAM,WAAW,GAAG,sCAAsC,CAAC;AAE3D,SAAS,OAAO,CAAC,KAAa;IAC1B,OAAO,IAAI,oCAAiB,EAAE,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;AACrE,CAAC;AAED,gFAAgF;AAChF,SAAS,QAAQ,CAAC,WAA6B,EAAE,MAAe;IAC5D,MAAM,IAAI,GAAG,IAAI,8BAAgB,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,IAAI,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9D,OAAO,IAAI,2BAAa,EAAE;SACrB,OAAO,CAAC,IAAI,WAAI,CAAC,WAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAC7D,cAAc,CAAC,WAAW,CAAC;SAC3B,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,cAAc,CAAC,MAAc;IAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,kCAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACvD,IAAI,GAAY,CAAC;IACjB,IAAI;QACA,IAAA,6BAAY,EAAC,KAAK,CAAC,CAAC;KACvB;IAAC,OAAO,CAAC,EAAE;QACR,GAAG,GAAG,CAAC,CAAC;KACX;IACD,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,6BAAoB,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,GAA2B,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,4BAAW,CAAC,CAAC;IACtC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACrD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;AACnD,CAAC;AAED,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,IAAI,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,cAAc,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,kCAAgB,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,CAAC,KAAK,CAAC,cAAc,EAAG,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,MAAM,CAAC,IAAA,yBAAQ,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,6BAAY,EAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,KAAK,GAAG,QAAQ,CAAC,kCAAgB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,cAAc,EAAG,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,CAAC,IAAA,yBAAQ,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,6BAAY,EAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,gCAAgC,EAAE,CAAC,IAAI,EAAE,EAAE;QAChE,MAAM,KAAK,GAAG,QAAQ,CAAC,kCAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,IAAA,yBAAQ,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,6BAAY,EAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,kCAAgB,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAA,yBAAQ,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAA,6BAAY,EAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// LM-258: a TBILL has no coupon. coupon_rate must be unset or 0 when
|
|
2
|
+
// product_type == TBILL. See docs/adr/tbill_coupon_validation.md.
|
|
3
|
+
|
|
4
|
+
import { ProductTypeProto } from '../../../fintekkers/models/security/product_type_pb';
|
|
5
|
+
import { BondDetailsProto, SecurityProto } from '../../../fintekkers/models/security/security_pb';
|
|
6
|
+
import { DecimalValueProto } from '../../../fintekkers/models/util/decimal_value_pb';
|
|
7
|
+
import { ModelValidationError } from '../errors';
|
|
8
|
+
import { UUID } from '../utils/uuid';
|
|
9
|
+
import { COUPON_RATE, requireValid, validate } from './security_rules';
|
|
10
|
+
|
|
11
|
+
const SECURITY_ID = '1f0e8a4c-58b1-4d0e-9a3c-7d2b6e5f4a10';
|
|
12
|
+
|
|
13
|
+
function decimal(value: string): DecimalValueProto {
|
|
14
|
+
return new DecimalValueProto().setArbitraryPrecisionValue(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A security of `productType`; `coupon` undefined leaves coupon_rate unset. */
|
|
18
|
+
function security(productType: ProductTypeProto, coupon?: string): SecurityProto {
|
|
19
|
+
const bond = new BondDetailsProto().setFaceValue(decimal('1000'));
|
|
20
|
+
if (coupon !== undefined) bond.setCouponRate(decimal(coupon));
|
|
21
|
+
return new SecurityProto()
|
|
22
|
+
.setUuid(new UUID(UUID.fromString(SECURITY_ID)).toUUIDProto())
|
|
23
|
+
.setProductType(productType)
|
|
24
|
+
.setBondDetails(bond);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function expectRejected(coupon: string): void {
|
|
28
|
+
const proto = security(ProductTypeProto.TBILL, coupon);
|
|
29
|
+
let err: unknown;
|
|
30
|
+
try {
|
|
31
|
+
requireValid(proto);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
err = e;
|
|
34
|
+
}
|
|
35
|
+
expect(err).toBeInstanceOf(ModelValidationError);
|
|
36
|
+
const error = err as ModelValidationError;
|
|
37
|
+
expect(error.name).toBe('ModelValidationError');
|
|
38
|
+
expect(error.violations).toHaveLength(1);
|
|
39
|
+
expect(error.field).toBe(COUPON_RATE);
|
|
40
|
+
expect(error.field).toBe('bond_details.coupon_rate');
|
|
41
|
+
expect(error.objectId).toBe(SECURITY_ID);
|
|
42
|
+
expect(error.message).toContain(SECURITY_ID);
|
|
43
|
+
expect(error.message).toContain('coupon_rate');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('TBILL coupon rule', () => {
|
|
47
|
+
test('rejects a TBILL with coupon 6.0', () => {
|
|
48
|
+
expectRejected('6.0');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('rejects a TBILL with coupon -1.0', () => {
|
|
52
|
+
expectRejected('-1.0');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('accepts a TBILL with no coupon_rate message', () => {
|
|
56
|
+
const proto = security(ProductTypeProto.TBILL);
|
|
57
|
+
expect(proto.getBondDetails()!.hasCouponRate()).toBe(false);
|
|
58
|
+
expect(validate(proto)).toEqual([]);
|
|
59
|
+
expect(() => requireValid(proto)).not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('accepts a TBILL with an empty coupon_rate value', () => {
|
|
63
|
+
const proto = security(ProductTypeProto.TBILL, '');
|
|
64
|
+
expect(proto.getBondDetails()!.hasCouponRate()).toBe(true);
|
|
65
|
+
expect(validate(proto)).toEqual([]);
|
|
66
|
+
expect(() => requireValid(proto)).not.toThrow();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test.each(['0', '0.00'])('accepts a TBILL with coupon %s', (zero) => {
|
|
70
|
+
const proto = security(ProductTypeProto.TBILL, zero);
|
|
71
|
+
expect(validate(proto)).toEqual([]);
|
|
72
|
+
expect(() => requireValid(proto)).not.toThrow();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('accepts a TREASURY_NOTE with coupon 6.0', () => {
|
|
76
|
+
const proto = security(ProductTypeProto.TREASURY_NOTE, '6.0');
|
|
77
|
+
expect(validate(proto)).toEqual([]);
|
|
78
|
+
expect(() => requireValid(proto)).not.toThrow();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SecurityProto } from '../../../fintekkers/models/security/security_pb';
|
|
2
|
+
import { FieldViolationProto } from '../../../fintekkers/requests/util/errors/field_violation_pb';
|
|
3
|
+
/**
|
|
4
|
+
* Security input rules: JS counterpart of Java's SecurityRules
|
|
5
|
+
* (docs/adr/typed-input-errors.md). Pure: proto in, violations out.
|
|
6
|
+
*
|
|
7
|
+
* Holds the TBILL coupon rule (LM-258, docs/adr/tbill_coupon_validation.md);
|
|
8
|
+
* LM-255c adds the bond rules Java already has. The Security wrapper does not
|
|
9
|
+
* call these rules, so stored rows that break them still load; writers call
|
|
10
|
+
* `validate` / `requireValid` before saving.
|
|
11
|
+
*/
|
|
12
|
+
export declare const COUPON_RATE = "bond_details.coupon_rate";
|
|
13
|
+
/**
|
|
14
|
+
* Returns every field-level violation on this security, or an empty array
|
|
15
|
+
* when it is valid. A link security returns an empty array: hydrate it first.
|
|
16
|
+
*/
|
|
17
|
+
export declare function validate(proto: SecurityProto): FieldViolationProto[];
|
|
18
|
+
/** Throws ModelValidationError carrying every violation if `validate` finds any. */
|
|
19
|
+
export declare function requireValid(proto: SecurityProto): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.requireValid = exports.validate = exports.COUPON_RATE = void 0;
|
|
7
|
+
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
8
|
+
const product_type_pb_1 = require("../../../fintekkers/models/security/product_type_pb");
|
|
9
|
+
const errors_1 = require("../errors");
|
|
10
|
+
const uuid_1 = require("../utils/uuid");
|
|
11
|
+
/**
|
|
12
|
+
* Security input rules: JS counterpart of Java's SecurityRules
|
|
13
|
+
* (docs/adr/typed-input-errors.md). Pure: proto in, violations out.
|
|
14
|
+
*
|
|
15
|
+
* Holds the TBILL coupon rule (LM-258, docs/adr/tbill_coupon_validation.md);
|
|
16
|
+
* LM-255c adds the bond rules Java already has. The Security wrapper does not
|
|
17
|
+
* call these rules, so stored rows that break them still load; writers call
|
|
18
|
+
* `validate` / `requireValid` before saving.
|
|
19
|
+
*/
|
|
20
|
+
exports.COUPON_RATE = 'bond_details.coupon_rate';
|
|
21
|
+
/**
|
|
22
|
+
* Returns every field-level violation on this security, or an empty array
|
|
23
|
+
* when it is valid. A link security returns an empty array: hydrate it first.
|
|
24
|
+
*/
|
|
25
|
+
function validate(proto) {
|
|
26
|
+
if (proto.getIsLink())
|
|
27
|
+
return [];
|
|
28
|
+
const out = [];
|
|
29
|
+
const coupon = tbillCouponViolation(proto, objectIdOf(proto));
|
|
30
|
+
if (coupon)
|
|
31
|
+
out.push(coupon);
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
exports.validate = validate;
|
|
35
|
+
/** Throws ModelValidationError carrying every violation if `validate` finds any. */
|
|
36
|
+
function requireValid(proto) {
|
|
37
|
+
const violations = validate(proto);
|
|
38
|
+
if (violations.length > 0)
|
|
39
|
+
throw new errors_1.ModelValidationError(violations);
|
|
40
|
+
}
|
|
41
|
+
exports.requireValid = requireValid;
|
|
42
|
+
/**
|
|
43
|
+
* A TBILL pays no coupon, so coupon_rate must be unset or 0. Uses the
|
|
44
|
+
* explicit product_type only. Units are not checked. A value that does not
|
|
45
|
+
* parse is skipped.
|
|
46
|
+
*/
|
|
47
|
+
function tbillCouponViolation(proto, id) {
|
|
48
|
+
var _a, _b;
|
|
49
|
+
if (proto.getProductType() !== product_type_pb_1.ProductTypeProto.TBILL)
|
|
50
|
+
return undefined;
|
|
51
|
+
const raw = (_b = (_a = proto.getBondDetails()) === null || _a === void 0 ? void 0 : _a.getCouponRate()) === null || _b === void 0 ? void 0 : _b.getArbitraryPrecisionValue();
|
|
52
|
+
if (!raw)
|
|
53
|
+
return undefined;
|
|
54
|
+
let coupon;
|
|
55
|
+
try {
|
|
56
|
+
coupon = new decimal_js_1.default(raw);
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
// decimal.js throws "[DecimalError] Invalid argument" on unparseable input.
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
if (!coupon.isFinite() || coupon.isZero())
|
|
63
|
+
return undefined;
|
|
64
|
+
return (0, errors_1.violation)(exports.COUPON_RATE, id, `coupon_rate must be null or 0 for a TBILL: coupon_rate=${raw}`);
|
|
65
|
+
}
|
|
66
|
+
/** The security's UUID, or undefined when it has none or it is not 16 bytes. */
|
|
67
|
+
function objectIdOf(proto) {
|
|
68
|
+
var _a;
|
|
69
|
+
const raw = (_a = proto.getUuid()) === null || _a === void 0 ? void 0 : _a.getRawUuid_asU8();
|
|
70
|
+
return raw && raw.length === 16 ? uuid_1.UUID.fromU8Array(raw) : undefined;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=security_rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security_rules.js","sourceRoot":"","sources":["security_rules.ts"],"names":[],"mappings":";;;;;;AAAA,4DAAiC;AACjC,yFAAuF;AAGvF,sCAA4D;AAC5D,wCAAqC;AAErC;;;;;;;;GAQG;AAEU,QAAA,WAAW,GAAG,0BAA0B,CAAC;AAEtD;;;GAGG;AACH,SAAgB,QAAQ,CAAC,KAAoB;IACzC,IAAI,KAAK,CAAC,SAAS,EAAE;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,GAAG,GAA0B,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9D,IAAI,MAAM;QAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,GAAG,CAAC;AACf,CAAC;AAND,4BAMC;AAED,oFAAoF;AACpF,SAAgB,YAAY,CAAC,KAAoB;IAC7C,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,6BAAoB,CAAC,UAAU,CAAC,CAAC;AAC1E,CAAC;AAHD,oCAGC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,KAAoB,EAAE,EAAoB;;IACpE,IAAI,KAAK,CAAC,cAAc,EAAE,KAAK,kCAAgB,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IACxE,MAAM,GAAG,GAAG,MAAA,MAAA,KAAK,CAAC,cAAc,EAAE,0CAAE,aAAa,EAAE,0CAAE,0BAA0B,EAAE,CAAC;IAClF,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,IAAI,MAAe,CAAC;IACpB,IAAI;QACA,MAAM,GAAG,IAAI,oBAAO,CAAC,GAAG,CAAC,CAAC;KAC7B;IAAC,OAAO,CAAC,EAAE;QACR,4EAA4E;QAC5E,OAAO,SAAS,CAAC;KACpB;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,SAAS,CAAC;IAC5D,OAAO,IAAA,kBAAS,EAAC,mBAAW,EAAE,EAAE,EAAE,0DAA0D,GAAG,EAAE,CAAC,CAAC;AACvG,CAAC;AAED,gFAAgF;AAChF,SAAS,UAAU,CAAC,KAAoB;;IACpC,MAAM,GAAG,GAAG,MAAA,KAAK,CAAC,OAAO,EAAE,0CAAE,eAAe,EAAE,CAAC;IAC/C,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,WAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Decimal from 'decimal.js';
|
|
2
|
+
import { ProductTypeProto } from '../../../fintekkers/models/security/product_type_pb';
|
|
3
|
+
import { SecurityProto } from '../../../fintekkers/models/security/security_pb';
|
|
4
|
+
import { FieldViolationProto } from '../../../fintekkers/requests/util/errors/field_violation_pb';
|
|
5
|
+
import { ModelValidationError, violation } from '../errors';
|
|
6
|
+
import { UUID } from '../utils/uuid';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Security input rules: JS counterpart of Java's SecurityRules
|
|
10
|
+
* (docs/adr/typed-input-errors.md). Pure: proto in, violations out.
|
|
11
|
+
*
|
|
12
|
+
* Holds the TBILL coupon rule (LM-258, docs/adr/tbill_coupon_validation.md);
|
|
13
|
+
* LM-255c adds the bond rules Java already has. The Security wrapper does not
|
|
14
|
+
* call these rules, so stored rows that break them still load; writers call
|
|
15
|
+
* `validate` / `requireValid` before saving.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const COUPON_RATE = 'bond_details.coupon_rate';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns every field-level violation on this security, or an empty array
|
|
22
|
+
* when it is valid. A link security returns an empty array: hydrate it first.
|
|
23
|
+
*/
|
|
24
|
+
export function validate(proto: SecurityProto): FieldViolationProto[] {
|
|
25
|
+
if (proto.getIsLink()) return [];
|
|
26
|
+
const out: FieldViolationProto[] = [];
|
|
27
|
+
const coupon = tbillCouponViolation(proto, objectIdOf(proto));
|
|
28
|
+
if (coupon) out.push(coupon);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Throws ModelValidationError carrying every violation if `validate` finds any. */
|
|
33
|
+
export function requireValid(proto: SecurityProto): void {
|
|
34
|
+
const violations = validate(proto);
|
|
35
|
+
if (violations.length > 0) throw new ModelValidationError(violations);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A TBILL pays no coupon, so coupon_rate must be unset or 0. Uses the
|
|
40
|
+
* explicit product_type only. Units are not checked. A value that does not
|
|
41
|
+
* parse is skipped.
|
|
42
|
+
*/
|
|
43
|
+
function tbillCouponViolation(proto: SecurityProto, id: UUID | undefined): FieldViolationProto | undefined {
|
|
44
|
+
if (proto.getProductType() !== ProductTypeProto.TBILL) return undefined;
|
|
45
|
+
const raw = proto.getBondDetails()?.getCouponRate()?.getArbitraryPrecisionValue();
|
|
46
|
+
if (!raw) return undefined;
|
|
47
|
+
let coupon: Decimal;
|
|
48
|
+
try {
|
|
49
|
+
coupon = new Decimal(raw);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// decimal.js throws "[DecimalError] Invalid argument" on unparseable input.
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
if (!coupon.isFinite() || coupon.isZero()) return undefined;
|
|
55
|
+
return violation(COUPON_RATE, id, `coupon_rate must be null or 0 for a TBILL: coupon_rate=${raw}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The security's UUID, or undefined when it has none or it is not 16 bytes. */
|
|
59
|
+
function objectIdOf(proto: SecurityProto): UUID | undefined {
|
|
60
|
+
const raw = proto.getUuid()?.getRawUuid_asU8();
|
|
61
|
+
return raw && raw.length === 16 ? UUID.fromU8Array(raw) : undefined;
|
|
62
|
+
}
|
|
@@ -40,6 +40,7 @@ const util_1 = require("util");
|
|
|
40
40
|
const security_1 = __importDefault(require("../../models/security/security"));
|
|
41
41
|
const dt = __importStar(require("../../models/utils/datetime"));
|
|
42
42
|
const identifier_1 = require("../../models/security/identifier");
|
|
43
|
+
const security_rules_1 = require("../../models/security/security_rules");
|
|
43
44
|
// Requests & Services
|
|
44
45
|
const security_service_grpc_pb_1 = require("../../../fintekkers/services/security-service/security_service_grpc_pb");
|
|
45
46
|
const query_security_request_pb_1 = require("../../../fintekkers/requests/security/query_security_request_pb");
|
|
@@ -65,6 +66,9 @@ class SecurityService {
|
|
|
65
66
|
// validateCreateRequest reject so the dry-run RPC can't mask a request
|
|
66
67
|
// that the real createOrUpdate would also fail on.
|
|
67
68
|
(0, identifier_1.validateIdentifiersForCreate)(security);
|
|
69
|
+
// Security input rules (LM-258: a TBILL has no coupon). Throws
|
|
70
|
+
// ModelValidationError before the gRPC round-trip.
|
|
71
|
+
(0, security_rules_1.requireValid)(security);
|
|
68
72
|
const createRequest = new create_security_request_pb_1.CreateSecurityRequestProto();
|
|
69
73
|
createRequest.setObjectClass('SecurityRequest');
|
|
70
74
|
createRequest.setVersion('0.0.1');
|
|
@@ -78,6 +82,7 @@ class SecurityService {
|
|
|
78
82
|
return __awaiter(this, void 0, void 0, function* () {
|
|
79
83
|
// Client-side guard (#347): see validateCreateSecurity above.
|
|
80
84
|
(0, identifier_1.validateIdentifiersForCreate)(security);
|
|
85
|
+
(0, security_rules_1.requireValid)(security);
|
|
81
86
|
const createRequest = new create_security_request_pb_1.CreateSecurityRequestProto();
|
|
82
87
|
createRequest.setObjectClass('SecurityRequest');
|
|
83
88
|
createRequest.setVersion('0.0.1');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityService.js","sourceRoot":"","sources":["SecurityService.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+BAAiC;AAMjC,8EAAsD;AAItD,gEAAkD;AAClD,iEAAgF;
|
|
1
|
+
{"version":3,"file":"SecurityService.js","sourceRoot":"","sources":["SecurityService.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ import Security from '../../models/security/security';
|
|
|
10
10
|
import { PositionFilter } from '../../models/position/positionfilter';
|
|
11
11
|
import * as dt from '../../models/utils/datetime';
|
|
12
12
|
import { validateIdentifiersForCreate } from '../../models/security/identifier';
|
|
13
|
+
import { requireValid } from '../../models/security/security_rules';
|
|
13
14
|
|
|
14
15
|
// Requests & Services
|
|
15
16
|
import { SecurityClient } from '../../../fintekkers/services/security-service/security_service_grpc_pb';
|
|
@@ -41,6 +42,9 @@ class SecurityService {
|
|
|
41
42
|
// validateCreateRequest reject so the dry-run RPC can't mask a request
|
|
42
43
|
// that the real createOrUpdate would also fail on.
|
|
43
44
|
validateIdentifiersForCreate(security);
|
|
45
|
+
// Security input rules (LM-258: a TBILL has no coupon). Throws
|
|
46
|
+
// ModelValidationError before the gRPC round-trip.
|
|
47
|
+
requireValid(security);
|
|
44
48
|
|
|
45
49
|
const createRequest = new CreateSecurityRequestProto();
|
|
46
50
|
createRequest.setObjectClass('SecurityRequest');
|
|
@@ -55,6 +59,7 @@ class SecurityService {
|
|
|
55
59
|
async createSecurity(security: SecurityProto): Promise<CreateSecurityResponseProto> {
|
|
56
60
|
// Client-side guard (#347): see validateCreateSecurity above.
|
|
57
61
|
validateIdentifiersForCreate(security);
|
|
62
|
+
requireValid(security);
|
|
58
63
|
|
|
59
64
|
const createRequest = new CreateSecurityRequestProto();
|
|
60
65
|
createRequest.setObjectClass('SecurityRequest');
|