diff --git a/.github/workflows/abstractions.yml b/.github/workflows/abstractions.yml new file mode 100644 index 0000000..d848eec --- /dev/null +++ b/.github/workflows/abstractions.yml @@ -0,0 +1,26 @@ +name: Swift abstractions + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: ['abstractions/**', '.github/workflows/**'] + pull_request: + paths: ['abstractions/**', '.github/workflows/**'] + +jobs: + build: + runs-on: ubuntu-latest + env: + relativePath: ./abstractions + steps: + - uses: actions/checkout@v4 + - uses: swift-actions/setup-swift@v2 + with: + swift-version: '5.7' + - name: Build SDK project + run: swift build + working-directory: ${{ env.relativePath }} + - name: Run unit tests + run: swift test + working-directory: ${{ env.relativePath }} diff --git a/abstractions/.gitignore b/abstractions/.gitignore new file mode 100644 index 0000000..3b29812 --- /dev/null +++ b/abstractions/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/abstractions/MicrosoftKiotaAbstractions.podspec b/abstractions/MicrosoftKiotaAbstractions.podspec new file mode 100644 index 0000000..ade5b27 --- /dev/null +++ b/abstractions/MicrosoftKiotaAbstractions.podspec @@ -0,0 +1,20 @@ +Pod::Spec.new do |s| + s.name = "MicrosoftKiotaAbstractions" + s.version = "1.0.0" + s.summary = "MicrosoftKiotaAbstractions provides the base infrastructure for the Kiota-generated SDKs to function. + It defines multiple concepts related to abstract HTTP requests, serialization, and authentication. + These concepts can then be implemented independently without tying the SDKs to any specific implementation. + Kiota also provides default implementations for these concepts." + s.homepage = "https://github.com/microsoft/kiota" + s.license = { :type => "MIT" } + s.authors = { "Microsoft" => "graphtooling+kiota@service.microsoft.com" } + + s.requires_arc = true + s.swift_version = "5.0" + s.osx.deployment_target = "10.9" + s.ios.deployment_target = "9.0" + s.watchos.deployment_target = "3.0" + s.tvos.deployment_target = "9.0" + s.source = { :git => "https://github.com/microsoft/kiota.git", :tag => s.version } + s.source_files = "Source/*.swift" +end \ No newline at end of file diff --git a/abstractions/MicrosoftKiotaAbstractions.xcworkspace/contents.xcworkspacedata b/abstractions/MicrosoftKiotaAbstractions.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..e95b612 --- /dev/null +++ b/abstractions/MicrosoftKiotaAbstractions.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/abstractions/Package.resolved b/abstractions/Package.resolved new file mode 100644 index 0000000..ad51a95 --- /dev/null +++ b/abstractions/Package.resolved @@ -0,0 +1,34 @@ +{ + "object": { + "pins": [ + { + "package": "PathKit", + "repositoryURL": "https://github.com/kylef/PathKit.git", + "state": { + "branch": null, + "revision": "3bfd2737b700b9a36565a8c94f4ad2b050a5e574", + "version": "1.0.1" + } + }, + { + "package": "Spectre", + "repositoryURL": "https://github.com/kylef/Spectre.git", + "state": { + "branch": null, + "revision": "26cc5e9ae0947092c7139ef7ba612e34646086c7", + "version": "0.10.1" + } + }, + { + "package": "URITemplate", + "repositoryURL": "https://github.com/kylef/URITemplate.swift.git", + "state": { + "branch": null, + "revision": "a309673fdf86e4919a0250730e461ac533a03b3a", + "version": "3.0.1" + } + } + ] + }, + "version": 1 +} diff --git a/abstractions/Package.swift b/abstractions/Package.swift new file mode 100644 index 0000000..f3fb819 --- /dev/null +++ b/abstractions/Package.swift @@ -0,0 +1,17 @@ +// swift-tools-version:5.0 +import PackageDescription + +let package = Package( + name: "MicrosoftKiotaAbstractions", + products: [ + .library(name: "MicrosoftKiotaAbstractions", targets: ["MicrosoftKiotaAbstractions"]) + ], + dependencies: [ + .package(url: "https://github.com/kylef/URITemplate.swift.git", from: "3.0.0") + ], + targets: [ + .target(name: "MicrosoftKiotaAbstractions", dependencies: ["URITemplate"]), + .testTarget(name: "MicrosoftKiotaAbstractionsTests", dependencies: ["MicrosoftKiotaAbstractions"]) + ], + swiftLanguageVersions: [.v5] +) \ No newline at end of file diff --git a/abstractions/Podfile b/abstractions/Podfile new file mode 100644 index 0000000..7df3631 --- /dev/null +++ b/abstractions/Podfile @@ -0,0 +1,23 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'MicrosoftKiotaAbstractions' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for MicrosoftKiotaAbstractions + pod 'URITemplate' + + target 'MicrosoftKiotaAbstractionsTests' do + # Pods for testing + end + +end + +target 'MicrosoftKiotaAbstractionsPackageDescription' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for MicrosoftKiotaAbstractionsPackageDescription + +end diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/ApiClientBuilder.swift b/abstractions/Source/MicrosoftKiotaAbstractions/ApiClientBuilder.swift new file mode 100644 index 0000000..cb94228 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/ApiClientBuilder.swift @@ -0,0 +1,33 @@ +public class ApiClientBuilder { + private init() { + + } + private static let defaultSerializationWriterFactoryInstanceIntl = SerializationWriterFactoryRegistry() + public static var defaultSerializationWriterFactoryInstance: SerializationWriterFactory { + get { + return defaultSerializationWriterFactoryInstanceIntl + } + } + public static func registerDefaultSerializer(metaFactory: () -> SerializationWriterFactory) { + let factory = metaFactory() + if let contentType = try? factory.getValidContentType() { + if contentType != "" { + defaultSerializationWriterFactoryInstanceIntl.contentTypeAssociatedFactories[contentType] = factory + } + } + } + private static let defaultParseNodeFactoryInstanceIntl = ParseNodeFactoryRegistry() + public static var defaultParseNodeFactoryInstance: ParseNodeFactory { + get { + return defaultParseNodeFactoryInstanceIntl + } + } + public static func registerDefaultParser(metaFactory: () -> ParseNodeFactory) { + let factory = metaFactory() + if let contentType = try? factory.getValidContentType() { + if contentType != "" { + defaultParseNodeFactoryInstanceIntl.contentTypeAssociatedFactories[contentType] = factory + } + } + } +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/ApiError.swift b/abstractions/Source/MicrosoftKiotaAbstractions/ApiError.swift new file mode 100644 index 0000000..1007772 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/ApiError.swift @@ -0,0 +1,3 @@ +public enum ApiError: Error { + case unknownError(String, Any? = nil) +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AccessTokenProvider.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AccessTokenProvider.swift new file mode 100644 index 0000000..8154b59 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AccessTokenProvider.swift @@ -0,0 +1,6 @@ +import Foundation + +public protocol AccessTokenProvider { + func getAuthenticationToken(url: URL) async throws -> String? + var allowedHostsValidator: AllowedHostsValidator { get } +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AllowedHostsValidator.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AllowedHostsValidator.swift new file mode 100644 index 0000000..4312a05 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AllowedHostsValidator.swift @@ -0,0 +1,25 @@ +import Foundation +public class AllowedHostsValidator { + private var validHostsIntl = Set() + public init(validHosts: [String]) { + self.validHosts = validHosts; + } + public var validHosts: [String] { get { + return Array(validHostsIntl) + + } set (validHosts) { + self.validHostsIntl = Set(validHosts.map { $0.lowercased() }) + }} + public func isUrlHostValid(url: URL) -> Bool { + if let host = url.host { + return validHostsIntl.contains(host.lowercased()) + } + return false + } +} +public let isSchemeHttps = { (url: URL) -> Bool in + if let scheme = url.scheme { + return scheme.lowercased() == "https" + } + return false +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AnonymousAuthenticationProvider.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AnonymousAuthenticationProvider.swift new file mode 100644 index 0000000..b45580e --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AnonymousAuthenticationProvider.swift @@ -0,0 +1,5 @@ +public class AnonymousAuthenticationProvider : AuthenticationProvider { + public func authenticateRequest(request: RequestInformation) async throws { + // Do nothing + } +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AuthenticationProvider.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AuthenticationProvider.swift new file mode 100644 index 0000000..7e67fbc --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/AuthenticationProvider.swift @@ -0,0 +1,3 @@ +public protocol AuthenticationProvider { + func authenticateRequest(request: RequestInformation) async throws +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/BaseBearerAuthenticationProvider.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/BaseBearerAuthenticationProvider.swift new file mode 100644 index 0000000..c111e7c --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Authentication/BaseBearerAuthenticationProvider.swift @@ -0,0 +1,22 @@ +public class BaseBearerAuthenticationProvider : AuthenticationProvider { + public init (accessTokenProvider: AccessTokenProvider) { + accessTokenProviderIntl = accessTokenProvider + } + private var accessTokenProviderIntl: AccessTokenProvider + public var accessTokenProvider: AccessTokenProvider { + get { + return accessTokenProviderIntl + } + } + public let authorizationHeaderKey = "Authorization" + public let authorizationHeaderValuePrefix = "Bearer " + public func authenticateRequest(request: RequestInformation) async throws { + if request.headers[authorizationHeaderKey] == nil { + let url = try request.getUri() + let tokenResult = try? await accessTokenProvider.getAuthenticationToken(url: url) + if let token = tokenResult { + request.headers[authorizationHeaderKey] = authorizationHeaderValuePrefix + token + } + } + } +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/HttpMethod.swift b/abstractions/Source/MicrosoftKiotaAbstractions/HttpMethod.swift new file mode 100644 index 0000000..70348a9 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/HttpMethod.swift @@ -0,0 +1,11 @@ +public enum HttpMethod { + case get + case post + case patch + case delete + case options + case connect + case put + case trace + case head +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/RequestAdapter.swift b/abstractions/Source/MicrosoftKiotaAbstractions/RequestAdapter.swift new file mode 100644 index 0000000..fd9fefd --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/RequestAdapter.swift @@ -0,0 +1,11 @@ +public typealias ErrorMappings = [String:ParsableFactory] +public typealias ResponseHandler = (Any, ErrorMappings) async throws -> DeserializationType +public protocol RequestAdapter { + func send(request: RequestInformation, ctor: ParsableFactory, responseHandler: ResponseHandler?, errorMappings: ErrorMappings?) async throws -> T? + func sendCollection(request: RequestInformation, ctor: ParsableFactory, responseHandler: ResponseHandler?, errorMappings: ErrorMappings?) async throws -> [T]? + func sendPrimitive(request: RequestInformation, responseHandler: ResponseHandler?, errorMappings: ErrorMappings?) async throws -> T? + func sendNoContent(request: RequestInformation, responseHandler: ResponseHandler?, errorMappings: ErrorMappings?) async throws -> Void + func enableBackingStore() throws + var baseUrl: String { get set } + var serializationWriterFactory: SerializationWriterFactory { get } +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/RequestInformation.swift b/abstractions/Source/MicrosoftKiotaAbstractions/RequestInformation.swift new file mode 100644 index 0000000..0b59d47 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/RequestInformation.swift @@ -0,0 +1,96 @@ +import Foundation +import URITemplate + +public enum RequestInformationErrors : Error { + case emptyUrlTemplate + case emptyContentType + case unableToExpandUriTemplate + case invalidRawUrl +} + +public class RequestInformation { + public var method: HttpMethod = HttpMethod.get + public var urlTemplate = "" + public var queryParameters = [String:String]() + public var pathParameters = [String:String]() + public var headers = [String:String]() + var contentInternal: Data? + public var content: Data? { + get { + return contentInternal + } + set(newContent) { + contentInternal = newContent + if newContent != nil { + headers[contentTypeHeaderKey] = binaryContentType + } + } + } + var uriInternal: URL? + public func getUri() throws -> URL { + if let uriInternalValue = uriInternal { + return uriInternalValue + } + guard !urlTemplate.isEmpty else { + throw RequestInformationErrors.emptyUrlTemplate + } + if let rawUrl = pathParameters[rawUrlKey] { + if rawUrl != "" { + let newValue = URL(string: rawUrl) + if let url = newValue { + setUri(newUri: url) + return url + } + } + throw RequestInformationErrors.invalidRawUrl + } else { + let urlTemplate = URITemplate(template: self.urlTemplate) + let merged = pathParameters.merging(queryParameters) + { (first, _) in first } + let url = urlTemplate.expand(merged) + if let newValue = URL(string: url) { + return newValue + } else { + throw RequestInformationErrors.unableToExpandUriTemplate + } + } + } + public func setUri(newUri: URL) { + uriInternal = newUri + pathParameters.removeAll() + queryParameters.removeAll() + } + var options = [String:RequestOption]() + let rawUrlKey = "request-raw-url" + let contentTypeHeaderKey = "Content-Type" + let binaryContentType = "application/octet-stream" + public func addRequestOption(options:RequestOption...) { + for option in options { + self.options[option.key] = option + } + } + public func getRequestOptions() -> [RequestOption] { + return [RequestOption](options.values) + } + public func setContentFromParsable(requestAdapter: RequestAdapter, contentType: String, item: T) throws { + guard contentType != "" else { + throw RequestInformationErrors.emptyContentType + } + if let writer = try? requestAdapter.serializationWriterFactory.getSerializationWriter(contentType: contentType) { + try writer.writeObjectValue(key: "", value: item) + self.content = try? writer.getSerializedContent() + self.headers[contentTypeHeaderKey] = contentType + } + } + public func setContentFromParsableCollection(requestAdapter: RequestAdapter, contentType: String, items: [T]) throws { + guard contentType != "" else { + throw RequestInformationErrors.emptyContentType + } + if let writer = try? requestAdapter.serializationWriterFactory.getSerializationWriter(contentType: contentType) { + try writer.writeCollectionOfObjectValues(key: "", value: items) + self.content = try? writer.getSerializedContent() + self.headers[contentTypeHeaderKey] = contentType + } + } + //TODO add query parameters from object by reflection +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/RequestOption.swift b/abstractions/Source/MicrosoftKiotaAbstractions/RequestOption.swift new file mode 100644 index 0000000..4ae1eb2 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/RequestOption.swift @@ -0,0 +1,3 @@ +public protocol RequestOption { + var key: String { get } +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/AdditionalDataHolder.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/AdditionalDataHolder.swift new file mode 100644 index 0000000..756421c --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/AdditionalDataHolder.swift @@ -0,0 +1,3 @@ +public protocol AdditionalDataHolder { + var additionalData : [String:Any] { get set } +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/Parsable.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/Parsable.swift new file mode 100644 index 0000000..1adcdeb --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/Parsable.swift @@ -0,0 +1,6 @@ +public typealias FieldDeserializer = (T, ParseNode) throws -> Void +public typealias ParsableFactory = (ParseNode) throws -> Parsable +public protocol Parsable { + func serialize(writer: SerializationWriter) throws + func getFieldDeserializers() -> [String:FieldDeserializer] +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNode.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNode.swift new file mode 100644 index 0000000..9fdb88a --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNode.swift @@ -0,0 +1,23 @@ +import Foundation +public protocol ParseNode { + func getChileNode(key: String) throws -> ParseNode? + func getObjectValue(ctor: ParsableFactory) throws -> T? + func getCollectionOfObjectValues(ctor: ParsableFactory) throws -> [T]? + func getCollectionOfPrimitiveValues() throws -> [T]? + func getStringValue() throws -> String? + func getBoolValue() throws -> Bool? + func getUint8Value() throws -> UInt8? + func getInt8Value() throws -> Int8? + func getInt32Value() throws -> Int32? + func getInt64Value() throws -> Int64? + func getFloat32Value() throws -> Float32? + func getFloat64Value() throws -> Float64? + func getByteArrayValue() throws -> [UInt8]? + func getDateValue() throws -> Date? //TODO + func getTimeOnlyValue() throws -> Date? //TODO + func getDateOnlyValue() throws -> Date? + func getDurationValue() throws -> Date? //TODO + func getUUIDValue() throws -> UUID? + func getAdditionalData() throws -> [String:Any]? + //TODO get enum value & get collection of enum values +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNodeFactory.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNodeFactory.swift new file mode 100644 index 0000000..f41dd5b --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNodeFactory.swift @@ -0,0 +1,5 @@ +import Foundation +public protocol ParseNodeFactory { + func getValidContentType() throws -> String + func getRootParseNode(contentType: String, content: Data?) throws -> ParseNode +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNodeFactoryRegistry.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNodeFactoryRegistry.swift new file mode 100644 index 0000000..1c42032 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/ParseNodeFactoryRegistry.swift @@ -0,0 +1,21 @@ +import Foundation +public enum ParseNodeFactoryRegistryErrors : Error { + case registrySupportsMultipleTypesGetOneFactory + case factoryNotFoundForContentType + case contentCannotBeNil +} +public class ParseNodeFactoryRegistry : ParseNodeFactory { + public var contentTypeAssociatedFactories = [String:ParseNodeFactory]() + public func getValidContentType() throws -> String { + throw ParseNodeFactoryRegistryErrors.registrySupportsMultipleTypesGetOneFactory + } + public func getRootParseNode(contentType: String, content: Data?) throws -> ParseNode { + guard let factory = contentTypeAssociatedFactories[contentType] else { + throw ParseNodeFactoryRegistryErrors.factoryNotFoundForContentType + } + guard content != nil else { + throw ParseNodeFactoryRegistryErrors.contentCannotBeNil + } + return try factory.getRootParseNode(contentType: contentType, content: content) + } +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriter.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriter.swift new file mode 100644 index 0000000..64d4f6a --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriter.swift @@ -0,0 +1,24 @@ +import Foundation + +public protocol SerializationWriter { + func writeStringValue(key : String, value : String?) throws + func writeBoolValue(key : String, value : Bool?) throws + func writeUint8Value(key : String, value : UInt8?) throws + func writeInt8Value(key : String, value : Int8?) throws + func writeInt32Value(key : String, value : Int32?) throws + func writeInt64Value(key : String, value : Int64?) throws + func writeFloat32Value(key : String, value : Float32?) throws + func writeFloat64Value(key : String, value : Float64?) throws + func writeByteArrayValue(key : String, value : [UInt8]) throws + func writeDateValue(key : String, value : Date?) throws //TODO + func writeTimeOnlyValue(key : String, value : Date?) throws //TODO + func writeDateOnlyValue(key : String, value : Date?) throws + func writeDurationValue(key : String, value : Date?) throws //TODO + func writeUUIDValue(key : String, value : UUID?) throws + func writeObjectValue(key: String, value: T) throws + func writeCollectionOfObjectValues(key: String, value: [T]) throws + func writeCollectionOfPrimitiveValues(key: String, value: [T]) throws + func getSerializedContent() throws -> Data? + func writeAdditionalData(value: [String:Any]?) throws + //TODO write enum value & write collection of enum values +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriterFactory.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriterFactory.swift new file mode 100644 index 0000000..c4f4e77 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriterFactory.swift @@ -0,0 +1,4 @@ +public protocol SerializationWriterFactory { + func getValidContentType() throws -> String + func getSerializationWriter(contentType: String) throws -> SerializationWriter +} \ No newline at end of file diff --git a/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriterFactoryRegistry.swift b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriterFactoryRegistry.swift new file mode 100644 index 0000000..c7f2031 --- /dev/null +++ b/abstractions/Source/MicrosoftKiotaAbstractions/Serialization/SerializationWriterFactoryRegistry.swift @@ -0,0 +1,16 @@ +public enum SerializationWriterFactoryRegistryErrors : Error { + case registrySupportsMultipleTypesGetOneFactory + case factoryNotFoundForContentType +} +public class SerializationWriterFactoryRegistry : SerializationWriterFactory { + public var contentTypeAssociatedFactories = [String:SerializationWriterFactory]() + public func getValidContentType() throws -> String { + throw SerializationWriterFactoryRegistryErrors.registrySupportsMultipleTypesGetOneFactory + } + public func getSerializationWriter(contentType: String) throws -> SerializationWriter { + guard let factory = contentTypeAssociatedFactories[contentType] else { + throw SerializationWriterFactoryRegistryErrors.factoryNotFoundForContentType + } + return try factory.getSerializationWriter(contentType: contentType) + } +} \ No newline at end of file diff --git a/abstractions/Tests/MicrosoftKiotaAbstractionsTests/RequestInformationTests.swift b/abstractions/Tests/MicrosoftKiotaAbstractionsTests/RequestInformationTests.swift new file mode 100644 index 0000000..e69de29