typia 15.0.0 → 15.0.1

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.
Files changed (110) hide show
  1. package/lib/http.d.ts +49 -24
  2. package/lib/http.js.map +1 -1
  3. package/lib/http.mjs.map +1 -1
  4. package/lib/internal/_httpFormDataReadBigint.js +7 -5
  5. package/lib/internal/_httpFormDataReadBigint.js.map +1 -1
  6. package/lib/internal/_httpFormDataReadBigint.mjs +1 -1
  7. package/lib/internal/_httpFormDataReadBigint.mjs.map +1 -1
  8. package/lib/internal/_httpFormDataReadBoolean.js +12 -10
  9. package/lib/internal/_httpFormDataReadBoolean.js.map +1 -1
  10. package/lib/internal/_httpFormDataReadBoolean.mjs +1 -1
  11. package/lib/internal/_httpFormDataReadBoolean.mjs.map +1 -1
  12. package/lib/internal/_httpFormDataReadNumber.js +7 -5
  13. package/lib/internal/_httpFormDataReadNumber.js.map +1 -1
  14. package/lib/internal/_httpFormDataReadNumber.mjs +1 -1
  15. package/lib/internal/_httpFormDataReadNumber.mjs.map +1 -1
  16. package/lib/internal/_httpFormDataReadString.d.ts +9 -1
  17. package/lib/internal/_httpFormDataReadString.js +10 -2
  18. package/lib/internal/_httpFormDataReadString.js.map +1 -1
  19. package/lib/internal/_httpFormDataReadString.mjs +9 -1
  20. package/lib/internal/_httpFormDataReadString.mjs.map +1 -1
  21. package/lib/internal/_httpHeaderReadBigint.js +5 -1
  22. package/lib/internal/_httpHeaderReadBigint.js.map +1 -1
  23. package/lib/internal/_httpHeaderReadBigint.mjs +2 -1
  24. package/lib/internal/_httpHeaderReadBigint.mjs.map +1 -1
  25. package/lib/internal/_httpHeaderReadNumber.js +5 -1
  26. package/lib/internal/_httpHeaderReadNumber.js.map +1 -1
  27. package/lib/internal/_httpHeaderReadNumber.mjs +2 -1
  28. package/lib/internal/_httpHeaderReadNumber.mjs.map +1 -1
  29. package/lib/internal/_httpParameterReadBigint.js +4 -0
  30. package/lib/internal/_httpParameterReadBigint.js.map +1 -1
  31. package/lib/internal/_httpParameterReadBigint.mjs +1 -0
  32. package/lib/internal/_httpParameterReadBigint.mjs.map +1 -1
  33. package/lib/internal/_httpParameterReadNumber.js +4 -0
  34. package/lib/internal/_httpParameterReadNumber.js.map +1 -1
  35. package/lib/internal/_httpParameterReadNumber.mjs +1 -0
  36. package/lib/internal/_httpParameterReadNumber.mjs.map +1 -1
  37. package/lib/internal/_httpParameterReadString.d.ts +9 -1
  38. package/lib/internal/_httpParameterReadString.js +9 -1
  39. package/lib/internal/_httpParameterReadString.js.map +1 -1
  40. package/lib/internal/_httpParameterReadString.mjs +9 -1
  41. package/lib/internal/_httpParameterReadString.mjs.map +1 -1
  42. package/lib/internal/_httpQueryReadBigint.js +8 -1
  43. package/lib/internal/_httpQueryReadBigint.js.map +1 -1
  44. package/lib/internal/_httpQueryReadBigint.mjs +1 -1
  45. package/lib/internal/_httpQueryReadBigint.mjs.map +1 -1
  46. package/lib/internal/_httpQueryReadBoolean.js +4 -1
  47. package/lib/internal/_httpQueryReadBoolean.js.map +1 -1
  48. package/lib/internal/_httpQueryReadBoolean.mjs +1 -1
  49. package/lib/internal/_httpQueryReadBoolean.mjs.map +1 -1
  50. package/lib/internal/_httpQueryReadNumber.js +8 -1
  51. package/lib/internal/_httpQueryReadNumber.js.map +1 -1
  52. package/lib/internal/_httpQueryReadNumber.mjs +1 -1
  53. package/lib/internal/_httpQueryReadNumber.mjs.map +1 -1
  54. package/lib/internal/_httpQueryReadString.d.ts +9 -1
  55. package/lib/internal/_httpQueryReadString.js +9 -1
  56. package/lib/internal/_httpQueryReadString.js.map +1 -1
  57. package/lib/internal/_httpQueryReadString.mjs +9 -1
  58. package/lib/internal/_httpQueryReadString.mjs.map +1 -1
  59. package/lib/internal/_randomBigint.d.ts +12 -1
  60. package/lib/internal/_randomBigint.js +90 -2
  61. package/lib/internal/_randomBigint.js.map +1 -1
  62. package/lib/internal/_randomBigint.mjs +70 -2
  63. package/lib/internal/_randomBigint.mjs.map +1 -1
  64. package/lib/internal/_randomInteger.js +21 -12
  65. package/lib/internal/_randomInteger.js.map +1 -1
  66. package/lib/internal/_randomInteger.mjs +21 -16
  67. package/lib/internal/_randomInteger.mjs.map +1 -1
  68. package/lib/internal/_randomNumber.js +19 -10
  69. package/lib/internal/_randomNumber.js.map +1 -1
  70. package/lib/internal/_randomNumber.mjs +19 -14
  71. package/lib/internal/_randomNumber.mjs.map +1 -1
  72. package/native/cmd/ttsc-typia/llm_evaluation_rejects_invalid_probability_test.go +9 -0
  73. package/native/cmd/ttsc-typia/random_recursive_min_items_diagnostic_test.go +18 -1
  74. package/native/core/factories/ExpressionFactory.go +16 -1
  75. package/native/core/factories/MetadataCommentTagFactory.go +177 -29
  76. package/native/core/factories/expression_factory_number_spells_non_finite_values_test.go +69 -0
  77. package/native/core/factories/metadata_comment_tag_factory_holds_bigint_tags_exact_test.go +168 -0
  78. package/native/core/factories/metadata_comment_tag_factory_reads_javascript_numbers_test.go +158 -0
  79. package/native/core/programmers/functional/FunctionalAssertParametersProgrammer.go +1 -3
  80. package/native/core/programmers/functional/FunctionalIsParametersProgrammer.go +107 -9
  81. package/native/core/programmers/functional/FunctionalValidateParametersProgrammer.go +1 -3
  82. package/native/core/programmers/http/HttpFormDataProgrammer.go +41 -2
  83. package/native/core/programmers/http/HttpHeadersProgrammer.go +56 -4
  84. package/native/core/programmers/http/HttpParameterProgrammer.go +1 -3
  85. package/native/core/programmers/http/HttpQueryProgrammer.go +26 -43
  86. package/native/core/programmers/iterate/json_schema_jsDocTags.go +10 -3
  87. package/native/core/programmers/iterate/json_schema_jsdoc_tags_cast_reads_javascript_numbers_test.go +64 -0
  88. package/native/core/programmers/llm/LlmEvaluationProgrammer.go +6 -2
  89. package/native/core/programmers/llm/LlmSchemaProgrammer.go +34 -4
  90. package/native/core/utils/NumberUtil.go +195 -0
  91. package/native/core/utils/number_util_rational_keeps_written_value_test.go +62 -0
  92. package/native/core/utils/number_util_reads_javascript_number_grammar_test.go +147 -0
  93. package/package.json +3 -3
  94. package/src/http.ts +61 -34
  95. package/src/internal/_httpFormDataReadBigint.ts +3 -1
  96. package/src/internal/_httpFormDataReadBoolean.ts +3 -1
  97. package/src/internal/_httpFormDataReadNumber.ts +3 -1
  98. package/src/internal/_httpFormDataReadString.ts +10 -1
  99. package/src/internal/_httpHeaderReadBigint.ts +6 -1
  100. package/src/internal/_httpHeaderReadNumber.ts +6 -1
  101. package/src/internal/_httpParameterReadBigint.ts +3 -0
  102. package/src/internal/_httpParameterReadNumber.ts +3 -0
  103. package/src/internal/_httpParameterReadString.ts +12 -2
  104. package/src/internal/_httpQueryReadBigint.ts +7 -1
  105. package/src/internal/_httpQueryReadBoolean.ts +3 -1
  106. package/src/internal/_httpQueryReadNumber.ts +7 -1
  107. package/src/internal/_httpQueryReadString.ts +10 -1
  108. package/src/internal/_randomBigint.ts +111 -3
  109. package/src/internal/_randomInteger.ts +31 -16
  110. package/src/internal/_randomNumber.ts +23 -12
@@ -2,10 +2,12 @@ package factories
2
2
 
3
3
  import (
4
4
  "math"
5
+ "math/big"
5
6
  "strconv"
6
7
  "strings"
7
8
 
8
9
  schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
10
+ nativeutils "github.com/samchon/typia/packages/typia/native/core/utils"
9
11
  )
10
12
 
11
13
  type metadataCommentTagFactoryNamespace struct{}
@@ -51,6 +53,22 @@ func (metadataCommentTagFactoryNamespace) Analyze(props struct {
51
53
  continue
52
54
  }
53
55
  for key, value := range tagger {
56
+ // An empty arm names a target the tag's value cannot be stated for; see
57
+ // metadataCommentTagFactory_bigint. It is an error only where that target
58
+ // occurs, and the other arms still apply to theirs.
59
+ if value != nil && len(value) == 0 {
60
+ for _, atomic := range props.Metadata.Atomics {
61
+ if atomic.Type == key {
62
+ text := ""
63
+ if len(tag.Text) != 0 {
64
+ text = tag.Text[0].Text
65
+ }
66
+ report(key + " value " + strings.TrimSpace(text) + " is not an int64 integer that a number represents exactly")
67
+ break
68
+ }
69
+ }
70
+ continue
71
+ }
54
72
  filtered := []schemametadata.IMetadataTypeTag{}
55
73
  for _, elem := range value {
56
74
  filtered = append(filtered, elem)
@@ -230,37 +248,37 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
230
248
  Report func(msg string) any
231
249
  Value string
232
250
  }) metadataCommentTagFactory_TagRecord {
233
- value := metadataCommentTagFactory_parse_integer(struct {
251
+ value := metadataCommentTagFactory_value(metadataCommentTagFactory_parse_integer(struct {
234
252
  Report func(msg string) any
235
253
  Unsigned bool
236
254
  Value string
237
- }{Report: props.Report, Value: props.Value, Unsigned: true})
255
+ }{Report: props.Report, Value: props.Value, Unsigned: true}))
238
256
  return metadataCommentTagFactory_TagRecord{"array": {
239
- {Name: "MinItems<" + props.Value + ">", Target: "array", Kind: "minItems", Value: value, Validate: props.Value + " <= $input.length", Exclusive: metadataCommentTagFactory_exclusive("minItems"), Schema: map[string]any{"minItems": value}},
240
- {Name: "MaxItems<" + props.Value + ">", Target: "array", Kind: "maxItems", Value: value, Validate: "$input.length <= " + props.Value, Exclusive: metadataCommentTagFactory_exclusive("maxItems"), Schema: map[string]any{"maxItems": value}},
257
+ {Name: "MinItems<" + props.Value + ">", Target: "array", Kind: "minItems", Value: value, Validate: metadataCommentTagFactory_splice(props.Value) + " <= $input.length", Exclusive: metadataCommentTagFactory_exclusive("minItems"), Schema: map[string]any{"minItems": value}},
258
+ {Name: "MaxItems<" + props.Value + ">", Target: "array", Kind: "maxItems", Value: value, Validate: "$input.length <= " + metadataCommentTagFactory_splice(props.Value), Exclusive: metadataCommentTagFactory_exclusive("maxItems"), Schema: map[string]any{"maxItems": value}},
241
259
  }}
242
260
  },
243
261
  "minItems": func(props struct {
244
262
  Report func(msg string) any
245
263
  Value string
246
264
  }) metadataCommentTagFactory_TagRecord {
247
- value := metadataCommentTagFactory_parse_integer(struct {
265
+ value := metadataCommentTagFactory_value(metadataCommentTagFactory_parse_integer(struct {
248
266
  Report func(msg string) any
249
267
  Unsigned bool
250
268
  Value string
251
- }{Report: props.Report, Value: props.Value, Unsigned: true})
252
- return metadataCommentTagFactory_TagRecord{"array": {{Name: "MinItems<" + props.Value + ">", Target: "array", Kind: "minItems", Value: value, Validate: props.Value + " <= $input.length", Exclusive: metadataCommentTagFactory_exclusive("minItems"), Schema: map[string]any{"minItems": value}}}}
269
+ }{Report: props.Report, Value: props.Value, Unsigned: true}))
270
+ return metadataCommentTagFactory_TagRecord{"array": {{Name: "MinItems<" + props.Value + ">", Target: "array", Kind: "minItems", Value: value, Validate: metadataCommentTagFactory_splice(props.Value) + " <= $input.length", Exclusive: metadataCommentTagFactory_exclusive("minItems"), Schema: map[string]any{"minItems": value}}}}
253
271
  },
254
272
  "maxItems": func(props struct {
255
273
  Report func(msg string) any
256
274
  Value string
257
275
  }) metadataCommentTagFactory_TagRecord {
258
- value := metadataCommentTagFactory_parse_integer(struct {
276
+ value := metadataCommentTagFactory_value(metadataCommentTagFactory_parse_integer(struct {
259
277
  Report func(msg string) any
260
278
  Unsigned bool
261
279
  Value string
262
- }{Report: props.Report, Value: props.Value, Unsigned: true})
263
- return metadataCommentTagFactory_TagRecord{"array": {{Name: "MaxItems<" + props.Value + ">", Target: "array", Kind: "maxItems", Value: value, Validate: "$input.length <= " + props.Value, Exclusive: metadataCommentTagFactory_exclusive("maxItems"), Schema: map[string]any{"maxItems": value}}}}
280
+ }{Report: props.Report, Value: props.Value, Unsigned: true}))
281
+ return metadataCommentTagFactory_TagRecord{"array": {{Name: "MaxItems<" + props.Value + ">", Target: "array", Kind: "maxItems", Value: value, Validate: "$input.length <= " + metadataCommentTagFactory_splice(props.Value), Exclusive: metadataCommentTagFactory_exclusive("maxItems"), Schema: map[string]any{"maxItems": value}}}}
264
282
  },
265
283
  "uniqueItems": func(props struct {
266
284
  Report func(msg string) any
@@ -273,25 +291,25 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
273
291
  Report func(msg string) any
274
292
  Value string
275
293
  }) metadataCommentTagFactory_TagRecord {
276
- return metadataCommentTagFactory_numeric(props, "Minimum", "minimum", props.Value+" <= $input", props.Value+" <= $input")
294
+ return metadataCommentTagFactory_numeric(props, "Minimum", "minimum", metadataCommentTagFactory_splice(props.Value)+" <= $input", metadataCommentTagFactory_splice_integer(props.Value)+" <= $input")
277
295
  },
278
296
  "maximum": func(props struct {
279
297
  Report func(msg string) any
280
298
  Value string
281
299
  }) metadataCommentTagFactory_TagRecord {
282
- return metadataCommentTagFactory_numeric(props, "Maximum", "maximum", "$input <= "+props.Value, "$input <= "+props.Value)
300
+ return metadataCommentTagFactory_numeric(props, "Maximum", "maximum", "$input <= "+metadataCommentTagFactory_splice(props.Value), "$input <= "+metadataCommentTagFactory_splice_integer(props.Value))
283
301
  },
284
302
  "exclusiveMinimum": func(props struct {
285
303
  Report func(msg string) any
286
304
  Value string
287
305
  }) metadataCommentTagFactory_TagRecord {
288
- return metadataCommentTagFactory_numeric(props, "ExclusiveMinimum", "exclusiveMinimum", props.Value+" < $input", props.Value+" < $input")
306
+ return metadataCommentTagFactory_numeric(props, "ExclusiveMinimum", "exclusiveMinimum", metadataCommentTagFactory_splice(props.Value)+" < $input", metadataCommentTagFactory_splice_integer(props.Value)+" < $input")
289
307
  },
290
308
  "exclusiveMaximum": func(props struct {
291
309
  Report func(msg string) any
292
310
  Value string
293
311
  }) metadataCommentTagFactory_TagRecord {
294
- return metadataCommentTagFactory_numeric(props, "ExclusiveMaximum", "exclusiveMaximum", "$input < "+props.Value, "$input < "+props.Value)
312
+ return metadataCommentTagFactory_numeric(props, "ExclusiveMaximum", "exclusiveMaximum", "$input < "+metadataCommentTagFactory_splice(props.Value), "$input < "+metadataCommentTagFactory_splice_integer(props.Value))
295
313
  },
296
314
  "multipleOf": func(props struct {
297
315
  Report func(msg string) any
@@ -303,7 +321,7 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
303
321
  // answers a different question than the `multipleOf` keyword this tag emits.
304
322
  // The bigint arm needs no helper: a bigint remainder is already exact.
305
323
  // `_isMultipleOf` first shipped in 13.1.19; see the doc comment above.
306
- return metadataCommentTagFactory_numeric(props, "MultipleOf", "multipleOf", "$importInternal(\"_isMultipleOf\")($input, "+props.Value+")", "$input % "+props.Value+"n === 0n")
324
+ return metadataCommentTagFactory_numeric(props, "MultipleOf", "multipleOf", "$importInternal(\"_isMultipleOf\")($input, "+metadataCommentTagFactory_splice(props.Value)+")", "$input % "+metadataCommentTagFactory_splice_integer(props.Value)+"n === 0n")
307
325
  },
308
326
  "format": func(props struct {
309
327
  Report func(msg string) any
@@ -335,8 +353,8 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
335
353
  // The comparison helpers keep that code-point walk but stop when the
336
354
  // declared boundary already determines the answer.
337
355
  return metadataCommentTagFactory_TagRecord{"string": {
338
- {Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: "$importInternal(\"_stringLengthGte\")($input, " + props.Value + ")", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}},
339
- {Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLengthLte\")($input, " + props.Value + ")", Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}},
356
+ {Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: "$importInternal(\"_stringLengthGte\")($input, " + metadataCommentTagFactory_splice(props.Value) + ")", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}},
357
+ {Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLengthLte\")($input, " + metadataCommentTagFactory_splice(props.Value) + ")", Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}},
340
358
  }}
341
359
  },
342
360
  "minLength": func(props struct {
@@ -344,14 +362,14 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
344
362
  Value string
345
363
  }) metadataCommentTagFactory_TagRecord {
346
364
  value := metadataCommentTagFactory_parse_number(props)
347
- return metadataCommentTagFactory_TagRecord{"string": {{Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: "$importInternal(\"_stringLengthGte\")($input, " + props.Value + ")", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}}}}
365
+ return metadataCommentTagFactory_TagRecord{"string": {{Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: "$importInternal(\"_stringLengthGte\")($input, " + metadataCommentTagFactory_splice(props.Value) + ")", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}}}}
348
366
  },
349
367
  "maxLength": func(props struct {
350
368
  Report func(msg string) any
351
369
  Value string
352
370
  }) metadataCommentTagFactory_TagRecord {
353
371
  value := metadataCommentTagFactory_parse_number(props)
354
- return metadataCommentTagFactory_TagRecord{"string": {{Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLengthLte\")($input, " + props.Value + ")", Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}}}}
372
+ return metadataCommentTagFactory_TagRecord{"string": {{Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLengthLte\")($input, " + metadataCommentTagFactory_splice(props.Value) + ")", Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}}}}
355
373
  },
356
374
  }
357
375
 
@@ -480,31 +498,161 @@ func metadataCommentTagFactory_numeric(props struct {
480
498
  Value string
481
499
  }, name string, kind string, numberValidate string, bigintValidate string) metadataCommentTagFactory_TagRecord {
482
500
  number := metadataCommentTagFactory_parse_number(props)
483
- integer := metadataCommentTagFactory_parse_integer(struct {
484
- Report func(msg string) any
485
- Unsigned bool
486
- Value string
487
- }{Report: func(string) any { return nil }, Value: props.Value, Unsigned: false})
501
+ if number == nil {
502
+ // Reported as an invalid or non-finite number already; a record would only
503
+ // add "requires number type" on a bigint property, which is not the fault.
504
+ return metadataCommentTagFactory_TagRecord{}
505
+ }
488
506
  exclusive := metadataCommentTagFactory_exclusive(kind)
489
507
  record := metadataCommentTagFactory_TagRecord{
490
508
  "number": {{Name: name + "<" + props.Value + ">", Target: "number", Kind: kind, Value: number, Validate: numberValidate, Exclusive: exclusive, Schema: map[string]any{kind: number}}},
491
509
  }
492
- if integer != nil {
493
- record["bigint"] = []schemametadata.IMetadataTypeTag{{Name: name + "<" + props.Value + "n>", Target: "bigint", Kind: kind, Value: *integer, Validate: bigintValidate, Exclusive: exclusive, Schema: map[string]any{kind: number}}}
510
+ integer, numeric, ok := metadataCommentTagFactory_bigint(props.Value)
511
+ if ok {
512
+ record["bigint"] = []schemametadata.IMetadataTypeTag{{Name: name + "<" + props.Value + "n>", Target: "bigint", Kind: kind, Value: integer, Validate: bigintValidate, Exclusive: exclusive, Schema: map[string]any{kind: number}}}
513
+ } else if numeric {
514
+ // An empty arm: the value bounds a bigint, but not with any value this tag
515
+ // can state. `Analyze` reports it where a bigint occurs, so the bigint part
516
+ // of a `number | bigint` property is never left unconstrained silently.
517
+ record["bigint"] = []schemametadata.IMetadataTypeTag{}
494
518
  }
495
519
  return record
496
520
  }
497
521
 
522
+ // metadataCommentTagFactory_bigint reads a numeric tag's value for its bigint
523
+ // arm.
524
+ //
525
+ // Consumers of that arm hold the value as a double: the bound validators splice
526
+ // a number literal, and the schema `random` draws from is numeric. The written
527
+ // integer survives them only when a double represents it exactly, so that is
528
+ // what the arm admits, within the int64 range its value is carried in
529
+ // (samchon/typia#2352). Reading any integer through a double instead enforced
530
+ // `@minimum 9007199254740993` as 9007199254740992, and let `random` generate
531
+ // values the exact `@multipleOf` check rejects (samchon/typia#2457).
532
+ //
533
+ // numeric reports whether the text writes a finite number at all. One that is
534
+ // no such integer -- `1.5`, `5.0000000000000001`, `9007199254740993`, or
535
+ // `1e-1000001` -- is a bigint bound the tag cannot state, while text that is no
536
+ // finite number has already been reported by the number arm.
537
+ func metadataCommentTagFactory_bigint(text string) (value int64, numeric bool, ok bool) {
538
+ if reading := nativeutils.NumberUtil.Read(text); reading.Numeric == false || reading.Finite == false {
539
+ return 0, false, false
540
+ }
541
+ integer, exact := metadataCommentTagFactory_integer(text)
542
+ if exact == false || integer.IsInt64() == false {
543
+ return 0, true, false
544
+ }
545
+ if _, accuracy := new(big.Float).SetInt(integer).Float64(); accuracy != big.Exact {
546
+ return 0, true, false
547
+ }
548
+ return integer.Int64(), true, true
549
+ }
550
+
551
+ // metadataCommentTagFactory_integer is the integer a finite numeric text
552
+ // writes exactly, if it writes one whose double lies within the int64 range. A
553
+ // few integers just below -2^63 round to it and pass; the bigint record checks
554
+ // the integer itself.
555
+ //
556
+ // Its double, which `NumberUtil.Read` rounds correctly, settles most texts
557
+ // without expanding them. A non-integer double comes only from a non-integer
558
+ // text, and a zero from a text with a non-zero digit is the underflow of a
559
+ // non-integer. A double at 2^63 or above, or below -2^63, is refused too: the
560
+ // int64 texts that round up to 2^63 (from 9223372036854775296) are no doubles,
561
+ // so no bigint record could hold them anyway. `big.Rat` expands only what
562
+ // remains, so a short text such as `@minimum 1e-1000000` costs nothing, on any
563
+ // target.
564
+ func metadataCommentTagFactory_integer(text string) (*big.Int, bool) {
565
+ reading := nativeutils.NumberUtil.Read(text)
566
+ if reading.Numeric == false ||
567
+ reading.Finite == false ||
568
+ reading.Value != math.Trunc(reading.Value) ||
569
+ reading.Value < metadataCommentTagFactory_INT64_MINIMUM ||
570
+ reading.Value >= metadataCommentTagFactory_INT64_EXCLUSIVE_MAXIMUM {
571
+ return nil, false
572
+ }
573
+ if reading.Value == 0 {
574
+ // A zero writes zero only when no digit before its exponent is non-zero;
575
+ // otherwise it underflowed. A radix zero has no `e`, being all zeros.
576
+ mantissa := text
577
+ if index := strings.IndexAny(text, "eE"); index != -1 {
578
+ mantissa = text[:index]
579
+ }
580
+ if strings.ContainsAny(mantissa, "123456789") {
581
+ return nil, false
582
+ }
583
+ return big.NewInt(0), true
584
+ }
585
+ rational, ok := nativeutils.NumberUtil.Rational(text)
586
+ if ok == false || rational.IsInt() == false {
587
+ return nil, false
588
+ }
589
+ return rational.Num(), true
590
+ }
591
+
592
+ // metadataCommentTagFactory_value unwraps a parsed count into the record's
593
+ // value and schema. Storing the pointer itself let every consumer that formats
594
+ // a value print an address: strict LLM schemas described `@minItems 2` as
595
+ // `@minItems 0xc000012345`.
596
+ func metadataCommentTagFactory_value(value *int64) any {
597
+ if value == nil {
598
+ return nil
599
+ }
600
+ return *value
601
+ }
602
+
498
603
  func metadataCommentTagFactory_parse_number(props struct {
499
604
  Report func(msg string) any
500
605
  Value string
501
606
  }) any {
502
- parsed, err := strconv.ParseFloat(props.Value, 64)
503
- if err != nil || math.IsNaN(parsed) {
607
+ reading := nativeutils.NumberUtil.Read(props.Value)
608
+ if reading.Numeric == false {
504
609
  props.Report("invalid number")
505
610
  return nil
506
611
  }
507
- return parsed
612
+ // A JSON Schema keyword such as `minimum` must be a JSON number, and JSON has
613
+ // no spelling for an infinity (samchon/typia#2452).
614
+ if reading.Finite == false {
615
+ props.Report("non-finite number")
616
+ return nil
617
+ }
618
+ return reading.Value
619
+ }
620
+
621
+ // metadataCommentTagFactory_splice respells a numeric tag value for the
622
+ // validator the record splices it into (`16 <= $input`, `$input % 1000n`).
623
+ //
624
+ // The spliced text has to be JavaScript for the value the tag was read as, in
625
+ // both the number and the bigint target. The source spelling need not be:
626
+ // `0x10` happens to be, but `1e3`, `1.0`, `+5`, and `007` are integers whose
627
+ // bigint splice (`1e3n`, `1.0n`, `+5n`, `007n`) is no valid BigInt expression
628
+ // (samchon/typia#2442). An integer spelling keeps its digits as written, so the
629
+ // splice never rounds where the source did not: `Number()` would read
630
+ // `9007199254740993` as ...992, while the bigint `@multipleOf` check appends
631
+ // `n` to the digits and compares exactly.
632
+ // Text that does not read as a finite number is passed through; the parser
633
+ // reports it and nothing is emitted.
634
+ func metadataCommentTagFactory_splice(value string) string {
635
+ if integer, ok := nativeutils.NumberUtil.Integer(value); ok {
636
+ return integer.String()
637
+ }
638
+ if reading := nativeutils.NumberUtil.Read(value); reading.Numeric && reading.Finite {
639
+ return nativeutils.NumberUtil.String(reading.Value)
640
+ }
641
+ return value
642
+ }
643
+
644
+ // metadataCommentTagFactory_splice_integer respells a tag value for a bigint
645
+ // validator: the exact digits of the integer the text writes, whatever its
646
+ // spelling, for every value a bigint record holds. `1.152921504606846976e18`
647
+ // writes 2^60, while its double spells 1152921504606847000, which spliced
648
+ // before an `n` would check a different integer (samchon/typia#2457). Text
649
+ // that writes no integer has no bigint record, and falls back to the number
650
+ // splice.
651
+ func metadataCommentTagFactory_splice_integer(value string) string {
652
+ if integer, ok := metadataCommentTagFactory_integer(value); ok {
653
+ return integer.String()
654
+ }
655
+ return metadataCommentTagFactory_splice(value)
508
656
  }
509
657
 
510
658
  func metadataCommentTagFactory_parse_integer(props struct {
@@ -0,0 +1,69 @@
1
+ package factories
2
+
3
+ import (
4
+ "math"
5
+ "testing"
6
+
7
+ shimast "github.com/microsoft/typescript-go/shim/ast"
8
+ )
9
+
10
+ // TestExpressionFactoryNumberSpellsNonFiniteValues verifies number literals for
11
+ // NaN and the infinities.
12
+ //
13
+ // ExpressionFactory.Number built the literal text with `fmt.Sprint`, which
14
+ // spells positive infinity `+Inf` and prints the magnitude of negative infinity
15
+ // as `Inf`. Both reached the emit as identifiers JavaScript does not declare, so
16
+ // every emitted schema, validator, or random bound carrying one threw
17
+ // `ReferenceError: Inf is not defined` on its first run (samchon/typia#2452).
18
+ // The expected shapes are the JavaScript globals TypeScript's own factory
19
+ // prints. Finite values are the negative twin and must keep their literal.
20
+ //
21
+ // 1. Build numbers for NaN, positive and negative infinity.
22
+ // 2. Assert they are the `NaN` / `Infinity` identifiers, negated for -Infinity.
23
+ // 3. Assert finite values, negative ones, and zero keep numeric literals, with
24
+ // negative zero negated like any negative value rather than spelled `-0`
25
+ // inside one literal.
26
+ func TestExpressionFactoryNumberSpellsNonFiniteValues(t *testing.T) {
27
+ identifier := func(node *shimast.Node, name string) bool {
28
+ return node != nil && node.Kind == shimast.KindIdentifier && node.Text() == name
29
+ }
30
+
31
+ if node := ExpressionFactory.Number(math.NaN()); identifier(node, "NaN") == false {
32
+ t.Fatalf("NaN should be the NaN identifier, got kind %v", node.Kind)
33
+ }
34
+ if node := ExpressionFactory.Number(math.Inf(1)); identifier(node, "Infinity") == false {
35
+ t.Fatalf("+Inf should be the Infinity identifier, got kind %v", node.Kind)
36
+ }
37
+ negative := ExpressionFactory.Number(math.Inf(-1))
38
+ if negative.Kind != shimast.KindPrefixUnaryExpression {
39
+ t.Fatalf("-Inf should be a negation, got kind %v", negative.Kind)
40
+ }
41
+ unary := negative.AsPrefixUnaryExpression()
42
+ if unary.Operator != shimast.KindMinusToken || identifier(unary.Operand, "Infinity") == false {
43
+ t.Fatal("-Inf should negate the Infinity identifier")
44
+ }
45
+
46
+ for _, item := range []struct {
47
+ value any
48
+ negative bool
49
+ text string
50
+ }{
51
+ {3, false, "3"},
52
+ {1.5, false, "1.5"},
53
+ {int64(0), false, "0"},
54
+ {-12, true, "12"},
55
+ {-2.5, true, "2.5"},
56
+ {math.Copysign(0, -1), true, "0"},
57
+ } {
58
+ node := ExpressionFactory.Number(item.value)
59
+ if item.negative {
60
+ if node.Kind != shimast.KindPrefixUnaryExpression {
61
+ t.Fatalf("%v should be a negation, got kind %v", item.value, node.Kind)
62
+ }
63
+ node = node.AsPrefixUnaryExpression().Operand
64
+ }
65
+ if node.Kind != shimast.KindNumericLiteral || node.Text() != item.text {
66
+ t.Fatalf("%v should be the numeric literal %q, got kind %v", item.value, item.text, node.Kind)
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,168 @@
1
+ package factories
2
+
3
+ import (
4
+ "fmt"
5
+ "testing"
6
+
7
+ schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
8
+ )
9
+
10
+ // TestMetadataCommentTagFactoryHoldsBigintTagsExact verifies a numeric comment
11
+ // tag states a bigint bound only when it can state it exactly.
12
+ //
13
+ // The bigint arm of `@minimum`, `@maximum`, `@exclusiveMinimum`,
14
+ // `@exclusiveMaximum`, and `@multipleOf` read its value through a double, so
15
+ // `@minimum 9007199254740993` was enforced as 9007199254740992: the bound
16
+ // validators splice a number literal JavaScript rounds, and `random` drew from
17
+ // the rounded schema while the `@multipleOf` check stayed exact
18
+ // (samchon/typia#2457). Every consumer of the bigint record holds its value as
19
+ // a double, so the record now admits an integer only when a double represents
20
+ // it exactly, within the int64 range of samchon/typia#2352, and splices its
21
+ // exact digits into the bigint check. A number target keeps reading the text
22
+ // as `Number()` does.
23
+ //
24
+ // 1. Admit integers a double represents exactly, whatever their spelling, and
25
+ // splice their exact digits into the bigint check.
26
+ // 2. Refuse every other finite value as a bigint bound: integers a double
27
+ // cannot represent, integers beyond int64, and non-integers, including
28
+ // ones `Number()` rounds to an integer.
29
+ // 3. Report a refused value on a bigint property, and on the bigint part of a
30
+ // `number | bigint` one, while a number property and the number part
31
+ // still take the tag, and report text that is no finite number once.
32
+ func TestMetadataCommentTagFactoryHoldsBigintTagsExact(t *testing.T) {
33
+ //----
34
+ // 1. exact integers
35
+ //----
36
+ for _, item := range []struct {
37
+ text string
38
+ value int64
39
+ }{
40
+ {"9007199254740991", 9007199254740991},
41
+ {"9007199254740992", 9007199254740992},
42
+ {"18014398509481984", 18014398509481984}, // 2^54
43
+ {"54043195528445952", 54043195528445952}, // 3 * 2^54, although `String()` spells it ...950
44
+ {"1152921504606846976", 1152921504606846976}, // 2^60
45
+ {"1.152921504606846976e18", 1152921504606846976},
46
+ {"-9007199254740992", -9007199254740992},
47
+ {"1e3", 1000},
48
+ {"0x10", 16},
49
+ {"-0", 0},
50
+ {"0e999999999", 0},
51
+ {"0E5", 0},
52
+ {"0e99999999999999999999", 0}, // an exponent `big.Rat` cannot parse
53
+ {"0x0", 0},
54
+ {"10000000000000000", 10000000000000000},
55
+ } {
56
+ value, numeric, ok := metadataCommentTagFactory_bigint(item.text)
57
+ if ok == false || numeric == false || value != item.value {
58
+ t.Fatalf("%s must be the bigint %d, got %d (numeric=%v ok=%v)", item.text, item.value, value, numeric, ok)
59
+ }
60
+ if spliced := metadataCommentTagFactory_splice_integer(item.text); spliced != fmt.Sprint(item.value) {
61
+ t.Fatalf("%s must splice into a bigint check as %d, got %s", item.text, item.value, spliced)
62
+ }
63
+ }
64
+
65
+ //----
66
+ // 2. finite values no bigint tag states, and text that is no number
67
+ //----
68
+ for _, text := range []string{
69
+ "9007199254740993", // rounds to ...992
70
+ "9007199254740993.0", // the same integer, spelled with a point
71
+ "9.007199254740993e15", // and with an exponent
72
+ "-9007199254740993",
73
+ "0x20000000000001", // 2^53 + 1 in hexadecimal
74
+ "1152921504606847000", // `String(2 ** 60)`, which is not 2^60
75
+ "10000000000000000000", // beyond int64
76
+ "1.5",
77
+ "1e-3",
78
+ "5.0000000000000001", // `Number()` reads 5
79
+ "1e-400", // `Number()` reads 0
80
+ "1e-1000001", // underflows to 0, but writes no zero
81
+ } {
82
+ if _, numeric, ok := metadataCommentTagFactory_bigint(text); ok || numeric == false {
83
+ t.Fatalf("%s must be a finite value no bigint tag states (numeric=%v ok=%v)", text, numeric, ok)
84
+ }
85
+ }
86
+ for _, text := range []string{"Infinity", "abc", "1_000"} {
87
+ if _, numeric, ok := metadataCommentTagFactory_bigint(text); ok || numeric {
88
+ t.Fatalf("%s is no finite number (numeric=%v ok=%v)", text, numeric, ok)
89
+ }
90
+ }
91
+
92
+ //----
93
+ // 3. where the refusal is reported
94
+ //----
95
+ analyze := func(types []string, name string, text string) (*schemametadata.MetadataSchema, []string) {
96
+ atomics := []*schemametadata.MetadataAtomic{}
97
+ for _, typ := range types {
98
+ atomics = append(atomics, schemametadata.MetadataAtomic_create(schemametadata.MetadataAtomic{Type: typ}))
99
+ }
100
+ meta := schemametadata.MetadataSchema_create(schemametadata.MetadataSchema{Atomics: atomics})
101
+ errors := []MetadataFactory_IError{}
102
+ MetadataCommentTagFactory.Analyze(struct {
103
+ Errors *[]MetadataFactory_IError
104
+ Metadata *schemametadata.MetadataSchema
105
+ Tags []schemametadata.IJsDocTagInfo
106
+ Explore MetadataFactory_IExplore
107
+ }{
108
+ Errors: &errors,
109
+ Metadata: meta,
110
+ Tags: []schemametadata.IJsDocTagInfo{{Name: name, Text: []schemametadata.IJsDocTagInfo_IText{{Text: text}}}},
111
+ })
112
+ messages := []string{}
113
+ for _, err := range errors {
114
+ messages = append(messages, err.Messages...)
115
+ }
116
+ return meta, messages
117
+ }
118
+ refusal := "bigint value 9007199254740993 is not an int64 integer that a number represents exactly"
119
+ for _, name := range []string{"minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"} {
120
+ if _, messages := analyze([]string{"bigint"}, name, "9007199254740993"); len(messages) != 1 || messages[0] != refusal {
121
+ t.Fatalf("@%s on a bigint must report the refusal alone, got %#v", name, messages)
122
+ }
123
+ meta, messages := analyze([]string{"number"}, name, "9007199254740993")
124
+ if len(messages) != 0 || len(meta.Atomics[0].Tags) != 1 {
125
+ t.Fatalf("@%s on a number must apply, got %#v", name, messages)
126
+ }
127
+ meta, messages = analyze([]string{"number", "bigint"}, name, "9007199254740993")
128
+ if len(messages) != 1 || messages[0] != refusal {
129
+ t.Fatalf("@%s on number | bigint must report the bigint part, got %#v", name, messages)
130
+ }
131
+ if len(meta.Atomics[0].Tags) != 1 || len(meta.Atomics[1].Tags) != 0 {
132
+ t.Fatalf("@%s on number | bigint must constrain the number part only", name)
133
+ }
134
+ if meta, messages := analyze([]string{"bigint"}, name, "9007199254740992"); len(messages) != 0 || len(meta.Atomics[0].Tags) != 1 {
135
+ t.Fatalf("@%s 9007199254740992 on a bigint must apply, got %#v", name, messages)
136
+ }
137
+ }
138
+ // A value `Number()` rounds to an integer still names no bigint bound: the
139
+ // number part takes it as 5, and the bigint part is reported, not dropped.
140
+ meta, messages := analyze([]string{"number", "bigint"}, "minimum", "5.0000000000000001")
141
+ if len(messages) != 1 || messages[0] != "bigint value 5.0000000000000001 is not an int64 integer that a number represents exactly" {
142
+ t.Fatalf("@minimum 5.0000000000000001 on number | bigint must report the bigint part, got %#v", messages)
143
+ }
144
+ if len(meta.Atomics[0].Tags) != 1 || meta.Atomics[0].Tags[0][0].Validate != "5 <= $input" || len(meta.Atomics[1].Tags) != 0 {
145
+ t.Fatal("@minimum 5.0000000000000001 on number | bigint must constrain the number part only")
146
+ }
147
+ if _, messages := analyze([]string{"bigint"}, "minimum", "1.5"); len(messages) != 1 || messages[0] != "bigint value 1.5 is not an int64 integer that a number represents exactly" {
148
+ t.Fatalf("@minimum 1.5 on a bigint must be refused, got %#v", messages)
149
+ }
150
+ if _, messages := analyze([]string{"number", "bigint"}, "minimum", "1e-1000001"); len(messages) != 1 || messages[0] != "bigint value 1e-1000001 is not an int64 integer that a number represents exactly" {
151
+ t.Fatalf("@minimum 1e-1000001 on number | bigint must report the bigint part, got %#v", messages)
152
+ }
153
+ // Text that is no finite number is reported once, as such, on any target.
154
+ for _, item := range []struct {
155
+ text string
156
+ message string
157
+ }{
158
+ {"abc", "invalid number"},
159
+ {"Infinity", "non-finite number"},
160
+ {"1e400", "non-finite number"},
161
+ } {
162
+ for _, types := range [][]string{{"bigint"}, {"number", "bigint"}} {
163
+ if _, messages := analyze(types, "minimum", item.text); len(messages) != 1 || messages[0] != item.message {
164
+ t.Fatalf("@minimum %s on %v must report %q alone, got %#v", item.text, types, item.message, messages)
165
+ }
166
+ }
167
+ }
168
+ }