@fedify/vocab-tools 2.1.24 → 2.1.25

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
@@ -162,6 +162,17 @@ export async function* generateClasses(
162
162
  isTemporalInstant,
163
163
  } from "@fedify/vocab-runtime/temporal";\n`;
164
164
  yield `
165
+ function canDecodeIri(iri: string): boolean {
166
+ return URL.canParse(iri) || iri.startsWith("at://");
167
+ }
168
+
169
+ function decodeIri(iri: string): URL {
170
+ return !URL.canParse(iri) && iri.startsWith("at://")
171
+ ? new URL("at://" + encodeURIComponent(iri.substring(5)))
172
+ : new URL(iri);
173
+ }
174
+ `;
175
+ yield `
165
176
  function isValidLanguageTag(language: string): boolean {
166
177
  try {
167
178
  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,
@@ -466,7 +475,7 @@ export async function* generateDecoder(
466
475
  yield `
467
476
  ;
468
477
  `;
469
- if (property.range.length > 1) {
478
+ if (property.range.length > 1 || lenient) {
470
479
  yield `
471
480
  if (typeof decoded === "undefined") {
472
481
  shouldCacheJsonLd = false;
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",
@@ -619,6 +628,10 @@ export function getDecoder(
619
628
  throw new Error(`Unknown type: ${typeUri}`);
620
629
  }
621
630
 
631
+ export function skipsUnparsable(typeUri: string): boolean {
632
+ return scalarTypes[typeUri]?.skipUnparsable ?? false;
633
+ }
634
+
622
635
  export function getDataCheck(
623
636
  typeUri: string,
624
637
  types: Record<string, TypeSchema>,