@@ -47,17 +47,29 @@ const ICP_MEASURE_MAP: Record<string, string> = {
4747 INDEX : "INX" ,
4848} ;
4949
50+ const VALID_SEGMENT = / ^ [ A - Z 0 - 9 _ + ] + $ / ;
51+
5052function normalize ( value : string ) : string {
5153 return value . trim ( ) . toUpperCase ( ) ;
5254}
5355
56+ function validateSegment ( value : string , label : string ) : string {
57+ const normalized = normalize ( value ) ;
58+ if ( ! VALID_SEGMENT . test ( normalized ) ) {
59+ throw new Error (
60+ `Invalid ${ label } : "${ value } ". Only letters, digits, and underscores are allowed.` ,
61+ ) ;
62+ }
63+ return normalized ;
64+ }
65+
5466/**
5567 * Exchange rates (EXR dataflow).
5668 * Template: {freq}.{target}.EUR.SP00.A
5769 */
5870export function buildExrKey ( target : string , freq = "D" ) : string {
59- const f = FREQ_MAP [ normalize ( freq ) ] || normalize ( freq ) ;
60- const t = normalize ( target ) ;
71+ const f = FREQ_MAP [ normalize ( freq ) ] || validateSegment ( freq , "frequency" ) ;
72+ const t = validateSegment ( target , "currency" ) ;
6173 return `${ f } .${ t } .EUR.SP00.A` ;
6274}
6375
@@ -87,15 +99,15 @@ export function buildFmKey(type: string): string {
8799
88100/**
89101 * Yield curve (YC dataflow).
90- * Template: B.U2.EUR.4F.G_N_A .SV_C_YM.{maturity}
102+ * Template: B.U2.EUR.4F.{issuer} .SV_C_YM.{maturity}
91103 *
92- * If issuerType is "all_gov", uses G_N_A (all government bonds).
93- * If "aaa", uses G_N_A (AAA-rated, which is the ECB default for YC ).
104+ * issuerType "aaa" (default) uses G_N_C (AAA-rated government bonds).
105+ * issuerType "all_gov" uses G_N_A (all government bonds regardless of rating ).
94106 */
95107export function buildYcKey ( maturity ?: string , issuerType ?: string ) : string {
96108 const m = maturity ? `SR_${ normalize ( maturity ) } ` : "SR_10Y" ;
97109 const issuer =
98- issuerType && normalize ( issuerType ) === "ALL_GOV" ? "G_N_A" : "G_N_A " ;
110+ issuerType && normalize ( issuerType ) === "ALL_GOV" ? "G_N_A" : "G_N_C " ;
99111 return `B.U2.EUR.4F.${ issuer } .SV_C_YM.${ m } ` ;
100112}
101113
@@ -107,8 +119,9 @@ export function buildYcKey(maturity?: string, issuerType?: string): string {
107119 * Measure defaults to "ANR" (annual rate of change).
108120 */
109121export function buildIcpKey ( country = "U2" , measure = "annual_rate" ) : string {
110- const c = normalize ( country ) ;
111- const m = ICP_MEASURE_MAP [ normalize ( measure ) ] || normalize ( measure ) ;
122+ const c = validateSegment ( country , "country" ) ;
123+ const m =
124+ ICP_MEASURE_MAP [ normalize ( measure ) ] || validateSegment ( measure , "measure" ) ;
112125 return `M.${ c } .N.000000.4.${ m } ` ;
113126}
114127
@@ -125,7 +138,7 @@ export function buildBsiKey(
125138 measure = "outstanding" ,
126139 country = "U2" ,
127140) : string {
128- const c = normalize ( country ) ;
141+ const c = validateSegment ( country , "country" ) ;
129142 const agg = normalize ( aggregate ) ;
130143 const item = MONEY_AGGREGATE_MAP [ agg ] ;
131144 if ( ! item ) {
0 commit comments