@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.
@@ -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,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":"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"}
@@ -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":"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"}
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');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fintekkers/ledger-models",
3
3
  "todo": "Replace the version with build script version number",
4
- "version": "0.4.17",
4
+ "version": "0.4.19",
5
5
  "description": "ledger model protos ",
6
6
  "authors": [
7
7
  "David Doherty",