@inrupt/solid-client
Version:
Make your web apps work with Solid Pods.
356 lines (338 loc) • 12.8 kB
text/typescript
/**
* Copyright 2020 Inrupt Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { Literal, NamedNode } from "rdf-js";
import { Thing, Url, UrlString, ThingPersisted } from "../interfaces";
import {
asNamedNode,
isNamedNode,
isLiteral,
normalizeLocale,
XmlSchemaTypeIri,
xmlSchemaTypes,
deserializeBoolean,
deserializeDatetime,
deserializeDecimal,
deserializeInteger,
internal_isValidUrl,
} from "../datatypes";
import { DataFactory } from "../rdfjs";
import {
internal_filterThing,
internal_throwIfNotThing,
} from "./thing.internal";
import {
asIri,
isThing,
ValidPropertyUrlExpectedError,
ValidValueUrlExpectedError,
} from "./thing";
/**
* Create a new Thing with all values removed for the given Property.
*
* The original `thing` is not modified; this function returns a cloned Thing with updated values.
*
* @param thing [[Thing]] to remove values from.
* @param property Property for which to remove all values from the Thing.
* @returns A new Thing equal to the input Thing with all values removed for the given Property.
*/
export function removeAll<T extends Thing>(
thing: T,
property: Url | UrlString
): T;
export function removeAll(thing: Thing, property: Url | UrlString): Thing {
internal_throwIfNotThing(thing);
if (!internal_isValidUrl(property)) {
throw new ValidPropertyUrlExpectedError(property);
}
const predicateNode = asNamedNode(property);
const updatedThing = internal_filterThing(
thing,
(quad) => !quad.predicate.equals(predicateNode)
);
return updatedThing;
}
/**
* Create a new Thing with the given URL removed for the given Property.
*
* The original `thing` is not modified; this function returns a cloned Thing with updated values.
*
* @param thing Thing to remove a URL value from.
* @param property Property for which to remove the given URL value.
* @param value URL to remove from `thing` for the given `Property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export const removeUrl: RemoveOfType<Url | UrlString | ThingPersisted> = (
thing,
property,
value
) => {
internal_throwIfNotThing(thing);
if (!internal_isValidUrl(property)) {
throw new ValidPropertyUrlExpectedError(property);
}
const predicateNode = asNamedNode(property);
if (!isThing(value) && !internal_isValidUrl(value)) {
throw new ValidValueUrlExpectedError(value);
}
const iriNode = isNamedNode(value)
? value
: typeof value === "string"
? asNamedNode(value)
: asNamedNode(asIri(value));
const updatedThing = internal_filterThing(thing, (quad) => {
return (
!quad.predicate.equals(predicateNode) ||
!isNamedNode(quad.object) ||
!quad.object.equals(iriNode)
);
});
return updatedThing;
};
/** @hidden Alias of [[removeUrl]] for those who prefer IRI terminology. */
export const removeIri = removeUrl;
/**
* Create a new Thing with the given boolean removed for the given Property.
*
* The original `thing` is not modified; this function returns a cloned Thing with updated values.
*
* @param thing Thing to remove a boolean value from.
* @param property Property for which to remove the given boolean value.
* @param value Boolean to remove from `thing` for the given `property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export const removeBoolean: RemoveOfType<boolean> = (
thing,
property,
value
) => {
internal_throwIfNotThing(thing);
return removeLiteralMatching(
thing,
property,
xmlSchemaTypes.boolean,
(foundBoolean) => deserializeBoolean(foundBoolean) === value
);
};
/**
* Create a new Thing with the given datetime removed for the given Property.
*
* The original `thing` is not modified; this function returns a cloned Thing with updated values.
*
* @param thing Thing to remove a datetime value from.
* @param property Property for which to remove the given datetime value.
* @param value Datetime to remove from `thing` for the given `property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export const removeDatetime: RemoveOfType<Date> = (thing, property, value) => {
internal_throwIfNotThing(thing);
return removeLiteralMatching(
thing,
property,
xmlSchemaTypes.dateTime,
(foundDatetime) =>
deserializeDatetime(foundDatetime)?.getTime() === value.getTime()
);
};
/**
* Create a new Thing with the given decimal removed for the given Property.
*
* The original `thing` is not modified; this function returns a cloned Thing with updated values.
*
* @param thing Thing to remove a decimal value from.
* @param property Property for which to remove the given decimal value.
* @param value Decimal to remove from `thing` for the given `property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export const removeDecimal: RemoveOfType<number> = (thing, property, value) => {
internal_throwIfNotThing(thing);
return removeLiteralMatching(
thing,
property,
xmlSchemaTypes.decimal,
(foundDecimal) => deserializeDecimal(foundDecimal) === value
);
};
/**
* Create a new Thing with the given integer removed for the given Property.
*
* The original `thing` is not modified; this function returns a cloned Thing with updated values.
*
* @param thing Thing to remove an integer value from.
* @param property Property for which to remove the given integer value.
* @param value Integer to remove from `thing` for the given `property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export const removeInteger: RemoveOfType<number> = (thing, property, value) => {
internal_throwIfNotThing(thing);
return removeLiteralMatching(
thing,
property,
xmlSchemaTypes.integer,
(foundInteger) => deserializeInteger(foundInteger) === value
);
};
/**
* Create a new Thing with the given localised string removed for the given Property.
*
* The original `thing` is not modified; this function returns a cloned Thing with updated values.
*
* @param thing Thing to remove a localised string value from.
* @param property Property for which to remove the given localised string value.
* @param value String to remove from `thing` for the given `property`.
* @param locale Locale of the string to remove.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export function removeStringWithLocale<T extends Thing>(
thing: T,
property: Url | UrlString,
value: string,
locale: string
): T {
internal_throwIfNotThing(thing);
// Note: Due to how the `DataFactory.literal` constructor behaves, this function
// must call directly `removeLiteral` directly, with the locale as the data
// type of the Literal (which is not a valid NamedNode).
return removeLiteral(
thing,
property,
DataFactory.literal(value, normalizeLocale(locale))
);
}
/**
* Create a new Thing with the given unlocalised string removed for the given Property.
*
* The original `thing` is not modified; this function returns a cloned Thing with updated values.
*
* @param thing Thing to remove an unlocalised string value from.
* @param property Property for which to remove the given string value.
* @param value String to remove from `thing` for the given `property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export const removeStringNoLocale: RemoveOfType<string> = (
thing,
property,
value
) => {
internal_throwIfNotThing(thing);
return removeLiteralMatching(
thing,
property,
xmlSchemaTypes.string,
(foundString) => foundString === value
);
};
/**
* @ignore This should not be needed due to the other remove*() functions. If you do find yourself needing it, please file a feature request for your use case.
* @param thing Thing to remove a NamedNode value from.
* @param property Property for which to remove the given NamedNode value.
* @param value NamedNode to remove from `thing` for the given `property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export function removeNamedNode<T extends Thing>(
thing: T,
property: Url | UrlString,
value: NamedNode
): T {
internal_throwIfNotThing(thing);
if (!internal_isValidUrl(property)) {
throw new ValidPropertyUrlExpectedError(property);
}
const predicateNode = asNamedNode(property);
const updatedThing = internal_filterThing(thing, (quad) => {
return (
!quad.predicate.equals(predicateNode) ||
!isNamedNode(quad.object) ||
!quad.object.equals(value)
);
});
return updatedThing;
}
/**
* @ignore This should not be needed due to the other remove*() functions. If you do find yourself needing it, please file a feature request for your use case.
* @param thing Thing to remove a Literal value from.
* @param property Property for which to remove the given Literal value.
* @param value Literal to remove from `thing` for the given `property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
export function removeLiteral<T extends Thing>(
thing: T,
property: Url | UrlString,
value: Literal
): T {
internal_throwIfNotThing(thing);
if (!internal_isValidUrl(property)) {
throw new ValidPropertyUrlExpectedError(property);
}
const predicateNode = asNamedNode(property);
const updatedThing = internal_filterThing(thing, (quad) => {
return (
!quad.predicate.equals(predicateNode) ||
!isLiteral(quad.object) ||
!quad.object.equals(value)
);
});
return updatedThing;
}
/**
* @param thing Thing to remove a Literal value from.
* @param property Property for which to remove the given Literal value.
* @param type Data type that the Literal should be stored as.
* @param matcher Function that returns true if the given value is an equivalent serialisation of the value to remove. For example, when removing a `false` boolean, the matcher should return true for both "0" and "false".
*/
function removeLiteralMatching<T extends Thing>(
thing: T,
property: Url | UrlString,
type: XmlSchemaTypeIri,
matcher: (serialisedValue: string) => boolean
): T {
if (!internal_isValidUrl(property)) {
throw new ValidPropertyUrlExpectedError(property);
}
const predicateNode = asNamedNode(property);
const updatedThing = internal_filterThing(thing, (quad) => {
// Copy every value from the old thing into the new thing, unless it:
return !(
// has the predicate of the value-to-be-removed,
(
quad.predicate.equals(predicateNode) &&
// also is a literal
isLiteral(quad.object) &&
// of the same type as the value-to-be-removed,
quad.object.datatype.equals(DataFactory.namedNode(type)) &&
// and has a value determined to be equal to the value-to-be-removed
// by the given matcher (i.e. because their serialisations are equal):
matcher(quad.object.value)
)
);
});
return updatedThing;
}
/**
* @param thing Thing to remove a value from.
* @param property Property for which to remove the given value.
* @param value Value to remove from `thing` for the given `property`.
* @returns A new Thing equal to the input Thing with the given value removed for the given Property.
*/
type RemoveOfType<Type> = <T extends Thing>(
thing: T,
property: Url | UrlString,
value: Type
) => T;