Skip to content

Commit 94f4488

Browse files
committed
Add models for JSON dictionaries
Add models for coding JSON dictionaries for REST APIs. rdar://101407961
1 parent b45d1f2 commit 94f4488

File tree

12 files changed

+468
-2
lines changed

12 files changed

+468
-2
lines changed

Sources/SymbolKit/Mixin/Mixin.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -88,3 +88,33 @@ extension MixinCodingInformation {
8888
})
8989
}
9090
}
91+
92+
/// Special-cased ``Mixin`` protocol wherein the coded value is held in a single `value` property.
93+
///
94+
/// The associated type, ``ValueType``, specifies the data type of the ``value`` property.
95+
/// It can be a scalar value, such as `Int` or `String`, an array of values, or another object.
96+
/// There is little value in wrapping an object—the object should be made the mixin directly, if possible.
97+
///
98+
/// The protocol provides default implementations of the `Codable` methods
99+
/// `Decodable/init(from:)` and `Encodable/encode(to:)` that
100+
/// read and write the `value` property.
101+
public protocol SingleValueMixin: Mixin {
102+
/// The type of the wrapped value.
103+
associatedtype ValueType: Codable
104+
/// The property holded the encoded/decoded value.
105+
var value: ValueType { get set }
106+
/// The constructor for the concrete type, taking just the wrapped value as its argument.
107+
init(_: ValueType)
108+
}
109+
110+
extension SingleValueMixin {
111+
public init(from decoder: Decoder) throws {
112+
let container = try decoder.singleValueContainer()
113+
self.init(try container.decode(ValueType.self))
114+
}
115+
116+
public func encode(to encoder: Encoder) throws {
117+
var container = encoder.singleValueContainer()
118+
try container.encode(self.value)
119+
}
120+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2023 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Foundation
12+
13+
extension SymbolGraph {
14+
/// Enumeration that can hold either a single boolean, integer, floating point, or string value or a null value.
15+
///
16+
/// This enumeration is used when a field within the symbol graph can hold a different value type
17+
/// depending on the context. One such case would be the default value of a dictionary property, whose
18+
/// value depends on the data type of the property (eg, a string or integer).
19+
public enum AnyScalar: Codable, Equatable {
20+
case null
21+
case boolean(Bool)
22+
case integer(Int)
23+
case float(Double)
24+
case string(String)
25+
26+
// This empty-marker case is here because non-frozen enums are only available when Library
27+
// Evolution is enabled, which is not available to Swift Packages without unsafe flags
28+
// (rdar://78773361). This can be removed once that is available and applied to SymbolKit
29+
// (rdar://89033233).
30+
@available(*, deprecated, message: "this enum is nonfrozen and may be expanded in the future; please add a `default` case instead of matching this one")
31+
case _nonfrozenEnum_useDefaultCase
32+
33+
public func encode(to encoder : Encoder) throws {
34+
var container = encoder.singleValueContainer()
35+
switch self {
36+
case .null:
37+
try container.encodeNil()
38+
case .boolean(let b):
39+
try container.encode(b)
40+
case .integer(let i):
41+
try container.encode(i)
42+
case .float(let d):
43+
try container.encode(d)
44+
case .string(let s):
45+
try container.encode(s)
46+
case ._nonfrozenEnum_useDefaultCase:
47+
fatalError("_nonfrozenEnum_useDefaultCase is not a supported case in AnyScalar")
48+
}
49+
}
50+
51+
public init(from decoder: Decoder) throws {
52+
let singleValue = try decoder.singleValueContainer()
53+
if singleValue.decodeNil() {
54+
self = .null
55+
} else if let value = try? singleValue.decode(Swift.Bool.self) {
56+
self = .boolean(value)
57+
} else if let value = try? singleValue.decode(Swift.Int.self) {
58+
self = .integer(value)
59+
} else if let value = try? singleValue.decode(Swift.Double.self) {
60+
self = .float(value)
61+
} else if let value = try? singleValue.decode(Swift.String.self) {
62+
self = .string(value)
63+
} else {
64+
throw DecodingError.typeMismatch(
65+
AnyScalar.self,
66+
DecodingError.Context(
67+
codingPath: decoder.codingPath,
68+
debugDescription: "Unable to parse scalar value"
69+
)
70+
)
71+
}
72+
}
73+
}
74+
75+
/// Enumeration that can hold either an integer or floating point value.
76+
///
77+
/// This enumeration is used when a field within the symbol graph can hold a different value type
78+
/// depending on the context. One such case would be the minimum value of a dictionary property, whose
79+
/// value depends on the numerical type of the property (eg, an integer or floating point).
80+
public enum AnyNumber: Codable, Equatable {
81+
case integer(Int)
82+
case float(Double)
83+
84+
// This empty-marker case is here because non-frozen enums are only available when Library
85+
// Evolution is enabled, which is not available to Swift Packages without unsafe flags
86+
// (rdar://78773361). This can be removed once that is available and applied to SymbolKit
87+
// (rdar://89033233).
88+
@available(*, deprecated, message: "this enum is nonfrozen and may be expanded in the future; please add a `default` case instead of matching this one")
89+
case _nonfrozenEnum_useDefaultCase
90+
91+
public func encode(to encoder : Encoder) throws {
92+
var container = encoder.singleValueContainer()
93+
switch self {
94+
case .integer(let i):
95+
try container.encode(i)
96+
case .float(let d):
97+
try container.encode(d)
98+
case ._nonfrozenEnum_useDefaultCase:
99+
fatalError("_nonfrozenEnum_useDefaultCase is not a supported case in AnyNumber")
100+
}
101+
102+
}
103+
104+
public init(from decoder: Decoder) throws {
105+
let container = try decoder.singleValueContainer()
106+
if let value = try? container.decode(Swift.Int.self) {
107+
self = .integer(value)
108+
} else if let value = try? container.decode(Swift.Double.self) {
109+
self = .float(value)
110+
} else {
111+
throw DecodingError.typeMismatch(AnyScalar.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unable to parse number value"))
112+
}
113+
}
114+
}
115+
}
116+
117+
extension String {
118+
public init(_ value: SymbolGraph.AnyScalar) {
119+
switch value {
120+
case .null:
121+
self = "null"
122+
case .boolean(let b):
123+
self = String(b)
124+
case .integer(let i):
125+
self = String(i)
126+
case .float(let d):
127+
self = String(d)
128+
case .string(let s):
129+
self = s
130+
case ._nonfrozenEnum_useDefaultCase:
131+
fatalError("_nonfrozenEnum_useDefaultCase is not a supported case in AnyScalar")
132+
}
133+
}
134+
135+
public init(_ value: SymbolGraph.AnyNumber) {
136+
switch value {
137+
case .integer(let i):
138+
self = String(i)
139+
case .float(let d):
140+
self = String(d)
141+
case ._nonfrozenEnum_useDefaultCase:
142+
fatalError("_nonfrozenEnum_useDefaultCase is not a supported case in AnyNumber")
143+
}
144+
}
145+
}

Sources/SymbolKit/SymbolGraph/Relationship/RelationshipKind.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ extension SymbolGraph.Relationship {
3030
*/
3131
public static let memberOf = Kind(rawValue: "memberOf")
3232

33+
/**
34+
A symbol `A` is an optional member of another symbol `B`.
35+
36+
For example, a key for a dictionary could be
37+
a member of that dictionary, but the key is not guaranteed
38+
to be present.
39+
40+
The implied inverse of this relationship is that
41+
symbol `B` is the owner of a member symbol `A`.
42+
*/
43+
public static let optionalMemberOf = Kind(rawValue: "optionalMemberOf")
44+
3345
/**
3446
A symbol `A` conforms to an interface/protocol symbol `B`.
3547

Sources/SymbolKit/SymbolGraph/Symbol/Availability/Availability.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,9 @@ extension SymbolGraph.Symbol {
5353
self.availability = availability
5454
}
5555
}
56+
57+
/// Convenience method to fetch the availability mixin value.
58+
public var availability: [Availability.AvailabilityItem]? {
59+
(mixins[Availability.mixinKey] as? Availability)?.availability
60+
}
5661
}

Sources/SymbolKit/SymbolGraph/Symbol/DeclarationFragments/DeclarationFragments.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,10 @@ extension SymbolGraph.Symbol {
154154
try container.encode(declarationFragments)
155155
}
156156
}
157+
158+
//// Convenience method to fetch the declaration fragment mixin value.
159+
public var declarationFragments : [DeclarationFragments.Fragment]? {
160+
(mixins[DeclarationFragments.mixinKey] as? DeclarationFragments)?
161+
.declarationFragments
162+
}
157163
}

Sources/SymbolKit/SymbolGraph/Symbol/KindIdentifier.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ extension SymbolGraph.Symbol {
7272
public static let module = KindIdentifier(rawValue: "module")
7373

7474
public static let `extension` = KindIdentifier(rawValue: "extension")
75+
76+
public static let dictionary = KindIdentifier(rawValue: "dictionary")
77+
78+
public static let dictionaryKey = KindIdentifier(rawValue: "dictionaryKey")
7579

7680
/// A string that uniquely identifies the symbol kind.
7781
///
@@ -109,6 +113,8 @@ extension SymbolGraph.Symbol {
109113
Self.var.rawValue: .var,
110114
Self.module.rawValue: .module,
111115
Self.extension.rawValue: .extension,
116+
Self.dictionary.rawValue: .dictionary,
117+
Self.dictionaryKey.rawValue: .dictionaryKey,
112118
]
113119

114120
/// Register custom ``SymbolGraph/Symbol/KindIdentifier``s so they can be parsed correctly and

Sources/SymbolKit/SymbolGraph/Symbol/Mutability.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,9 @@ extension SymbolGraph.Symbol {
4040
try container.encode(isReadOnly)
4141
}
4242
}
43+
44+
/// Convenience method to fetch the mutability mixin value.
45+
public var isReadOnly : Bool? {
46+
(mixins[Mutability.mixinKey] as? Mutability)?.isReadOnly
47+
}
4348
}

Sources/SymbolKit/SymbolGraph/Symbol/Symbol.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,14 @@ extension SymbolGraph.Symbol {
224224
static let functionSignature = FunctionSignature.symbolCodingInfo
225225
static let spi = SPI.symbolCodingInfo
226226
static let snippet = Snippet.symbolCodingInfo
227+
static let minimum = Minimum.symbolCodingInfo
228+
static let maximum = Maximum.symbolCodingInfo
229+
static let minimumExclusive = MinimumExclusive.symbolCodingInfo
230+
static let maximumExclusive = MaximumExclusive.symbolCodingInfo
231+
static let minimumLength = MinimumLength.symbolCodingInfo
232+
static let maximumLength = MaximumLength.symbolCodingInfo
233+
static let allowedValues = AllowedValues.symbolCodingInfo
234+
static let defaultValue = DefaultValue.symbolCodingInfo
227235

228236
static let mixinCodingInfo: [String: SymbolMixinCodingInfo] = [
229237
CodingKeys.availability.codingKey.stringValue: Self.availability,
@@ -236,6 +244,14 @@ extension SymbolGraph.Symbol {
236244
CodingKeys.functionSignature.codingKey.stringValue: Self.functionSignature,
237245
CodingKeys.spi.codingKey.stringValue: Self.spi,
238246
CodingKeys.snippet.codingKey.stringValue: Self.snippet,
247+
CodingKeys.minimum.codingKey.stringValue: Self.minimum,
248+
CodingKeys.maximum.codingKey.stringValue: Self.maximum,
249+
CodingKeys.minimumExclusive.codingKey.stringValue: Self.minimumExclusive,
250+
CodingKeys.maximumExclusive.codingKey.stringValue: Self.maximumExclusive,
251+
CodingKeys.minimumLength.codingKey.stringValue: Self.minimumLength,
252+
CodingKeys.maximumLength.codingKey.stringValue: Self.maximumLength,
253+
CodingKeys.allowedValues.codingKey.stringValue: Self.allowedValues,
254+
CodingKeys.defaultValue.codingKey.stringValue: Self.defaultValue,
239255
]
240256

241257
static func == (lhs: SymbolGraph.Symbol.CodingKeys, rhs: SymbolGraph.Symbol.CodingKeys) -> Bool {

0 commit comments

Comments
 (0)