Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions Sources/Internal/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,32 @@ import Foundation

/// Wraps a dictionary parsed from a JSON string.
/// This is a trick to keep the Web Publication structs equatable without having to override `==` and compare all the other properties.
public struct JSONDictionary {
public var json: [String: Any]
public struct JSONDictionary: Sendable {
public typealias Key = String
public typealias Value = Sendable
public typealias Wrapped = [Key: Value]

public var json: Wrapped

public init() {
json = [:]
}

public init?(_ json: Any?) {
guard let json = json as? [String: Any] else {
guard let json = json as? Wrapped else {
return nil
}
self.json = json
}

public mutating func pop(_ key: String) -> Any? {
public mutating func pop(_ key: Key) -> Value? {
json.removeValue(forKey: key)
}
}

extension JSONDictionary: Collection {
public typealias Index = DictionaryIndex<String, Any>
public typealias Element = (key: String, value: Any)
public typealias Index = DictionaryIndex<Key, Value>
public typealias Element = (key: Key, value: Value)

public var startIndex: Index {
json.startIndex
Expand Down Expand Up @@ -122,7 +126,7 @@ public func parseDate(_ json: Any?) -> Date? {

/// Returns the given JSON object after removing any key with NSNull value.
/// To be used with `encodeIfX` functions for more compact serialization code.
public func makeJSON(_ object: [String: Any], additional: [String: Any] = [:]) -> [String: Any] {
public func makeJSON(_ object: JSONDictionary.Wrapped, additional: JSONDictionary.Wrapped = [:]) -> JSONDictionary.Wrapped {
object.filter { _, value in
!(value is NSNull)
}.merging(additional, uniquingKeysWith: { current, _ in current })
Expand Down
14 changes: 7 additions & 7 deletions Sources/Shared/Publication/Accessibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ReadiumInternal
///
/// https://www.w3.org/2021/a11y-discov-vocab/latest/
/// https://readium.org/webpub-manifest/schema/a11y.schema.json
public struct Accessibility: Hashable {
public struct Accessibility: Hashable, Sendable {
/// An established standard to which the described resource conforms.
public var conformsTo: [Profile]

Expand Down Expand Up @@ -49,7 +49,7 @@ public struct Accessibility: Hashable {
public var hazards: [Hazard]

/// Accessibility profile.
public struct Profile: Hashable {
public struct Profile: Hashable, Sendable {
public let uri: String

public init(_ uri: String) {
Expand All @@ -64,7 +64,7 @@ public struct Accessibility: Hashable {
public static let epubA11y10WCAG20AAA = Profile("http://www.idpf.org/epub/a11y/accessibility-20170105.html#wcag-aaa")
}

public struct Certification: Hashable {
public struct Certification: Hashable, Sendable {
/// Identifies a party responsible for the testing and certification of the accessibility of a Publication.
///
/// https://www.w3.org/TR/epub-a11y/#certifiedBy
Expand All @@ -89,7 +89,7 @@ public struct Accessibility: Hashable {
}
}

public struct AccessMode: Hashable {
public struct AccessMode: Hashable, Sendable {
public let id: String

public init(_ id: String) {
Expand Down Expand Up @@ -134,7 +134,7 @@ public struct Accessibility: Hashable {
public static let visual = AccessMode("visual")
}

public enum PrimaryAccessMode: String, Hashable {
public enum PrimaryAccessMode: String, Hashable, Sendable {
/// Indicates that auditory perception is necessary to consume the information.
case auditory

Expand All @@ -151,7 +151,7 @@ public struct Accessibility: Hashable {
case visual
}

public struct Feature: Hashable {
public struct Feature: Hashable, Sendable {
public let id: String

public init(_ id: String) {
Expand Down Expand Up @@ -306,7 +306,7 @@ public struct Accessibility: Hashable {
public static let none = Feature("none")
}

public struct Hazard: Hashable {
public struct Hazard: Hashable, Sendable {
public let id: String

public init(_ id: String) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Shared/Publication/Asset/FileAsset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

/// Represents a publication stored as a file on the local file system.
public final class FileAsset: PublicationAsset, Loggable {
public final class FileAsset: PublicationAsset, Loggable, Sendable {
/// File URL on the file system.
public let file: FileURL

Expand Down
2 changes: 1 addition & 1 deletion Sources/Shared/Publication/Asset/PublicationAsset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

/// Represents a digital medium (e.g. a file) offering access to a publication.
public protocol PublicationAsset {
public protocol PublicationAsset: Sendable {
/// Name of the asset, e.g. a filename.
var name: String { get }

Expand Down
2 changes: 1 addition & 1 deletion Sources/Shared/Publication/Contributor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
import ReadiumInternal

/// https://readium.org/webpub-manifest/schema/contributor-object.schema.json
public struct Contributor: Hashable {
public struct Contributor: Hashable, Sendable {
/// The name of the contributor.
public var localizedName: LocalizedString
public var name: String { localizedName.string }
Expand Down
10 changes: 5 additions & 5 deletions Sources/Shared/Publication/Link.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public enum LinkError: Error, Equatable {

/// Link Object for the Readium Web Publication Manifest.
/// https://readium.org/webpub-manifest/schema/link.schema.json
public struct Link: JSONEquatable, Hashable {
public struct Link: JSONEquatable, Hashable, Sendable {
/// URI or URI template of the linked resource.
/// Note: a String because templates are lost with URL.
public var href: String // URI
Expand Down Expand Up @@ -95,7 +95,7 @@ public struct Link: JSONEquatable, Hashable {
json: Any,
warnings: WarningLogger? = nil
) throws {
guard let jsonObject = json as? [String: Any],
guard let jsonObject = json as? JSONDictionary.Wrapped,
var href = jsonObject["href"] as? String
else {
warnings?.log("`href` is required", model: Self.self, source: json)
Expand Down Expand Up @@ -132,7 +132,7 @@ public struct Link: JSONEquatable, Hashable {
)
}

public var json: [String: Any] {
public var json: JSONDictionary.Wrapped {
makeJSON([
"href": href,
"type": encodeIfNotNil(type),
Expand Down Expand Up @@ -252,7 +252,7 @@ public struct Link: JSONEquatable, Hashable {
}

/// Merges in the given additional other `properties`.
public mutating func addProperties(_ properties: [String: Any]) {
public mutating func addProperties(_ properties: JSONDictionary.Wrapped) {
self.properties.add(properties)
}

Expand Down Expand Up @@ -281,7 +281,7 @@ public extension Array where Element == Link {
append(contentsOf: links)
}

var json: [[String: Any]] {
var json: [JSONDictionary.Wrapped] {
map(\.json)
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Shared/Publication/LinkRelation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

/// Link relations as defined in https://readium.org/webpub-manifest/relationships.html
public struct LinkRelation {
public struct LinkRelation: Sendable {
/// The string representation of this link relation.
public let string: String

Expand Down
2 changes: 1 addition & 1 deletion Sources/Shared/Publication/LocalizedString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
/// Can be either:
/// - a single nonlocalized string
/// - a dictionary of localized strings indexed by the BCP 47 language tag
public enum LocalizedString: Hashable {
public enum LocalizedString: Hashable, Sendable {
case nonlocalized(String)
case localized([String: String])

Expand Down
34 changes: 17 additions & 17 deletions Sources/Shared/Publication/Locator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
import ReadiumInternal

/// https://github.com/readium/architecture/tree/master/locators
public struct Locator: Hashable, CustomStringConvertible, Loggable {
public struct Locator: Hashable, CustomStringConvertible, Loggable, Sendable {
/// The URI of the resource that the Locator Object points to.
public var href: String // URI

Expand Down Expand Up @@ -36,7 +36,7 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {
if json == nil {
return nil
}
guard let jsonObject = json as? [String: Any],
guard let jsonObject = json as? JSONDictionary.Wrapped,
let href = jsonObject["href"] as? String,
let type = jsonObject["type"] as? String
else {
Expand Down Expand Up @@ -82,7 +82,7 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {
@available(*, unavailable, message: "This may create an incorrect `Locator` if the link `type` is missing. Use `publication.locate(Link)` instead.")
public init(link: Link) { fatalError() }

public var json: [String: Any] {
public var json: JSONDictionary.Wrapped {
makeJSON([
"href": href,
"type": type,
Expand Down Expand Up @@ -120,7 +120,7 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {
///
/// Properties are mutable for convenience when making a copy, but the `locations` property
/// is immutable in `Locator`, for safety.
public struct Locations: Hashable, Loggable, WarningLogger {
public struct Locations: Hashable, Loggable, WarningLogger, Sendable {
/// Contains one or more fragment in the resource referenced by the `Locator`.
public var fragments: [String]
/// Progression in the resource expressed as a percentage (between 0 and 1).
Expand All @@ -131,15 +131,15 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {
public var position: Int?

/// Additional locations for extensions.
public var otherLocations: [String: Any] {
public var otherLocations: JSONDictionary.Wrapped {
get { otherLocationsJSON.json }
set { otherLocationsJSON = JSONDictionary(newValue) ?? JSONDictionary() }
}

// Trick to keep the struct equatable despite [String: Any]
private var otherLocationsJSON: JSONDictionary

public init(fragments: [String] = [], progression: Double? = nil, totalProgression: Double? = nil, position: Int? = nil, otherLocations: [String: Any] = [:]) {
public init(fragments: [String] = [], progression: Double? = nil, totalProgression: Double? = nil, position: Int? = nil, otherLocations: JSONDictionary.Wrapped = [:]) {
self.fragments = fragments
self.progression = progression
self.totalProgression = totalProgression
Expand Down Expand Up @@ -181,7 +181,7 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {

public var isEmpty: Bool { json.isEmpty }

public var json: [String: Any] {
public var json: JSONDictionary.Wrapped {
makeJSON([
"fragments": encodeIfNotEmpty(fragments),
"progression": encodeIfNotNil(progression),
Expand All @@ -197,7 +197,7 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {
public subscript(key: String) -> Any? { otherLocations[key] }
}

public struct Text: Hashable, Loggable {
public struct Text: Hashable, Loggable, Sendable {
public var after: String?
public var before: String?
public var highlight: String?
Expand All @@ -213,7 +213,7 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {
self.init()
return
}
guard let jsonObject = json as? [String: Any] else {
guard let jsonObject = json as? JSONDictionary.Wrapped else {
warnings?.log("Invalid Text object", model: Self.self, source: json)
throw JSONError.parsing(Self.self)
}
Expand All @@ -234,7 +234,7 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {
}
}

public var json: [String: Any] {
public var json: JSONDictionary.Wrapped {
makeJSON([
"after": encodeIfNotNil(after),
"before": encodeIfNotNil(before),
Expand Down Expand Up @@ -286,15 +286,15 @@ public extension Array where Element == Locator {
/// Parses multiple JSON locators into an array of `Locator`.
init(json: Any?, warnings: WarningLogger? = nil) {
self.init()
guard let json = json as? [Any] else {
guard let json = json as? [JSONDictionary.Wrapped] else {
return
}

let links = json.compactMap { try? Locator(json: $0, warnings: warnings) }
append(contentsOf: links)
}

var json: [[String: Any]] {
var json: [JSONDictionary.Wrapped] {
map(\.json)
}
}
Expand All @@ -320,7 +320,7 @@ public struct _LocatorCollection: Hashable {
if json == nil {
return nil
}
guard let jsonObject = json as? [String: Any] else {
guard let jsonObject = json as? JSONDictionary.Wrapped else {
warnings?.log("Not a JSON object", model: Self.self, source: json)
return nil
}
Expand All @@ -331,7 +331,7 @@ public struct _LocatorCollection: Hashable {
)
}

public var json: [String: Any] {
public var json: JSONDictionary.Wrapped {
makeJSON([
"metadata": encodeIfNotEmpty(metadata.json),
"links": encodeIfNotEmpty(links.json),
Expand All @@ -348,7 +348,7 @@ public struct _LocatorCollection: Hashable {
public var numberOfItems: Int?

/// Additional properties for extensions.
public var otherMetadata: [String: Any] {
public var otherMetadata: JSONDictionary.Wrapped {
get { otherMetadataJSON.json }
set { otherMetadataJSON = JSONDictionary(newValue) ?? JSONDictionary() }
}
Expand All @@ -359,7 +359,7 @@ public struct _LocatorCollection: Hashable {
public init(
title: LocalizedStringConvertible? = nil,
numberOfItems: Int? = nil,
otherMetadata: [String: Any] = [:]
otherMetadata: JSONDictionary.Wrapped = [:]
) {
localizedTitle = title?.localizedString
self.numberOfItems = numberOfItems
Expand All @@ -379,7 +379,7 @@ public struct _LocatorCollection: Hashable {
}
}

public var json: [String: Any] {
public var json: JSONDictionary.Wrapped {
makeJSON([
"title": encodeIfNotNil(localizedTitle?.json),
"numberOfItems": encodeIfNotNil(numberOfItems),
Expand Down
4 changes: 2 additions & 2 deletions Sources/Shared/Publication/Manifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ReadiumInternal
/// Manifest.
///
/// See. https://readium.org/webpub-manifest/
public struct Manifest: JSONEquatable, Hashable {
public struct Manifest: JSONEquatable, Hashable, Sendable {
public var context: [String] // @context

public var metadata: Metadata
Expand Down Expand Up @@ -85,7 +85,7 @@ public struct Manifest: JSONEquatable, Hashable {
subcollections = PublicationCollection.makeCollections(json: json.json, warnings: warnings)
}

public var json: [String: Any] {
public var json: JSONDictionary.Wrapped {
makeJSON([
"@context": encodeIfNotEmpty(context),
"metadata": metadata.json,
Expand Down
Loading