-
Notifications
You must be signed in to change notification settings - Fork 161
Expand file tree
/
Copy pathYamsParser.swift
More file actions
149 lines (138 loc) · 6.77 KB
/
YamsParser.swift
File metadata and controls
149 lines (138 loc) · 6.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
import OpenAPIKit
import OpenAPIKit30
import OpenAPIKitCompat
import Yams
/// A parser that uses the Yams library to parse the provided
/// raw file into an OpenAPI document.
public struct YamsParser: ParserProtocol {
/// Extracts the top-level keys from a YAML string.
///
/// - Parameter yamlString: The YAML string from which to extract keys.
/// - Returns: An array of top-level keys as strings.
/// - Throws: An error if there are any issues with parsing the YAML string.
public static func extractTopLevelKeys(fromYAMLString yamlString: String) throws -> [String] {
var yamlKeys: [String] = []
let parser = try Parser(yaml: yamlString)
if let rootNode = try parser.singleRoot(), case let .mapping(mapping) = rootNode {
for (key, _) in mapping { yamlKeys.append(key.string ?? "") }
}
return yamlKeys
}
/// Parses a YAML file as an OpenAPI document.
///
/// This function supports documents following any of the following OpenAPI Specifications:
/// - 3.0.0, 3.0.1, 3.0.2, 3.0.3
/// - 3.1.0
///
/// - Parameters
/// - input: The file contents of the OpenAPI document.
/// - diagnostics: A diagnostics collector used for emiting parsing warnings and errors.
/// - Returns: Parsed OpenAPI document.
/// - Throws: If the OpenAPI document cannot be parsed.
/// Note that errors are also emited using the diagnostics collector.
public static func parseOpenAPIDocument(_ input: InMemoryInputFile, diagnostics: any DiagnosticCollector) throws
-> OpenAPIKit.OpenAPI.Document
{
let decoder = YAMLDecoder()
let openapiData = input.contents
struct OpenAPIVersionedDocument: Decodable { var openapi: String? }
let versionedDocument: OpenAPIVersionedDocument
do {
versionedDocument = try decoder.decode(OpenAPIVersionedDocument.self, from: openapiData)
} catch DecodingError.dataCorrupted(let errorContext) {
try checkParsingError(context: errorContext, input: input)
throw DecodingError.dataCorrupted(errorContext)
}
guard let openAPIVersion = versionedDocument.openapi else {
throw Diagnostic.openAPIMissingVersionError(location: .init(filePath: input.absolutePath.path))
}
do {
let document: OpenAPIKit.OpenAPI.Document
switch openAPIVersion {
case "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4":
let openAPI30Document = try decoder.decode(OpenAPIKit30.OpenAPI.Document.self, from: input.contents)
document = openAPI30Document.convert(to: .v3_1_0)
case "3.1.0", "3.1.1", "3.1.2":
document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents)
case "3.2.0":
document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents)
default:
throw Diagnostic.openAPIVersionError(
versionString: "openapi: \(openAPIVersion)",
location: .init(filePath: input.absolutePath.path)
)
}
return document
} catch DecodingError.dataCorrupted(let errorContext) {
try checkParsingError(context: errorContext, input: input)
throw DecodingError.dataCorrupted(errorContext)
}
}
func parseOpenAPI(_ input: InMemoryInputFile, config: Config, diagnostics: any DiagnosticCollector) throws
-> ParsedOpenAPIRepresentation
{ try Self.parseOpenAPIDocument(input, diagnostics: diagnostics) }
/// Detects specific YAML parsing errors to throw nicely formatted diagnostics for IDEs.
///
/// - Parameters:
/// - context: The decoding error context that triggered the parsing error.
/// - input: The input file being worked on when the parsing error was triggered.
/// - Throws: Throws a `Diagnostic` if the decoding error is a common parsing error.
private static func checkParsingError(context: DecodingError.Context, input: InMemoryInputFile) throws {
if let yamlError = context.underlyingError as? YamlError {
if case .parser(let yamlContext, let yamlProblem, let yamlMark, _) = yamlError {
throw Diagnostic.error(
message: "\(yamlProblem) \(yamlContext?.description ?? "")",
location: .init(filePath: input.absolutePath.path, lineNumber: yamlMark.line - 1)
)
} else if case .scanner(let yamlContext, let yamlProblem, let yamlMark, _) = yamlError {
throw Diagnostic.error(
message: "\(yamlProblem) \(yamlContext?.description ?? "")",
location: .init(filePath: input.absolutePath.path, lineNumber: yamlMark.line - 1)
)
}
} else if let openAPIError = context.underlyingError as? (any OpenAPIError) {
throw Diagnostic.error(
message: openAPIError.localizedDescription,
location: .init(filePath: input.absolutePath.path)
)
}
}
}
extension Diagnostic {
/// Use when the document is an unsupported version.
/// - Parameters:
/// - versionString: The OpenAPI version number that was parsed from the document.
/// - location: Describes the input file being worked on when the error occurred.
/// - Returns: An error diagnostic.
static func openAPIVersionError(versionString: String, location: Location) -> Diagnostic {
error(
message:
"Unsupported document version: \(versionString). Please provide a document with OpenAPI versions in the 3.0.x, 3.1.x, or 3.2.x sets.",
location: location
)
}
/// Use when the YAML document is completely missing the `openapi` version key.
/// - Parameter location: Describes the input file being worked on when the error occurred
/// - Returns: An error diagnostic.
static func openAPIMissingVersionError(location: Location) -> Diagnostic {
error(
message:
"No key named openapi found. Please provide a valid OpenAPI document with OpenAPI versions in the 3.0.x, 3.1.x, or 3.2.x sets.",
location: location
)
}
}