diff --git a/Packages/Infrastructure/Sources/Public/Types/StructuredData.swift b/Packages/Infrastructure/Sources/Public/Types/StructuredData.swift index 5e5addb..fb38c4b 100644 --- a/Packages/Infrastructure/Sources/Public/Types/StructuredData.swift +++ b/Packages/Infrastructure/Sources/Public/Types/StructuredData.swift @@ -46,11 +46,17 @@ public extension StructuredData { /// - Parameters: /// - name: the name of the organization and the site. /// - url: the absolute URL the site is served at. + /// - alternateName: the name the organization is also known by, or `nil` (the default) to omit its property. + /// - description: what the organization does, or `nil` (the default) to omit its property. + /// - areaServed: the area the organization serves, or `nil` (the default) to omit its property. /// - logo: the absolute URL of the organization's logo, or `nil` (the default) to omit its property. /// - profiles: the absolute URLs of the organization's public profiles, or empty (the default) to omit their property. init( name: String, url: String, + alternateName: String? = nil, + description: String? = nil, + areaServed: String? = nil, logo: String? = nil, profiles: [String] = [] ) { @@ -61,6 +67,18 @@ public extension StructuredData { .init(.url, value: .string(url)), ] + if let alternateName { + organization.append(.init(.alternateName, value: .string(alternateName))) + } + + if let description { + organization.append(.init(.description, value: .string(description))) + } + + if let areaServed { + organization.append(.init(.areaServed, value: .string(areaServed))) + } + if let logo { organization.append(.init(.logo, value:.string(logo))) } diff --git a/Packages/Infrastructure/Sources/Public/Types/StructuredData/StructuredDataProperty.swift b/Packages/Infrastructure/Sources/Public/Types/StructuredData/StructuredDataProperty.swift index fb52cb0..2fd7044 100644 --- a/Packages/Infrastructure/Sources/Public/Types/StructuredData/StructuredDataProperty.swift +++ b/Packages/Infrastructure/Sources/Public/Types/StructuredData/StructuredDataProperty.swift @@ -69,6 +69,12 @@ extension StructuredData.Property { // MARK: - Constants public extension StructuredData.Property.Name { + /// The name the thing a node describes is also known by. + static let alternateName: Self = "alternateName" + /// The area an organization serves. + static let areaServed: Self = "areaServed" + /// What the thing a node describes is or does. + static let description: Self = "description" /// The absolute URL of an organization's logo. static let logo: Self = "logo" /// The name of the thing a node describes. diff --git a/Packages/Infrastructure/Sources/Public/Types/StructuredData/StructuredDataValue.swift b/Packages/Infrastructure/Sources/Public/Types/StructuredData/StructuredDataValue.swift index 4ee5324..06b8a31 100644 --- a/Packages/Infrastructure/Sources/Public/Types/StructuredData/StructuredDataValue.swift +++ b/Packages/Infrastructure/Sources/Public/Types/StructuredData/StructuredDataValue.swift @@ -1,5 +1,5 @@ extension StructuredData { - /// A value of a ``Property``: a string, a list, a nested node, or a reference to another node. + /// A value of a ``Property``: a string, a number, a list, a nested node, or a reference to another node. /// /// Every string a value renders is escaped as a JSON literal with `<` escaped as well, so a value can never close the `script` /// tag embedding the payload it renders into. @@ -8,6 +8,8 @@ extension StructuredData { case array([Value]) /// A nested node, e.g. the place a schema.org event is located at. case node(Node) + /// A whole number, e.g. a list item's position, rendered unquoted so it reads as a number rather than as text. + case number(Int) /// A reference to the ``Node/id`` of another node in the graph, rendered as an `@id` object. case reference(String) /// A string value. @@ -28,6 +30,8 @@ extension StructuredData.Value { "[\(values.map(\.fragment).joined(separator: .Separator.comma))]" case .node(let node): node.fragment + case .number(let number): + String(number) case .reference(let id): #"{"@id":\#(Self.literal(id))}"# case .string(let string): diff --git a/Packages/Infrastructure/Tests/Cases/Public/Types/StructuredDataTests.swift b/Packages/Infrastructure/Tests/Cases/Public/Types/StructuredDataTests.swift index f0bfcb3..131ead0 100644 --- a/Packages/Infrastructure/Tests/Cases/Public/Types/StructuredDataTests.swift +++ b/Packages/Infrastructure/Tests/Cases/Public/Types/StructuredDataTests.swift @@ -30,6 +30,21 @@ struct StructuredDataTests { ) } + @Test + func `carries the organization's alternate name, description, and area in that order`() { + let data = StructuredData( + name: "A Site", + url: "https://site.example/", + alternateName: "The Site", + description: "What the site is.", + areaServed: "A City" + ) + + #expect(data.payload.contains( + #""name":"A Site","url":"https://site.example/","alternateName":"The Site","description":"What the site is.","areaServed":"A City""# + )) + } + @Test func `omits the properties of the facts minimal data does not carry`() { let data = StructuredData( @@ -120,6 +135,9 @@ struct StructuredDataTests { #expect(StructuredData.Value.array([.string("A"), .string("B")]).fragment == #"["A","B"]"#) #expect(StructuredData.Value.reference("https://site.example/#organization").fragment == #"{"@id":"https://site.example/#organization"}"#) #expect(StructuredData.Value.node(.init(type: "Place", properties: [])).fragment == #"{"@type":"Place"}"#) + // Unquoted, or a consumer reads the position of a list item as text and cannot order by it. + #expect(StructuredData.Value.number(1).fragment == "1") + #expect(StructuredData.Value.number(-3).fragment == "-3") } @Test