@fedify/vocab-tools 2.2.14-dev.2082 → 2.2.15-dev.2196

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
@@ -212,6 +212,17 @@ export async function* generateClasses(
212
212
  isTemporalInstant,
213
213
  } from "@fedify/vocab-runtime/temporal";\n`;
214
214
  yield `
215
+ function canDecodeIri(iri: string): boolean {
216
+ return URL.canParse(iri) || iri.startsWith("at://");
217
+ }
218
+
219
+ function decodeIri(iri: string): URL {
220
+ return !URL.canParse(iri) && iri.startsWith("at://")
221
+ ? new URL("at://" + encodeURIComponent(iri.substring(5)))
222
+ : new URL(iri);
223
+ }
224
+ `;
225
+ yield `
215
226
  function isValidLanguageTag(language: string): boolean {
216
227
  try {
217
228
  new Intl.Locale(language);
package/src/codec.ts CHANGED
@@ -6,11 +6,13 @@ 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
  isCompactableType,
15
+ skipsUnparsable,
14
16
  } from "./type.ts";
15
17
 
16
18
  export async function* generateEncoder(
@@ -445,7 +447,13 @@ export async function* generateDecoder(
445
447
  yield `
446
448
  const decoded =
447
449
  `;
450
+ const lenient = property.range.length == 1 &&
451
+ skipsUnparsable(property.range[0]);
448
452
  if (property.range.length == 1) {
453
+ if (lenient) {
454
+ yield getDataCheck(property.range[0], types, "v");
455
+ yield " ? ";
456
+ }
449
457
  yield getDecoder(
450
458
  property.range[0],
451
459
  types,
@@ -453,6 +461,7 @@ export async function* generateDecoder(
453
461
  "options",
454
462
  `(values["@id"] == null ? options.baseUrl : new URL(values["@id"]))`,
455
463
  );
464
+ if (lenient) yield " : undefined";
456
465
  } else {
457
466
  const decoders = getDecoders(
458
467
  property.range,
package/src/property.ts CHANGED
@@ -251,7 +251,16 @@ async function* generateProperty(
251
251
  ${JSON.stringify(property.compactName)}];
252
252
  const doc = Array.isArray(prop) ? prop[0] : prop;
253
253
  if (doc != null && typeof doc === "object" && "@context" in doc) {
254
- v = await this.#${property.singularName}_fromJsonLd(doc, options);
254
+ try {
255
+ v = await this.#${property.singularName}_fromJsonLd(doc, options);
256
+ } catch (error) {
257
+ if (!options.suppressError) throw error;
258
+ getLogger(["fedify", "vocab"]).debug(
259
+ "Failed to parse embedded value of {property}: {error}",
260
+ { property: ${JSON.stringify(property.uri)}, error },
261
+ );
262
+ return null;
263
+ }
255
264
  }
256
265
  }
257
266
  `;
@@ -350,7 +359,18 @@ async function* generateProperty(
350
359
  ${JSON.stringify(property.compactName)}];
351
360
  const obj = Array.isArray(prop) ? prop[i] : prop;
352
361
  if (obj != null && typeof obj === "object" && "@context" in obj) {
353
- v = await this.#${property.singularName}_fromJsonLd(obj, options);
362
+ try {
363
+ v = await this.#${property.singularName}_fromJsonLd(obj, options);
364
+ } catch (error) {
365
+ if (!options.suppressError) throw error;
366
+ getLogger(["fedify", "vocab"]).debug(
367
+ "Failed to parse embedded value of {property} at index {index}: {error}",
368
+ { property: ${
369
+ JSON.stringify(property.uri)
370
+ }, index: i, error },
371
+ );
372
+ continue;
373
+ }
354
374
  }
355
375
  }
356
376
  `;
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>,