Skip to content

Commit c21f412

Browse files
authored
Add initial support for documenting HTTP/REST requests (#495)
rdar://101408429 rdar://101409112 rdar://101408868 rdar://101408667
1 parent 2bf078a commit c21f412

38 files changed

+1456
-23
lines changed

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftDocC/Coverage/DocumentationCoverageOptions.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ extension DocumentationCoverageOptions {
100100
internal static let typeSubscript = KindFilterOptions(rawValue: BitFlagRepresentation.typeSubscript.bitMask)
101101
internal static let globalVariable = KindFilterOptions(rawValue: BitFlagRepresentation.globalVariable.bitMask)
102102
internal static let dictionary = KindFilterOptions(rawValue: BitFlagRepresentation.dictionary.bitMask)
103+
internal static let httpRequest = KindFilterOptions(rawValue: BitFlagRepresentation.httpRequest.bitMask)
103104

104105
/// Mask with all valid/used bit flags set to 1.
105106
internal static let allSingleBitOptions: [KindFilterOptions] = {
@@ -174,6 +175,7 @@ extension DocumentationCoverageOptions.KindFilterOptions {
174175
case typeSubscript
175176
case globalVariable
176177
case dictionary
178+
case httpRequest
177179

178180
/// Parses given `String` to corresponding `BitFlagRepresentation` if possible. Returns `nil` if the given string does not specify a representable value.
179181
public init?(string: String) {
@@ -229,6 +231,8 @@ extension DocumentationCoverageOptions.KindFilterOptions {
229231
self = .globalVariable
230232
case .dictionary: // 21
231233
self = .dictionary
234+
case .httpRequest: // 22
235+
self = .httpRequest
232236
default:
233237
return nil
234238
}
@@ -281,6 +285,8 @@ extension DocumentationCoverageOptions.KindFilterOptions {
281285
return 1 << 20
282286
case .dictionary:
283287
return 1 << 21
288+
case .httpRequest:
289+
return 1 << 22
284290
}
285291
}
286292

@@ -337,6 +343,8 @@ extension DocumentationCoverageOptions.KindFilterOptions {
337343
return .globalVariable
338344
case .dictionary: // 21
339345
return .dictionary
346+
case .httpRequest: // 22
347+
return .httpRequest
340348
}
341349
}
342350

@@ -386,6 +394,8 @@ extension DocumentationCoverageOptions.KindFilterOptions {
386394
return "global-variable"
387395
case .dictionary: // 21
388396
return "dictionary"
397+
case .httpRequest: // 21
398+
return "http-request"
389399
}
390400
}
391401
/// A dictionary where keys are all valid argument strings and values are corresponding instances of ``BitFlagRepresentation``.
@@ -433,6 +443,8 @@ extension DocumentationCoverageOptions.KindFilterOptions {
433443
"global-variable": .globalVariable,
434444
// 21
435445
"dictionary": .dictionary,
446+
// 22
447+
"http-request": .httpRequest,
436448
]
437449

438450
}

Sources/SwiftDocC/Infrastructure/CoverageDataEntry.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ extension CoverageDataEntry {
249249
.extendedStructure,
250250
.extendedEnumeration,
251251
.extendedProtocol,
252+
.httpRequest,
252253
.unknownExtendedType:
253254
self = .types
254255
case .localVariable,
@@ -279,6 +280,7 @@ extension CoverageDataEntry {
279280
case `class`(memberStats: [InstanceMemberType: RatioStatistic])
280281
case structure(memberStats: [InstanceMemberType: RatioStatistic])
281282
case dictionary
283+
case httpRequest
282284
case enumeration(memberStats: [InstanceMemberType: RatioStatistic])
283285
case `protocol`(memberStats: [InstanceMemberType: RatioStatistic])
284286
case typeAlias
@@ -448,6 +450,7 @@ extension CoverageDataEntry.KindSpecificData {
448450
case structure
449451
case dictionary
450452
case enumeration
453+
case httpRequest
451454
case `protocol`
452455
case `operator`
453456
case typeAlias
@@ -482,6 +485,7 @@ extension CoverageDataEntry.KindSpecificData {
482485
.`structure`,
483486
.dictionary,
484487
.enumeration,
488+
.httpRequest,
485489
.protocol,
486490
.typeAlias,
487491
.instanceProperty,
@@ -514,6 +518,7 @@ extension CoverageDataEntry.KindSpecificData {
514518
case .instanceMethod,
515519
.initializer,
516520
.dictionary,
521+
.httpRequest,
517522
.typeAlias,
518523
.instanceProperty,
519524
.enumerationCase,
@@ -542,6 +547,8 @@ extension CoverageDataEntry.KindSpecificData {
542547
return .dictionary
543548
case .enumeration:
544549
return .enumeration
550+
case .httpRequest:
551+
return .httpRequest
545552
case .protocol:
546553
return .protocol
547554
case .typeAlias:
@@ -634,6 +641,8 @@ extension CoverageDataEntry.KindSpecificData {
634641

635642
case .dictionary:
636643
self = .dictionary
644+
case .httpRequest:
645+
self = .httpRequest
637646
case .typeAlias:
638647
self = .typeAlias
639648
case .instanceProperty:
@@ -667,6 +676,7 @@ extension CoverageDataEntry.KindSpecificData {
667676

668677
case .typeAlias,
669678
.dictionary,
679+
.httpRequest,
670680
.instanceProperty,
671681
.variable,
672682
.framework,

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,8 +1461,8 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
14611461
for (selector, relationships) in combinedRelationships {
14621462
// Build relationships in the completed graph
14631463
buildRelationships(relationships, selector: selector, bundle: bundle, engine: diagnosticEngine)
1464-
// Merge dictionary keys into target dictionaries
1465-
populateDictionaryKeys(from: relationships, selector: selector)
1464+
// Merge into target symbols the member symbols that get rendered on the same page as target.
1465+
populateOnPageMemberRelationships(from: relationships, selector: selector)
14661466
}
14671467

14681468
// Index references
@@ -1553,21 +1553,48 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
15531553
}
15541554

15551555
/// Identifies all the dictionary keys and records them in the appropriate target dictionaries.
1556-
private func populateDictionaryKeys(
1556+
private func populateOnPageMemberRelationships(
15571557
from relationships: Set<SymbolGraph.Relationship>,
15581558
selector: UnifiedSymbolGraph.Selector
15591559
) {
1560-
var keysByTarget = [String:[DictionaryKey]]()
1560+
var keysByTarget = [String: [DictionaryKey]]()
1561+
var parametersByTarget = [String: [HTTPParameter]]()
1562+
var bodyByTarget = [String: HTTPBody]()
1563+
var responsesByTarget = [String: [HTTPResponse]]()
15611564

15621565
for edge in relationships {
15631566
if edge.kind == .memberOf || edge.kind == .optionalMemberOf {
1564-
if let source = symbolIndex[edge.source], let target = symbolIndex[edge.target], let keySymbol = source.symbol,
1565-
source.kind == .dictionaryKey && target.kind == .dictionary {
1566-
let dictionaryKey = DictionaryKey(name: keySymbol.title, contents: [], symbol: keySymbol, required: (edge.kind == .memberOf))
1567-
if keysByTarget[edge.target] == nil {
1568-
keysByTarget[edge.target] = [dictionaryKey]
1569-
} else {
1570-
keysByTarget[edge.target]?.append(dictionaryKey)
1567+
if let source = symbolIndex[edge.source], let _ = symbolIndex[edge.target], let sourceSymbol = source.symbol {
1568+
switch source.kind {
1569+
case .dictionaryKey:
1570+
let dictionaryKey = DictionaryKey(name: sourceSymbol.title, contents: [], symbol: sourceSymbol, required: (edge.kind == .memberOf))
1571+
if keysByTarget[edge.target] == nil {
1572+
keysByTarget[edge.target] = [dictionaryKey]
1573+
} else {
1574+
keysByTarget[edge.target]?.append(dictionaryKey)
1575+
}
1576+
case .httpParameter:
1577+
let parameter = HTTPParameter(name: sourceSymbol.title, source: (sourceSymbol.httpParameterSource ?? "query"), contents: [], symbol: sourceSymbol, required: (edge.kind == .memberOf))
1578+
if parametersByTarget[edge.target] == nil {
1579+
parametersByTarget[edge.target] = [parameter]
1580+
} else {
1581+
parametersByTarget[edge.target]?.append(parameter)
1582+
}
1583+
case .httpBody:
1584+
let body = HTTPBody(mediaType: sourceSymbol.httpMediaType ?? "application/json", contents: [], symbol: sourceSymbol)
1585+
bodyByTarget[edge.target] = body
1586+
case .httpResponse:
1587+
let statusParts = sourceSymbol.title.split(separator: " ", maxSplits: 1)
1588+
let statusCode = UInt(statusParts[0]) ?? 0
1589+
let reason = statusParts.count > 1 ? String(statusParts[1]) : nil
1590+
let response = HTTPResponse(statusCode: statusCode, reason: reason, mediaType: sourceSymbol.httpMediaType ?? "application/json", contents: [], symbol: sourceSymbol)
1591+
if responsesByTarget[edge.target] == nil {
1592+
responsesByTarget[edge.target] = [response]
1593+
} else {
1594+
responsesByTarget[edge.target]?.append(response)
1595+
}
1596+
default:
1597+
continue
15711598
}
15721599
}
15731600
}
@@ -1586,6 +1613,47 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
15861613
}
15871614
}
15881615
}
1616+
1617+
// Merge in all the parameters for each target into their section variants.
1618+
parametersByTarget.forEach { targetIdentifier, parameters in
1619+
let target = symbolIndex[targetIdentifier]
1620+
if let semantic = target?.semantic as? Symbol {
1621+
let parameters = parameters.sorted { $0.name < $1.name }
1622+
let trait = DocumentationDataVariantsTrait(for: selector)
1623+
if semantic.httpParametersSectionVariants[trait] == nil {
1624+
semantic.httpParametersSectionVariants[trait] = HTTPParametersSection(parameters: parameters)
1625+
} else {
1626+
semantic.httpParametersSectionVariants[trait]?.mergeParameters(parameters)
1627+
}
1628+
}
1629+
}
1630+
1631+
// Merge in the body for each target into their section variants.
1632+
bodyByTarget.forEach { targetIdentifier, body in
1633+
let target = symbolIndex[targetIdentifier]
1634+
if let semantic = target?.semantic as? Symbol {
1635+
let trait = DocumentationDataVariantsTrait(for: selector)
1636+
if semantic.httpBodySectionVariants[trait] == nil {
1637+
semantic.httpBodySectionVariants[trait] = HTTPBodySection(body: body)
1638+
} else {
1639+
semantic.httpBodySectionVariants[trait]?.mergeBody(body)
1640+
}
1641+
}
1642+
}
1643+
1644+
// Merge in all the responses for each target into their section variants.
1645+
responsesByTarget.forEach { targetIdentifier, responses in
1646+
let target = symbolIndex[targetIdentifier]
1647+
if let semantic = target?.semantic as? Symbol {
1648+
let responses = responses.sorted { $0.statusCode < $1.statusCode }
1649+
let trait = DocumentationDataVariantsTrait(for: selector)
1650+
if semantic.httpResponsesSectionVariants[trait] == nil {
1651+
semantic.httpResponsesSectionVariants[trait] = HTTPResponsesSection(responses: responses)
1652+
} else {
1653+
semantic.httpResponsesSectionVariants[trait]?.mergeResponses(responses)
1654+
}
1655+
}
1656+
}
15891657
}
15901658

15911659
/// Look up and add symbols that are _referenced_ in the symbol graph but don't exist in the symbol graph, using an `externalSymbolResolver` (if not `nil`).

Sources/SwiftDocC/Infrastructure/Extensions/KindIdentifier+Curation.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ extension SymbolGraph.Symbol.KindIdentifier {
1414
/// The kinds of symbols that should not generate pages in the documentation hierarchy.
1515
static let noPageKinds: [SymbolGraph.Symbol.KindIdentifier] = [
1616
.dictionaryKey,
17+
.httpBody,
18+
.httpResponse,
19+
.httpParameter,
1720
.module,
1821
.snippet,
1922
.snippetGroup

Sources/SwiftDocC/Infrastructure/External Data/ExternalSymbolResolver+SymbolKind.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ extension ExternalSymbolResolver {
5656
symbolKind = .dictionary
5757
case .dictionaryKey:
5858
symbolKind = .dictionaryKey
59+
case .httpRequest:
60+
symbolKind = .httpRequest
61+
case .httpResponse:
62+
symbolKind = .httpResponse
63+
case .httpBody:
64+
symbolKind = .httpBody
65+
case .httpParameter:
66+
symbolKind = .httpParameter
5967
case .instanceSubscript:
6068
symbolKind = .subscript
6169
case .typeMethod:

Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,10 @@ extension OutOfProcessReferenceResolver {
983983
returnsSectionVariants: .empty,
984984
parametersSectionVariants: .empty,
985985
dictionaryKeysSectionVariants: .empty,
986+
httpEndpointSectionVariants: .empty,
987+
httpBodySectionVariants: .empty,
988+
httpParametersSectionVariants: .empty,
989+
httpResponsesSectionVariants: .empty,
986990
redirectsVariants: .empty
987991
)
988992
}

Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ extension AutomaticCuration {
199199
case .dictionary: return "Dictionaries"
200200
case .extension: return "Extensions"
201201
case .`func`: return "Functions"
202+
case .httpRequest: return "Endpoints"
202203
case .`operator`: return "Operators"
203204
case .`init`: return "Initializers"
204205
case .ivar: return "Instance Variables"
@@ -232,6 +233,7 @@ extension AutomaticCuration {
232233
.`class`,
233234
.`protocol`,
234235
.`struct`,
236+
.`httpRequest`,
235237
.`dictionary`,
236238
.`var`,
237239
.`func`,

Sources/SwiftDocC/Model/DocumentationNode.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ public struct DocumentationNode {
209209
mixins[SymbolGraph.Symbol.Availability.mixinKey] as? SymbolGraph.Symbol.Availability
210210
}
211211

212+
let endpointVariants = DocumentationDataVariants(
213+
symbolData: unifiedSymbol.mixins,
214+
platformName: platformName
215+
) { mixins -> HTTPEndpointSection? in
216+
if let endpoint = mixins[SymbolGraph.Symbol.HTTP.Endpoint.mixinKey] as? SymbolGraph.Symbol.HTTP.Endpoint {
217+
return HTTPEndpointSection(endpoint: endpoint)
218+
}
219+
return nil
220+
}
221+
212222
var languages = Set([reference.sourceLanguage])
213223
var operatingSystemName = platformName.map({ Set([$0]) }) ?? []
214224

@@ -285,6 +295,10 @@ public struct DocumentationNode {
285295
returnsSectionVariants: .empty,
286296
parametersSectionVariants: .empty,
287297
dictionaryKeysSectionVariants: .empty,
298+
httpEndpointSectionVariants: endpointVariants,
299+
httpBodySectionVariants: .empty,
300+
httpParametersSectionVariants: .empty,
301+
httpResponsesSectionVariants: .empty,
288302
redirectsVariants: .empty,
289303
crossImportOverlayModule: moduleData.bystanders.map({ (moduleData.name, $0) })
290304
)
@@ -484,6 +498,10 @@ public struct DocumentationNode {
484498
case .`enum`: return .enumeration
485499
case .`case`: return .enumerationCase
486500
case .`func`: return .function
501+
case .httpRequest: return .httpRequest
502+
case .httpParameter: return .httpParameter
503+
case .httpBody: return .httpBody
504+
case .httpResponse: return .httpResponse
487505
case .`operator`: return .operator
488506
case .`init`: return .initializer
489507
case .ivar: return .instanceVariable
@@ -603,6 +621,10 @@ public struct DocumentationNode {
603621
returnsSectionVariants: .init(swiftVariant: markupModel.discussionTags.flatMap({ $0.returns.isEmpty ? nil : ReturnsSection(content: $0.returns[0].contents) })),
604622
parametersSectionVariants: .init(swiftVariant: markupModel.discussionTags.flatMap({ $0.parameters.isEmpty ? nil : ParametersSection(parameters: $0.parameters) })),
605623
dictionaryKeysSectionVariants: .init(swiftVariant: markupModel.discussionTags.flatMap({ $0.dictionaryKeys.isEmpty ? nil : DictionaryKeysSection(dictionaryKeys: $0.dictionaryKeys) })),
624+
httpEndpointSectionVariants: .empty,
625+
httpBodySectionVariants: .empty,
626+
httpParametersSectionVariants: .empty,
627+
httpResponsesSectionVariants: .empty,
606628
redirectsVariants: .init(swiftVariant: article?.redirects)
607629
)
608630

0 commit comments

Comments
 (0)