Skip to content

Commit 97d93f5

Browse files
committed
feat: add IRI.Representable convenience initializers
Add convenience initializers accepting `any IRI.Representable` to all types with IRI parameters, enabling Foundation URL support. Changes: - Person, Link, Generator, Category, Content: add convenience init - Feed, Entry, Source: add convenience init for all IRI parameters - Delegate to concrete IRI initializers using `.iri` property - Add test demonstrating Foundation URL support API now supports three patterns: 1. String literals via ExpressibleByStringLiteral 2. Concrete IRI types directly 3. Foundation URL and other IRI.Representable types Example: ```swift // All three work! Person(name: "John", uri: "https://example.com") // String literal Person(name: "John", uri: someIRI) // IRI instance Person(name: "John", uri: someURL) // Foundation URL ``` All tests pass (23/23)
1 parent cff9ae4 commit 97d93f5

File tree

9 files changed

+238
-0
lines changed

9 files changed

+238
-0
lines changed

Sources/RFC 4287/Category.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ extension RFC_4287 {
3030
self.scheme = scheme
3131
self.label = label
3232
}
33+
34+
/// Creates a new category with IRI.Representable scheme (convenience)
35+
///
36+
/// Accepts any IRI.Representable type such as Foundation URL.
37+
///
38+
/// - Parameters:
39+
/// - term: The category term
40+
/// - scheme: The categorization scheme IRI (e.g., URL)
41+
/// - label: A human-readable label
42+
public init(
43+
term: String,
44+
scheme: (any RFC_3987.IRI.Representable)?,
45+
label: String? = nil
46+
) {
47+
self.init(term: term, scheme: scheme?.iri, label: label)
48+
}
3349
}
3450
}
3551

Sources/RFC 4287/Content.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ extension RFC_4287 {
9090
self.src = src
9191
}
9292

93+
/// Creates out-of-line content with IRI.Representable source (convenience)
94+
///
95+
/// Accepts any IRI.Representable type such as Foundation URL.
96+
///
97+
/// - Parameters:
98+
/// - src: The IRI of the content (e.g., URL)
99+
/// - type: The content type
100+
public init(src: any RFC_3987.IRI.Representable, type: ContentType = .text) {
101+
self.init(src: src.iri, type: type)
102+
}
103+
93104
/// Determines if this content type requires base64 encoding per RFC 4287 Section 4.1.3.3
94105
///
95106
/// Returns true if the content is a binary media type that requires base64 encoding:

Sources/RFC 4287/Entry.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,55 @@ extension RFC_4287 {
108108
self.summary = summary
109109
}
110110

111+
/// Creates a new entry with validation using IRI.Representable id (convenience)
112+
///
113+
/// Accepts any IRI.Representable type such as Foundation URL.
114+
///
115+
/// - Parameters:
116+
/// - id: Permanent, universally unique identifier (e.g., URL)
117+
/// - title: Human-readable title
118+
/// - updated: Timestamp of last modification
119+
/// - authors: Entry authors
120+
/// - content: Entry content
121+
/// - links: Associated links
122+
/// - categories: Entry categories
123+
/// - contributors: Entry contributors
124+
/// - published: Publication timestamp
125+
/// - rights: Rights information
126+
/// - source: Source feed metadata
127+
/// - summary: Entry summary
128+
///
129+
/// - Returns: A validated entry, or nil if validation fails
130+
public init?(
131+
id: any RFC_3987.IRI.Representable,
132+
title: Text,
133+
updated: Date,
134+
authors: [Person] = [],
135+
content: Content? = nil,
136+
links: [Link] = [],
137+
categories: [Category] = [],
138+
contributors: [Person] = [],
139+
published: Date? = nil,
140+
rights: Text? = nil,
141+
source: Source? = nil,
142+
summary: Text? = nil
143+
) {
144+
self.init(
145+
id: id.iri,
146+
title: title,
147+
updated: updated,
148+
authors: authors,
149+
content: content,
150+
links: links,
151+
categories: categories,
152+
contributors: contributors,
153+
published: published,
154+
rights: rights,
155+
source: source,
156+
summary: summary
157+
)
158+
}
159+
111160
/// Creates a new entry without validation (for internal use, e.g. decoding)
112161
internal static func makeUnchecked(
113162
id: String,

Sources/RFC 4287/Feed.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,58 @@ extension RFC_4287 {
108108
self.subtitle = subtitle
109109
}
110110

111+
/// Creates a new feed with validation using IRI.Representable types (convenience)
112+
///
113+
/// Accepts any IRI.Representable types such as Foundation URL.
114+
///
115+
/// - Parameters:
116+
/// - id: Permanent, universally unique identifier (e.g., URL)
117+
/// - title: Human-readable title
118+
/// - updated: Timestamp of last modification
119+
/// - authors: Feed authors
120+
/// - entries: Feed entries
121+
/// - links: Associated links
122+
/// - categories: Feed categories
123+
/// - contributors: Feed contributors
124+
/// - generator: Generator information
125+
/// - icon: Icon IRI (e.g., URL)
126+
/// - logo: Logo IRI (e.g., URL)
127+
/// - rights: Rights information
128+
/// - subtitle: Feed subtitle
129+
///
130+
/// - Returns: A validated feed, or nil if validation fails
131+
public init?(
132+
id: any RFC_3987.IRI.Representable,
133+
title: Text,
134+
updated: Date,
135+
authors: [Person] = [],
136+
entries: [Entry] = [],
137+
links: [Link] = [],
138+
categories: [Category] = [],
139+
contributors: [Person] = [],
140+
generator: Generator? = nil,
141+
icon: (any RFC_3987.IRI.Representable)? = nil,
142+
logo: (any RFC_3987.IRI.Representable)? = nil,
143+
rights: Text? = nil,
144+
subtitle: Text? = nil
145+
) {
146+
self.init(
147+
id: id.iri,
148+
title: title,
149+
updated: updated,
150+
authors: authors,
151+
entries: entries,
152+
links: links,
153+
categories: categories,
154+
contributors: contributors,
155+
generator: generator,
156+
icon: icon?.iri,
157+
logo: logo?.iri,
158+
rights: rights,
159+
subtitle: subtitle
160+
)
161+
}
162+
111163
/// Creates a new feed without validation (for internal use, e.g. decoding)
112164
internal static func makeUnchecked(
113165
id: String,

Sources/RFC 4287/Generator.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,21 @@ extension RFC_4287 {
3030
self.uri = uri
3131
self.version = version
3232
}
33+
34+
/// Creates a new generator with IRI.Representable URI (convenience)
35+
///
36+
/// Accepts any IRI.Representable type such as Foundation URL.
37+
///
38+
/// - Parameters:
39+
/// - value: The generator name
40+
/// - uri: IRI for the generator (e.g., URL)
41+
/// - version: Version of the generator
42+
public init(
43+
value: String,
44+
uri: (any RFC_3987.IRI.Representable)?,
45+
version: String? = nil
46+
) {
47+
self.init(value: value, uri: uri?.iri, version: version)
48+
}
3349
}
3450
}

Sources/RFC 4287/Link.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,28 @@ extension RFC_4287 {
101101
self.length = length
102102
}
103103

104+
/// Creates a new link with IRI.Representable href (convenience)
105+
///
106+
/// Accepts any IRI.Representable type such as Foundation URL.
107+
///
108+
/// - Parameters:
109+
/// - href: The IRI of the resource (e.g., URL, RFC_3987.IRI)
110+
/// - rel: The link relation type
111+
/// - type: The media type
112+
/// - hreflang: The language
113+
/// - title: A title for the link
114+
/// - length: The length in bytes
115+
public init(
116+
href: any RFC_3987.IRI.Representable,
117+
rel: Relation? = nil,
118+
type: String? = nil,
119+
hreflang: String? = nil,
120+
title: String? = nil,
121+
length: Int? = nil
122+
) {
123+
self.init(href: href.iri, rel: rel, type: type, hreflang: hreflang, title: title, length: length)
124+
}
125+
104126
/// Returns true if this link should be treated as an "alternate" relation
105127
///
106128
/// Per RFC 4287 Section 4.2.7.2: If the rel attribute is not present,

Sources/RFC 4287/Person.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,22 @@ extension RFC_4287 {
3737
self.email = email
3838
}
3939

40+
/// Creates a new person construct with IRI.Representable URI (convenience)
41+
///
42+
/// Accepts any IRI.Representable type such as Foundation URL.
43+
///
44+
/// - Parameters:
45+
/// - name: The person's name
46+
/// - uri: An optional IRI-representable value (e.g., URL)
47+
/// - email: An optional email address (RFC 2822 AddrSpec)
48+
public init(
49+
name: String,
50+
uri: (any RFC_3987.IRI.Representable)?,
51+
email: RFC_2822.AddrSpec? = nil
52+
) {
53+
self.init(name: name, uri: uri?.iri, email: email)
54+
}
55+
4056
/// Creates a new person construct with string email (convenience)
4157
///
4258
/// - Parameters:

Sources/RFC 4287/Source.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,52 @@ extension RFC_4287 {
8484
self.title = title
8585
self.updated = updated
8686
}
87+
88+
/// Creates a new source construct with IRI.Representable types (convenience)
89+
///
90+
/// Accepts any IRI.Representable types such as Foundation URL.
91+
///
92+
/// - Parameters:
93+
/// - authors: Feed authors
94+
/// - categories: Feed categories
95+
/// - contributors: Feed contributors
96+
/// - generator: Generator information
97+
/// - icon: Icon IRI (e.g., URL)
98+
/// - id: Feed ID (e.g., URL)
99+
/// - links: Links
100+
/// - logo: Logo IRI (e.g., URL)
101+
/// - rights: Feed rights
102+
/// - subtitle: Feed subtitle
103+
/// - title: Feed title
104+
/// - updated: Last updated timestamp
105+
public init(
106+
authors: [Person] = [],
107+
categories: [Category] = [],
108+
contributors: [Person] = [],
109+
generator: Generator? = nil,
110+
icon: (any RFC_3987.IRI.Representable)? = nil,
111+
id: (any RFC_3987.IRI.Representable)? = nil,
112+
links: [Link] = [],
113+
logo: (any RFC_3987.IRI.Representable)? = nil,
114+
rights: Text? = nil,
115+
subtitle: Text? = nil,
116+
title: Text? = nil,
117+
updated: Date? = nil
118+
) {
119+
self.init(
120+
authors: authors,
121+
categories: categories,
122+
contributors: contributors,
123+
generator: generator,
124+
icon: icon?.iri,
125+
id: id?.iri,
126+
links: links,
127+
logo: logo?.iri,
128+
rights: rights,
129+
subtitle: subtitle,
130+
title: title,
131+
updated: updated
132+
)
133+
}
87134
}
88135
}

Tests/RFC 4287 Tests/PersonValidationTests.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,13 @@ struct PersonIRIIntegrationTests {
160160

161161
#expect(person.uri == "https://example.com/~user")
162162
}
163+
164+
@Test("Person with Foundation URL via IRI.Representable")
165+
func foundationURLSupport() throws {
166+
let url = URL(string: "https://example.com/~user")!
167+
let email = try RFC_2822.AddrSpec(localPart: "user", domain: "example.com")
168+
let person = RFC_4287.Person(name: "User", uri: url, email: email)
169+
170+
#expect(person.uri == "https://example.com/~user")
171+
}
163172
}

0 commit comments

Comments
 (0)