From 53e5cb9b18222f66cb8d6fb684d7383e705e0936 Mon Sep 17 00:00:00 2001 From: pdwilson12 Date: Fri, 31 Mar 2023 15:54:36 -0700 Subject: [PATCH] Add new `typeDetails` symbol mixin to hold type details of weakly-typed dictionary keys and HTTP parameters. (#56) Co-authored-by: Peter Wilson --- .../SymbolKit/SymbolGraph/Symbol/Symbol.swift | 2 + .../SymbolGraph/Symbol/ValueConstraints.swift | 49 +++++++++++++++++++ .../Symbol/ValueConstraintsTests.swift | 10 ++++ 3 files changed, 61 insertions(+) diff --git a/Sources/SymbolKit/SymbolGraph/Symbol/Symbol.swift b/Sources/SymbolKit/SymbolGraph/Symbol/Symbol.swift index 6dfc463..5060d0c 100644 --- a/Sources/SymbolKit/SymbolGraph/Symbol/Symbol.swift +++ b/Sources/SymbolKit/SymbolGraph/Symbol/Symbol.swift @@ -232,6 +232,7 @@ extension SymbolGraph.Symbol { static let maximumLength = MaximumLength.symbolCodingInfo static let allowedValues = AllowedValues.symbolCodingInfo static let defaultValue = DefaultValue.symbolCodingInfo + static let typeDetails = TypeDetails.symbolCodingInfo static let httpEndpoint = HTTP.Endpoint.symbolCodingInfo static let httpParameterSource = HTTP.ParameterSource.symbolCodingInfo static let httpMediaType = HTTP.MediaType.symbolCodingInfo @@ -255,6 +256,7 @@ extension SymbolGraph.Symbol { CodingKeys.maximumLength.codingKey.stringValue: Self.maximumLength, CodingKeys.allowedValues.codingKey.stringValue: Self.allowedValues, CodingKeys.defaultValue.codingKey.stringValue: Self.defaultValue, + CodingKeys.typeDetails.codingKey.stringValue: Self.typeDetails, CodingKeys.httpEndpoint.codingKey.stringValue: Self.httpEndpoint, CodingKeys.httpParameterSource.codingKey.stringValue: Self.httpParameterSource, CodingKeys.httpMediaType.codingKey.stringValue: Self.httpMediaType, diff --git a/Sources/SymbolKit/SymbolGraph/Symbol/ValueConstraints.swift b/Sources/SymbolKit/SymbolGraph/Symbol/ValueConstraints.swift index 6df2009..b5e1ea7 100644 --- a/Sources/SymbolKit/SymbolGraph/Symbol/ValueConstraints.swift +++ b/Sources/SymbolKit/SymbolGraph/Symbol/ValueConstraints.swift @@ -124,9 +124,58 @@ extension SymbolGraph.Symbol { public init(_ value: ValueType) { self.value = value } + + // Need custom init(from:) to special case `null` value. + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self.init(.null) + return + } + self.init(try container.decode(ValueType.self)) + } } public var defaultValue : SymbolGraph.AnyScalar? { (mixins[DefaultValue.mixinKey] as? DefaultValue)?.value } + + /// A detailed description of the set of types allowed for a parameter or key. + /// + /// Weakly-typed data structures, such as JSON, can allow a field to hold a value from a set of types, + /// rather than being of a singular fixed type. + /// For example, a time could be specified as an integer number of seconds from an epoch (eg, 1234) + /// or a time stamp string ("12:34pm"). A client can detect the different types and interpret them accordingly. + /// Whereas ``DeclarationFragments`` represents the declaration of the whole entity, + /// each ``TypeDetail`` member provides information, including the declaration, about the individual allowed types. + public struct TypeDetails: SingleValueMixin { + public static let mixinKey = "typeDetails" + public typealias ValueType = [TypeDetail] + public var value: ValueType + public init(_ value: ValueType) { + self.value = value + } + } + + public var typeDetails : [TypeDetail]? { + (mixins[TypeDetails.mixinKey] as? TypeDetails)?.value + } + + /// Detailed description of one of the types allowed for a weakly-typed parameter or key. + public struct TypeDetail: Codable { + /// The declaration of this individual type. + public var fragments: [DeclarationFragments.Fragment]? + + /// The primitive type of this type, such as "string", "integer", or "dictionary". + public var baseType: String? + + /// Whether the value for this type is actually an array of values. + public var arrayMode: Bool? + + public init(fragments: [DeclarationFragments.Fragment]? = nil, baseType: String? = nil, arrayMode: Bool? = nil) { + self.fragments = fragments + self.baseType = baseType + self.arrayMode = arrayMode + } + } } diff --git a/Tests/SymbolKitTests/SymbolGraph/Symbol/ValueConstraintsTests.swift b/Tests/SymbolKitTests/SymbolGraph/Symbol/ValueConstraintsTests.swift index f932b60..2fc43ad 100644 --- a/Tests/SymbolKitTests/SymbolGraph/Symbol/ValueConstraintsTests.swift +++ b/Tests/SymbolKitTests/SymbolGraph/Symbol/ValueConstraintsTests.swift @@ -32,6 +32,9 @@ class ValueConstraintsTests: XCTestCase { "maximum": 3.5, "default": "str", "allowedValues": ["a", 1, null], + "typeDetails": [ + { "baseType": "integer", "arrayMode": false, "fragments": [{"kind": "text", "spelling": "integer"}] } + ] } """.data(using: .utf8) @@ -47,6 +50,13 @@ class ValueConstraintsTests: XCTestCase { XCTAssertEqual(allowedValues[0], .string("a")) XCTAssertEqual(allowedValues[1], .integer(1)) XCTAssertEqual(allowedValues[2], .null) + + let typeDetails = try XCTUnwrap(symbol.typeDetails) + XCTAssertEqual(typeDetails.count, 1) + XCTAssertEqual(typeDetails[0].baseType, "integer") + XCTAssertEqual(typeDetails[0].arrayMode, false) + XCTAssertEqual(typeDetails[0].fragments?.count, 1) + XCTAssertEqual(typeDetails[0].fragments?[0].spelling, "integer") } }