Skip to content

Commit 6455f16

Browse files
committed
refactor: migrate source to TypeScript
Eid, exceptions, preconditions rewritten as typed classes. ES2022 Error.cause support in all exception constructors. tryToExecute preserves original error as cause. Assisted-by: 🤖 claude-opus-4-6@default
1 parent 5c41454 commit 6455f16

4 files changed

Lines changed: 552 additions & 0 deletions

File tree

‎src/eid.ts‎

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* Copyright 2016 Wave Software
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export interface UniqIdGenerator {
18+
generateUniqId(): string;
19+
}
20+
21+
class JFormatter {
22+
private readonly template: string;
23+
24+
constructor(template: string) {
25+
this.template = template;
26+
}
27+
28+
format(args: unknown[]): string {
29+
const regex = /%s/;
30+
const reducer = (p: string, c: unknown): string => p.replace(regex, String(c));
31+
return args.reduce(reducer, this.template) as string;
32+
}
33+
}
34+
35+
class MathRandom {
36+
private static readonly LONG_MAX = 2 ** 53 - 1;
37+
private static readonly LONG_MIN = -1 * 2 ** 53;
38+
39+
nextLong(): number {
40+
return this.nextNumber(MathRandom.LONG_MIN, MathRandom.LONG_MAX);
41+
}
42+
43+
nextInt(max: number): number {
44+
return this.nextNumber(0, max);
45+
}
46+
47+
nextNumber(min: number, max: number): number {
48+
return Math.floor(Math.random() * (max - min + 1)) + min;
49+
}
50+
}
51+
52+
class StdUniqIdGenerator implements UniqIdGenerator {
53+
private static readonly BASE36 = 36;
54+
private static readonly INTEGER_MAX_VALUE = 2 ** 31;
55+
private static readonly MIN_6_CHAR_HASH = 60466177; // for '100001' in base36
56+
57+
private readonly padding: string;
58+
private readonly generator: MathRandom;
59+
60+
constructor() {
61+
this.generator = new MathRandom();
62+
this.padding = this.generator
63+
.nextNumber(StdUniqIdGenerator.MIN_6_CHAR_HASH, StdUniqIdGenerator.INTEGER_MAX_VALUE)
64+
.toString(StdUniqIdGenerator.BASE36);
65+
}
66+
67+
generateUniqId(): string {
68+
const first = Math.abs(this.generator.nextLong() + 1);
69+
const second = Math.abs(this.generator.nextInt(StdUniqIdGenerator.INTEGER_MAX_VALUE));
70+
const calc = first + second;
71+
const hash = (Math.abs(calc) % StdUniqIdGenerator.INTEGER_MAX_VALUE).toString(
72+
StdUniqIdGenerator.BASE36,
73+
);
74+
return String(this.padding + hash).slice(-6);
75+
}
76+
}
77+
78+
const DEFAULT_FORMAT = "[%s]<%s>";
79+
const DEFAULT_REF_FORMAT = "[%s|%s]<%s>";
80+
const DEFAULT_MESSAGE_FORMAT = "%s => %s";
81+
const DEFAULT_UNIQ_ID_GENERATOR: UniqIdGenerator = new StdUniqIdGenerator();
82+
83+
const FORMAT_NUM_SPEC = 2;
84+
const REF_FORMAT_NUM_SPEC = 3;
85+
const MESSAGE_FORMAT_NUM_SPEC = 2;
86+
87+
interface EidState {
88+
format: string;
89+
refFormat: string;
90+
messageFormat: string;
91+
uniqIdGenerator: UniqIdGenerator;
92+
}
93+
94+
const EidInternal: EidState = {
95+
format: DEFAULT_FORMAT,
96+
refFormat: DEFAULT_REF_FORMAT,
97+
messageFormat: DEFAULT_MESSAGE_FORMAT,
98+
uniqIdGenerator: DEFAULT_UNIQ_ID_GENERATOR,
99+
};
100+
101+
function validateFormat(format: string | null | undefined, numSpecifiers: number): void {
102+
if (format === null || format === undefined) {
103+
throw new TypeError("Format can't be null, but just received one");
104+
}
105+
const specifiers: string[] = [];
106+
for (let i = 0; i < numSpecifiers; i++) {
107+
specifiers.push(`${i}-test-id`);
108+
}
109+
const formatted = new JFormatter(format).format(specifiers);
110+
for (const specifier of specifiers) {
111+
if (formatted.indexOf(specifier) === -1) {
112+
throw new TypeError(
113+
`Given format contains to little format specifiers, expected ${numSpecifiers} but given "${format}"`,
114+
);
115+
}
116+
}
117+
}
118+
119+
/**
120+
* Exception identifier for all Eid Runtime Exceptions.
121+
*
122+
* This class shouldn't be used in any public API or library. It is designed
123+
* to be used for in-house development of end user applications which will
124+
* report Bugs in standardized error pages or post them to issue tracker.
125+
*/
126+
export class Eid {
127+
static readonly DEFAULT_FORMAT: string = DEFAULT_FORMAT;
128+
static readonly DEFAULT_REF_FORMAT: string = DEFAULT_REF_FORMAT;
129+
static readonly DEFAULT_MESSAGE_FORMAT: string = DEFAULT_MESSAGE_FORMAT;
130+
static readonly DEFAULT_UNIQ_ID_GENERATOR: UniqIdGenerator = DEFAULT_UNIQ_ID_GENERATOR;
131+
132+
private readonly id: string;
133+
private readonly ref: string;
134+
private readonly uniq: string;
135+
136+
constructor(id: string, ref?: string | null) {
137+
this.id = id;
138+
this.ref = ref === null || ref === undefined ? "" : ref;
139+
this.uniq = EidInternal.uniqIdGenerator.generateUniqId();
140+
}
141+
142+
toString(): string {
143+
if (this.ref === "") {
144+
return new JFormatter(EidInternal.format).format([this.id, this.uniq]);
145+
}
146+
return new JFormatter(EidInternal.refFormat).format([this.id, this.ref, this.uniq]);
147+
}
148+
149+
getId(): string {
150+
return this.id;
151+
}
152+
153+
getRef(): string {
154+
return this.ref;
155+
}
156+
157+
getUniq(): string {
158+
return this.uniq;
159+
}
160+
161+
makeLogMessage(logMessageFormat: string, ...params: unknown[]): string {
162+
const message = new JFormatter(logMessageFormat).format(params);
163+
return new JFormatter(Eid.getMessageFormat()).format([this.toString(), message]);
164+
}
165+
166+
static setFormat(format: string): string {
167+
validateFormat(format, FORMAT_NUM_SPEC);
168+
const previously = EidInternal.format;
169+
EidInternal.format = format;
170+
return previously;
171+
}
172+
173+
static setRefFormat(refFormat: string): string {
174+
validateFormat(refFormat, REF_FORMAT_NUM_SPEC);
175+
const previously = EidInternal.refFormat;
176+
EidInternal.refFormat = refFormat;
177+
return previously;
178+
}
179+
180+
static setMessageFormat(format: string): string {
181+
validateFormat(format, MESSAGE_FORMAT_NUM_SPEC);
182+
const oldFormat = EidInternal.messageFormat;
183+
EidInternal.messageFormat = format;
184+
return oldFormat;
185+
}
186+
187+
static getMessageFormat(): string {
188+
return EidInternal.messageFormat;
189+
}
190+
191+
static setUniqIdGenerator(uniqIdGenerator: UniqIdGenerator): UniqIdGenerator {
192+
if (uniqIdGenerator === null || uniqIdGenerator === undefined) {
193+
throw new TypeError("Unique ID generator can't be null, but given one");
194+
}
195+
const previous = EidInternal.uniqIdGenerator;
196+
EidInternal.uniqIdGenerator = uniqIdGenerator;
197+
return previous;
198+
}
199+
}
200+
201+
export default Eid;

‎src/exceptions.ts‎

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2016 Wave Software
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { Eid } from "./eid";
18+
19+
function prepareMessage(eid: Eid, message: string | undefined): string {
20+
return message !== undefined ? `${eid} ${message}` : String(eid);
21+
}
22+
23+
function validateEid(eid: string | Eid | undefined): Eid {
24+
if (eid === undefined) {
25+
throw new TypeError(
26+
"You need to provide an valid Eid number to EidRuntimeExceptions, but given undefined",
27+
);
28+
}
29+
if (!(eid instanceof Eid)) {
30+
return new Eid(String(eid));
31+
}
32+
return eid;
33+
}
34+
35+
interface ErrorWithCaptureStackTrace {
36+
captureStackTrace?(targetObject: object, constructorOpt?: unknown): void;
37+
}
38+
39+
function correctStackTrace(target: Error, ctor: unknown): void {
40+
const ErrorCtor = Error as unknown as ErrorWithCaptureStackTrace;
41+
if (typeof ErrorCtor.captureStackTrace === "function") {
42+
ErrorCtor.captureStackTrace(target, ctor);
43+
} /* v8 ignore start -- legacy fallback for environments without Error.captureStackTrace */ else {
44+
const stringified = target.toString();
45+
let st: string = new Error().stack ?? stringified;
46+
if (typeof st !== "string") {
47+
st = stringified;
48+
}
49+
if (st.indexOf(stringified) < 0) {
50+
st = st.replace(/^(?:Error\n)?/, `${stringified}\n`).trim();
51+
}
52+
target.stack = st;
53+
} /* v8 ignore stop */
54+
}
55+
56+
/**
57+
* Baseline of all Eid runtime exception classes.
58+
*
59+
* This class shouldn't be used in any public API or library. It is designed
60+
* to be used for in-house development of end user applications which will
61+
* report Bugs in standardized error pages or post them to issue tracker.
62+
*/
63+
export class EidRuntimeException extends Error {
64+
readonly eid: Eid;
65+
66+
constructor(eid: string | Eid | undefined, message?: string, cause?: unknown) {
67+
const checkedEid = validateEid(eid);
68+
const fullMessage = prepareMessage(checkedEid, message);
69+
super(fullMessage, cause !== undefined ? { cause } : undefined);
70+
Object.setPrototypeOf(this, new.target.prototype);
71+
this.name = "EidRuntimeException";
72+
this.eid = checkedEid;
73+
correctStackTrace(this, EidRuntimeException);
74+
}
75+
}
76+
77+
/**
78+
* Eid version of NullPointerException.
79+
*
80+
* This class shouldn't be used in any public API or library.
81+
*/
82+
export class EidNullPointerException extends EidRuntimeException {
83+
constructor(eid: string | Eid | undefined, message?: string, cause?: unknown) {
84+
super(eid, message, cause);
85+
Object.setPrototypeOf(this, new.target.prototype);
86+
this.name = "EidNullPointerException";
87+
correctStackTrace(this, EidNullPointerException);
88+
}
89+
}
90+
91+
/**
92+
* Eid version of IllegalArgumentException.
93+
*
94+
* This class shouldn't be used in any public API or library.
95+
*/
96+
export class EidIllegalArgumentException extends EidRuntimeException {
97+
constructor(eid: string | Eid | undefined, message?: string, cause?: unknown) {
98+
super(eid, message, cause);
99+
Object.setPrototypeOf(this, new.target.prototype);
100+
this.name = "EidIllegalArgumentException";
101+
correctStackTrace(this, EidIllegalArgumentException);
102+
}
103+
}
104+
105+
/**
106+
* Eid version of IllegalStateException.
107+
*
108+
* This class shouldn't be used in any public API or library.
109+
*/
110+
export class EidIllegalStateException extends EidRuntimeException {
111+
constructor(eid: string | Eid | undefined, message?: string, cause?: unknown) {
112+
super(eid, message, cause);
113+
Object.setPrototypeOf(this, new.target.prototype);
114+
this.name = "EidIllegalStateException";
115+
correctStackTrace(this, EidIllegalStateException);
116+
}
117+
}
118+
119+
/**
120+
* Eid version of IndexOutOfBoundsException.
121+
*
122+
* This class shouldn't be used in any public API or library.
123+
*/
124+
export class EidIndexOutOfBoundsException extends EidRuntimeException {
125+
constructor(eid: string | Eid | undefined, message?: string, cause?: unknown) {
126+
super(eid, message, cause);
127+
Object.setPrototypeOf(this, new.target.prototype);
128+
this.name = "EidIndexOutOfBoundsException";
129+
correctStackTrace(this, EidIndexOutOfBoundsException);
130+
}
131+
}

‎src/index.ts‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2016 Wave Software
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export { Eid } from "./eid";
18+
export type { UniqIdGenerator } from "./eid";
19+
export {
20+
EidRuntimeException,
21+
EidNullPointerException,
22+
EidIllegalArgumentException,
23+
EidIllegalStateException,
24+
EidIndexOutOfBoundsException,
25+
} from "./exceptions";
26+
export { EidPreconditions } from "./preconditions";
27+
28+
import { Eid } from "./eid";
29+
import {
30+
EidIllegalArgumentException,
31+
EidIllegalStateException,
32+
EidIndexOutOfBoundsException,
33+
EidNullPointerException,
34+
EidRuntimeException,
35+
} from "./exceptions";
36+
import { EidPreconditions } from "./preconditions";
37+
38+
// Backward compatibility: attach preconditions and exceptions to Eid
39+
const preconditions = EidPreconditions;
40+
const exceptions = {
41+
EidRuntimeException,
42+
EidNullPointerException,
43+
EidIllegalArgumentException,
44+
EidIllegalStateException,
45+
EidIndexOutOfBoundsException,
46+
};
47+
48+
export { preconditions, exceptions };
49+
export default Eid;

0 commit comments

Comments
 (0)