@fedify/vocab-tools 2.3.9-dev.2048 → 2.3.9

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/src/class.ts CHANGED
@@ -222,6 +222,17 @@ export async function* generateClasses(
222
222
  isTemporalInstant,
223
223
  } from "@fedify/vocab-runtime/temporal";\n`;
224
224
  yield `
225
+ function canDecodeIri(iri: string): boolean {
226
+ return URL.canParse(iri) || iri.startsWith("at://");
227
+ }
228
+
229
+ function decodeIri(iri: string): URL {
230
+ return !URL.canParse(iri) && iri.startsWith("at://")
231
+ ? new URL("at://" + encodeURIComponent(iri.substring(5)))
232
+ : new URL(iri);
233
+ }
234
+ `;
235
+ yield `
225
236
  function isValidLanguageTag(language: string): boolean {
226
237
  try {
227
238
  new Intl.Locale(language);
package/src/codec.ts CHANGED
@@ -6,12 +6,14 @@ import {
6
6
  areAllScalarTypes,
7
7
  emitOverride,
8
8
  getAllProperties,
9
+ getDataCheck,
9
10
  getDecoder,
10
11
  getDecoders,
11
12
  getEncoders,
12
13
  getSubtypes,
13
14
  getTypeNames,
14
15
  isCompactableType,
16
+ skipsUnparsable,
15
17
  } from "./type.ts";
16
18
 
17
19
  function* generatePreprocessorBlock(
@@ -506,7 +508,13 @@ export async function* generateDecoder(
506
508
  yield `
507
509
  const decoded =
508
510
  `;
511
+ const lenient = property.range.length == 1 &&
512
+ skipsUnparsable(property.range[0]);
509
513
  if (property.range.length == 1) {
514
+ if (lenient) {
515
+ yield getDataCheck(property.range[0], types, "v");
516
+ yield " ? ";
517
+ }
510
518
  yield getDecoder(
511
519
  property.range[0],
512
520
  types,
@@ -514,6 +522,7 @@ export async function* generateDecoder(
514
522
  "options",
515
523
  propertyBaseUrl,
516
524
  );
525
+ if (lenient) yield " : undefined";
517
526
  } else {
518
527
  const decoders = getDecoders(
519
528
  property.range,
package/src/property.ts CHANGED
@@ -302,14 +302,19 @@ async function* generateProperty(
302
302
  ${JSON.stringify(property.compactName)}];
303
303
  const doc = Array.isArray(prop) ? prop[0] : prop;
304
304
  if (doc != null && typeof doc === "object" && "@context" in doc) {
305
- `;
306
- yield `
307
- v = await this.#${property.singularName}_fromJsonLd(doc, {
308
- ...options,
309
- baseUrl: ${cachedPropertyBaseUrl},
310
- });
311
- `;
312
- yield `
305
+ try {
306
+ v = await this.#${property.singularName}_fromJsonLd(doc, {
307
+ ...options,
308
+ baseUrl: ${cachedPropertyBaseUrl},
309
+ });
310
+ } catch (error) {
311
+ if (!options.suppressError) throw error;
312
+ getLogger(["fedify", "vocab"]).debug(
313
+ "Failed to parse embedded value of {property}: {error}",
314
+ { property: ${JSON.stringify(property.uri)}, error },
315
+ );
316
+ return null;
317
+ }
313
318
  }
314
319
  }
315
320
  `;
@@ -408,14 +413,21 @@ async function* generateProperty(
408
413
  ${JSON.stringify(property.compactName)}];
409
414
  const obj = Array.isArray(prop) ? prop[i] : prop;
410
415
  if (obj != null && typeof obj === "object" && "@context" in obj) {
411
- `;
412
- yield `
413
- v = await this.#${property.singularName}_fromJsonLd(obj, {
414
- ...options,
415
- baseUrl: ${cachedPropertyBaseUrl},
416
- });
417
- `;
418
- yield `
416
+ try {
417
+ v = await this.#${property.singularName}_fromJsonLd(obj, {
418
+ ...options,
419
+ baseUrl: ${cachedPropertyBaseUrl},
420
+ });
421
+ } catch (error) {
422
+ if (!options.suppressError) throw error;
423
+ getLogger(["fedify", "vocab"]).debug(
424
+ "Failed to parse embedded value of {property} at index {index}: {error}",
425
+ { property: ${
426
+ JSON.stringify(property.uri)
427
+ }, index: i, error },
428
+ );
429
+ continue;
430
+ }
419
431
  }
420
432
  }
421
433
  `;
package/src/type.ts CHANGED
@@ -17,6 +17,12 @@ interface ScalarType {
17
17
  compactEncoder?: (variable: string) => string;
18
18
  dataCheck(variable: string): string;
19
19
  decoder(variable: string, baseUrlVar: string): string;
20
+ /**
21
+ * Whether a value that fails `dataCheck()` should be skipped instead of
22
+ * being handed to `decoder()`. Set this for types decoded from untrusted
23
+ * remote input, where one malformed value must not fail the whole object.
24
+ */
25
+ skipUnparsable?: boolean;
20
26
  }
21
27
 
22
28
  const scalarTypes: Record<string, ScalarType> = {
@@ -332,13 +338,16 @@ const scalarTypes: Record<string, ScalarType> = {
332
338
  return `${v}.href`;
333
339
  },
334
340
  dataCheck(v) {
335
- return `typeof ${v} === "object" && "@value" in ${v}
336
- && typeof ${v}["@value"] === "string"
337
- && ${v}["@value"] !== "" && ${v}["@value"] !== "/"`;
341
+ return `typeof ${v} === "object" &&
342
+ ((typeof ${v}["@id"] === "string" && canDecodeIri(${v}["@id"])) ||
343
+ (typeof ${v}["@value"] === "string" && canDecodeIri(${v}["@value"])))`;
338
344
  },
339
345
  decoder(v) {
340
- return `new URL(${v}["@value"])`;
346
+ return `decodeIri(
347
+ typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"]
348
+ )`;
341
349
  },
350
+ skipUnparsable: true,
342
351
  },
343
352
  "fedify:publicKey": {
344
353
  name: "CryptoKey",
@@ -649,6 +658,10 @@ export function getDecoder(
649
658
  throw new Error(`Unknown type: ${typeUri}`);
650
659
  }
651
660
 
661
+ export function skipsUnparsable(typeUri: string): boolean {
662
+ return scalarTypes[typeUri]?.skipUnparsable ?? false;
663
+ }
664
+
652
665
  export function getDataCheck(
653
666
  typeUri: string,
654
667
  types: Record<string, TypeSchema>,