├── .DS_Store ├── .gitignore ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ ├── francescodemarco.xcuserdatad │ │ ├── IDEFindNavigatorScopes.plist │ │ └── UserInterfaceState.xcuserstate │ │ ├── giuseppecarannante.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── kekko.xcuserdatad │ │ ├── IDEFindNavigatorScopes.plist │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ ├── francescodemarco.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── giuseppecarannante.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ └── kekko.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── FUNDING.yml ├── LICENSE ├── Package.swift ├── README.md └── Sources ├── .DS_Store └── FMProKit ├── APIProtocol.swift ├── Enumerations.swift ├── Error.swift ├── FMDataAPI ├── .DS_Store ├── Extensions │ ├── CRUD │ │ ├── createRecordExtensionData.swift │ │ ├── deleteRecordExtensionData.swift │ │ ├── editRecordExtensionData.swift │ │ └── getTableExtensionData.swift │ ├── metadataExtensionData.swift │ ├── openRequestExtension.swift │ ├── scriptExtensionData.swift │ └── supportFunctionsExtensionData.swift ├── FMDataAPI.swift └── JSON Helper │ ├── JSONFileFMData.swift │ ├── MetadataModels.swift │ ├── Script.swift │ └── TokenModel.swift ├── FMOdataAPI ├── Extensions │ ├── CRUD │ │ ├── createRecordExtensionOData.swift │ │ ├── deleteRecordExtensionOData.swift │ │ ├── editRecordExtensionOData.swift │ │ └── getTableExtensionOData.swift │ ├── executeQueryExtensionOData.swift │ ├── getFromTableExtensionOData.swift │ ├── getMetadataExtensionOData.swift │ ├── joinTableExtensionOData.swift │ ├── modifyingSchemaExtensionOData.swift │ └── scriptExtensionOData.swift ├── FMODataAPI.swift └── JSON support │ ├── JSONSupportDBModification.swift │ ├── JSONSupportScript.swift │ └── JSONValue.swift ├── FileMakerErrors.swift └── supportFunctionRequest.swift /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderlyStudio/FMProKit/70c85f5cdf73bea8d8759b9a31f352540d6ad64b/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .swiftpm/xcode/package.xcworkspace/xcuserdata/francescodemarco.xcuserdatad/UserInterfaceState.xcuserstate 3 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcuserdata/francescodemarco.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcuserdata/francescodemarco.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderlyStudio/FMProKit/70c85f5cdf73bea8d8759b9a31f352540d6ad64b/.swiftpm/xcode/package.xcworkspace/xcuserdata/francescodemarco.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcuserdata/giuseppecarannante.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderlyStudio/FMProKit/70c85f5cdf73bea8d8759b9a31f352540d6ad64b/.swiftpm/xcode/package.xcworkspace/xcuserdata/giuseppecarannante.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcuserdata/kekko.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcuserdata/kekko.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderlyStudio/FMProKit/70c85f5cdf73bea8d8759b9a31f352540d6ad64b/.swiftpm/xcode/package.xcworkspace/xcuserdata/kekko.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/francescodemarco.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/francescodemarco.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FMProKit-Package.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | FMProKit.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | FMProKit 21 | 22 | primary 23 | 24 | 25 | FMProKitTests 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/giuseppecarannante.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/giuseppecarannante.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FMProKit.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | FMProKit 16 | 17 | primary 18 | 19 | 20 | FMProKitTests 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/kekko.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 36 | 37 | 51 | 52 | 66 | 67 | 81 | 82 | 96 | 97 | 111 | 112 | 126 | 127 | 141 | 142 | 143 | 144 | 145 | 147 | 159 | 160 | 161 | 163 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/kekko.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FMProKit.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | FMProKit 16 | 17 | primary 18 | 19 | 20 | FMProKitTests 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | custom: ['https://bmc.link/CoderlyStudio'] 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Coderly Studio: Francesco De Marco, Gianluca Annina, Giuseppe Carannante 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "FMProKit", 8 | platforms: [ 9 | .iOS(.v15), 10 | .macOS(.v12) 11 | ], 12 | products: [ 13 | // Products define the executables and libraries a package produces, and make them visible to other packages. 14 | .library( 15 | name: "FMProKit", 16 | targets: ["FMProKit"]), 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | // .package(url: /* package url */, from: "1.0.0"), 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 25 | .target( 26 | name: "FMProKit", 27 | dependencies: []) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![FMPKgitBanner](https://user-images.githubusercontent.com/129211821/235753747-12615a14-c8e9-4429-9874-55c55ad18a9d.png) 2 | 3 | 4 | ![GitHub](https://img.shields.io/badge/Data--Version-vLatest-green) ![GitHub](https://img.shields.io/badge/OData--Version-v4-brightgreen) ![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fstarsite%2FSwiftFM%2Fbadge%3Ftype%3Dplatforms) 5 | 6 | --- 7 | 8 | 9 | # FMProKit - an easier way to communicate with FileMaker 10 | 11 | [![](https://img.shields.io/badge/FMProKit-info-informational)](#) 12 | 13 | FMProKit is a swift package that lets you easily communicate with a FileMaker database using both OData and FileMaker Data protocols 14 | 15 | ## Overview 16 | 17 | Thanks to FMProkit, it is possible to use: 18 | * The basic CRUD (CRUD is an acronym that refers to the four functions that are considered necessary to implement a persistent storage application: create, read, update and delete.); 19 | * Run scripts from the server itself; 20 | * Retrieve the metadata; 21 | * Call some custom query; 22 | * And only for OData the possibility to modify the database schema. 23 | 24 | --- 25 | 26 | ### Index 27 | 28 | - [Concepts](#arrows_counterclockwise-asyncawait) 29 | - [Async/await](#arrows_counterclockwise-asyncawait) 30 | - [Generics](#busts_in_silhouette-generics) 31 | - [Codable](#currency_exchange-codable) 32 | - [DocC](#books-docc) 33 | - [Error Handling](#warning-error-handling) 34 | - [Testing](#test_tube-testing) 35 | - [How To Use](#-how-to-use) 36 | - [Before you start](#before-you-start) 37 | 38 | 39 | --- 40 | 41 | ### Examples 42 | 43 | - [Fetch an entire table](#fetch-an-entire-table) 44 | - [Create a new record](#create-a-new-record) 45 | - [Delete a record](#delete-a-record) 46 | - [Edit a record](#edit-a-record) 47 | - [Fetch data using a query](#fetch-data-using-a-query) 48 | - [Get metadata](#get-metadata) 49 | 50 | --- 51 | 52 | ### :arrows_counterclockwise: Async/await 53 | 54 | [![](https://img.shields.io/badge/Swift%20Concurrency-info-informational)](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) 55 | 56 | This package uses modern and Swift built-in support `async/await` to run asynchronous and parallel code in a structured way. 57 | _Asynchronous_ code can be suspended and resumed later, allowings your program continue to make progress on short-term operations like updating its UI while continuing to work on long-running operations like fetching data over the network or parsing files. 58 | _Parallel_ code means multiple pieces of code run simultaneously, for example each core in a processor can run a different piece of code at the same time, completing different tasks. 59 | 60 | --- 61 | 62 | ### :busts_in_silhouette: Generics 63 | 64 | [![](https://img.shields.io/badge/Swift%20Generics-info-informational)](https://docs.swift.org/swift-book/LanguageGuide/Generics.html) 65 | 66 | `Generic` code enables you to write flexible, reusable functions and types that can work with any type. 67 | _Generics_ are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code, for example, Swift’s Array and Dictionary types are both generic collections. You can create an array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift. Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be. 68 | 69 | In `FMProKit` this is foundamental to create a package completely detached from a particular data model allowing to work with any type and how many fields your projects need. 70 | 71 | --- 72 | 73 | ### :currency_exchange: Codable 74 | 75 | [![](https://img.shields.io/badge/Codable-info-informational)](https://developer.apple.com/documentation/swift/codable) 76 | 77 | `Codable` is a type that can convert itself into and out of an external representation. 78 | Codable is a type alias for the `Encodable` and `Decodable` protocols. When you use Codable as a type or a generic constraint, it matches any type that conforms to both protocols. 79 | 80 | We use `Codable` structures to extract data from `JSON` files and to convert yours data in `JSON` to send to FileMaker. 81 | 82 | --- 83 | 84 | ### :books: DocC 85 | 86 | [![](https://img.shields.io/badge/DocC-info-informational)](https://developer.apple.com/documentation/docc) 87 | 88 | The `DocC documentation compiler` converts Markdown-based text into rich documentation for Swift and Objective-C projects, and displays it right in the Xcode documentation window. 89 | 90 | We used it to explain for each method its parameters, what it does and what it returns, if it does. 91 | With `DocC` there is an entire structured documentation aviable for you already in the package without external links. 92 | 93 | --- 94 | 95 | ### :warning: Error Handling 96 | [![](https://img.shields.io/badge/Error%20Handling-ino-informational)](#https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html) 97 | 98 | To better handle errors in our package, we implemented custom enumerations by extending the `Error` protocol. 99 | 100 | These custom errors allow us to avoid crashes and appropriately handle all possible HTTP responses that the server returns. Thanks to this type of implementation when using the functions it will only be necessary to use a `do catch` block so as to avoid any blocking errors. Any server errors or incorrect requests will be shown in the console with related HTTP code. 101 | 102 | --- 103 | 104 | ### :test_tube: Testing 105 | 106 | [![](https://img.shields.io/badge/Testing-in%20progress-critical)](#) 107 | 108 | Our functions are designed to use `FileMaker API` in the most efficient and easiest way possible, reducing the error possibility. 109 | To achieve this goal we tested in many different ways almost every function. 110 | 111 | `Testing` allows us to avoid crashes and handle an incredible variety of errors that will be shown to you in case something went wrong. 112 | This is a very long and complex process and requires time. Every feedback or crash report is fundamental to improving FMProKit. 113 | 114 | --- 115 | 116 | ### 🗳 How To Use 117 | 118 | [![](https://img.shields.io/badge/Swift%20Package%20Manager-info-informational)](https://www.swift.org/package-manager/) 119 | 120 | * Xcode -> File -> Add Packages 121 | * Insert this link `https://github.com/CoderlyStudio/FMProKit` 122 | * Add an `import FMProKit` statement 123 | * Initialize an OData or Data object 124 | 125 | ```swift 126 | import FMProKit 127 | 128 | //YOUR CLASS/STRUCT/VIEW/CODE 129 | 130 | //OData API init 131 | let api:FMODataAPI = FMODataAPI(server: "serverName", database: "dbName", username: "username", password: "password") 132 | 133 | //Data API init 134 | let api:FMDataAPI = FMDataAPI(server: "serverName", database: "dbName", username: "username", password: "password") 135 | 136 | ``` 137 | * **You're ready to use FileMaker API like a Pro!** :sunglasses: 138 | --- 139 | 140 | # Before You Start 141 | 142 | In order to convert your objects to JSON and vice versa, they must conform to the [`Codable`](#currency_exchange-codable) protocol. 143 | 144 | Make sure that the `fields` in the database table are represented with the same `name` and `type` in your Swift structure/class. 145 | If there is an inconsistency between name and/or type, this will cause an `encodeError` or a `decodeError`, depending on the operation performed. 146 | 147 | ### Watch out 148 | If you use `images` within the FileMaker database, in Swift, the corresponding field must be of type `Data`. 149 | 150 | `Dates`, instead, must be of type `String` in order to be used in Swift. 151 | 152 | Remember that in order to use the `OData` protocol, it is necessary to grant permissions in FileMaker as follows: 153 | 154 | * File -> Manage -> Security 155 | * Advanced Settings -> Extended Privileges 156 | * Look for `fmodata` and set privileges 157 | 158 | --- 159 | 160 | # Examples 161 | 162 | **Remember**: since they are asynchronous calls, they must always be used within `Task` or `.task` structures. 163 | 164 | 165 | 166 | Let's see some examples of functions inside our kit. 167 | 168 | 169 | ### Fetch an entire table 170 | To fetch all the records from a table, use the code below: 171 | 172 | ```swift 173 | Task { 174 | do { 175 | //OData API 176 | let list:[yourType] = try await api.getTable(table: "tableName") 177 | 178 | //Data API 179 | let list:[yourType] = try await api.getTable(table: "tableName") 180 | 181 | } catch { 182 | print(error) 183 | } 184 | } 185 | ``` 186 | ### Create a new record 187 | To create a new record, use the code below: 188 | 189 | ```swift 190 | 191 | let object = YourObject() 192 | 193 | Task { 194 | do { 195 | //OData API 196 | try await api.createRecord(table: "tableName", data: object) 197 | 198 | //Data API 199 | try await api.createRecord(table: "tableName", data: object) 200 | 201 | } catch { 202 | print(error) 203 | } 204 | } 205 | ``` 206 | 207 | ### Delete a record 208 | To delete a record, use the code below: 209 | 210 | ```swift 211 | Task { 212 | do { 213 | //OData API 214 | try await api.deleteRecord(table: "tableName", id: "recordId") 215 | 216 | //Data API 217 | try await api.deleteRecord(table: "tableName", id: objectToDelete) 218 | 219 | } catch { 220 | print(error) 221 | } 222 | } 223 | ``` 224 | 225 | ### Edit a record 226 | To edit a record, use the code below: 227 | 228 | ```swift 229 | 230 | let object = YourObject() 231 | 232 | Task { 233 | do { 234 | //OData API 235 | try await api.editRecord(table: "tableName", id: "recordId", data: object) 236 | 237 | //Data API 238 | try await api.editRecord(table: "tableName", findData: objectToEdit, editData: object) 239 | 240 | } catch { 241 | print(error) 242 | } 243 | } 244 | ``` 245 | 246 | ### Fetch data using a query 247 | To fetch a record using a query, use the code below: 248 | 249 | ```swift 250 | Task { 251 | do { 252 | //OData API 253 | try await api.getTable(table: "tableName", query: "query") 254 | 255 | //Data API 256 | try await api.getTable(table: "tableName", query: "query") 257 | 258 | } catch { 259 | print(error) 260 | } 261 | } 262 | ``` 263 | 264 | ### Get metadata 265 | To the metadata, use the code below: 266 | 267 | ```swift 268 | Task { 269 | do { 270 | //OData API 271 | let metadata: Metadata = try await api.getMetadataAsData() 272 | 273 | //Data API 274 | let metadata: Data = try await api.getMetadataAsData() 275 | 276 | } catch { 277 | print(error) 278 | } 279 | } 280 | ``` 281 | 282 | And this are just some of the possible functions it is possible to use thanks to FMProKit 283 | 284 | --- 285 | 286 | More to come... **Stay Tuned!** :smirk: 287 | -------------------------------------------------------------------------------- /Sources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderlyStudio/FMProKit/70c85f5cdf73bea8d8759b9a31f352540d6ad64b/Sources/.DS_Store -------------------------------------------------------------------------------- /Sources/FMProKit/APIProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 29/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | /// This protocol defines the basic structre and functions signature 11 | protocol APIProtocol: ObservableObject { 12 | /// a constant contains the server domain 13 | var hostname: String { get } 14 | /// a constant contains the actual name of your database 15 | var database: String { get } 16 | /// a variable is used to build the first common part URI for APIs 17 | var baseUri: String { get } 18 | /// a constant contains the username credential to access the database 19 | var username: String { get } 20 | /// a constant contains the password credential to access the database 21 | var password: String { get } 22 | /// a variable contains the HTTP response in JSON format 23 | var responseJSON: Data { get set } 24 | /// extends the package instead of putting it inside the library 25 | var protocolVersion: ProtocolVersion { get set } 26 | /// Used for the Authorization HTTP firld 27 | var authData: String { get set } 28 | /// Store the last sent request 29 | var request: URLRequest { get set } 30 | /// Intizializer 31 | init(server: String, database: String, username: String, password: String, version: ProtocolVersion) 32 | } 33 | -------------------------------------------------------------------------------- /Sources/FMProKit/Enumerations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension.swift 3 | // FilemakerAPI 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 08/10/22. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Enumeration of all the possible filterOption of an OData query 11 | public enum FilterOption: String { 12 | case equal = "eq" 13 | case notEqual = "ne" 14 | case greaterThen = "gt" 15 | case lessThen = "lt" 16 | case greaterEqual = "ge" 17 | case lessEqual = "le" 18 | } 19 | 20 | /// Enumeration of all the possible HTTP method of the protocol 21 | public enum HTTPMethod: String { 22 | case get = "GET" 23 | case patch = "PATCH" 24 | case delete = "DELETE" 25 | case post = "POST" 26 | } 27 | 28 | // change the documentation 29 | /// Enumeration of all the possible version of the protocol 30 | public enum ProtocolVersion: String { 31 | case vLatest 32 | case v1 33 | case v2 34 | case v4 35 | } 36 | /// Enumeration of all the possible orderOption of an OData query 37 | public enum Order: String { 38 | case desc 39 | case asc 40 | } 41 | -------------------------------------------------------------------------------- /Sources/FMProKit/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // FMApi 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 05/10/22. 6 | // 7 | 8 | import Foundation 9 | 10 | /// An enumeration of all the possible type of error that can be thrown by the functions that interfaces with the Filemaker Database 11 | enum FetchError: Error { 12 | case tokenRequestError 13 | } 14 | 15 | /// Possible errors that may cause HTTP errors 16 | enum FMProErrors: Error { 17 | case negativeNumber 18 | case tableNameMissing 19 | case emptyListOfFields 20 | case fieldNameMissing 21 | case indexNameMissing 22 | } 23 | 24 | enum ProtocolVersionError: Error { 25 | case wrongProtocolVersion 26 | } 27 | 28 | /// An enumeration of all the possible type of error that can be thrown by the functions that interfaces with the Filemaker Database 29 | enum HTTPError: Error { 30 | case errorCode100Continue 31 | case errorCode101SwitchingProtocols 32 | case errorCode102Processing 33 | case errorCode300MultipleChoices 34 | case errorCode301MovedPermanently 35 | case errorCode302Found 36 | case errorCode303SeeOther 37 | case errorCode304NotModified 38 | case errorCode305UseProxy 39 | case errorCode306SwitchProxy 40 | case errorCode307TemporaryRedirect 41 | case errorCode308PermanentRedirect 42 | case errorCode400BadRequest 43 | case errorCode401Unauthorized 44 | case errorCode402PaymentRequired 45 | case errorCode403Forbidden 46 | case errorCode404NotFound 47 | case errorCode405MethodNotAllowed 48 | case errorCode406NotAcceptable 49 | case errorCode407ProxyAuthenticationRequired 50 | case errorCode408RequestTimeout 51 | case errorCode409Conflict 52 | case errorCode410Gone 53 | case errorCode411LenghtRequired 54 | case errorCode412PreconditionFailed 55 | case errorCode413PayloadTooLarge 56 | case errorCode414URITooLong 57 | case errorCode415UnsupportedMediaType 58 | case errorCode416RangeNotSatisfiable 59 | case errorCode417ExpectationFailed 60 | case errorCode418Teapot 61 | case errorCode421MisdirectedRequest 62 | case errorCode422UnprocessableEntity 63 | case errorCode423Locked 64 | case errorCode424FailedDependecy 65 | case errorCode426UpgradeRequired 66 | case errorCode428PreconditionRequired 67 | case errorCode429TooManyRequests 68 | case errorCode431RequestHeaderFieldsTooLarge 69 | case errorCode444NoResponse 70 | case errorCode451UnavailableForLegalReasons 71 | case errorCode495SSLCertificateError 72 | case errorCode496SSLCertificateRequired 73 | case errorCode497HTTPRequestSentToHTTPSPort 74 | case errorCode499ClientClosedRequest 75 | case errorCode500InternalServerError 76 | case errorCode501NotImplemented 77 | case errorCode502BadGateway 78 | case errorCode503ServiceUnavailable 79 | case errorCode504GatewayTimeout 80 | case errorCode505HTTPVersionNotSupported 81 | case errorCode506VariantAlsoNegotiates 82 | case errorCode507InsufficientStorage 83 | case errorCode508LoopDetected 84 | case errorCode510NotExtended 85 | case errorCode511NetworkAuthenticationRequired 86 | case undefinedError 87 | } 88 | 89 | /// An enumeration that describes the error caused by decoding the structure 90 | enum AuthError: Error { 91 | case authorizationEncodingError 92 | } 93 | 94 | /// This is a list of Hypertext Transfer Protocol (HTTP) response status codes. 95 | /// It includes codes from IETF internet standards, other IETF RFCs, other specifications, and some additional commonly used codes. 96 | /// The first digit of the status code specifies one of five classes of response; an HTTP client must recognise these five classes at a minimum. 97 | enum HTTPStatusCode: Int { 98 | /// The response class representation of status codes, these get grouped by their first digit. 99 | enum ResponseType { 100 | /// - informational: This class of status code indicates a provisional response, consisting only of the Status-Line and optional headers, and is terminated by an empty line. 101 | case informational 102 | /// - success: This class of status codes indicates the action requested by the client was received, understood, accepted, and processed successfully. 103 | case success 104 | /// - redirection: This class of status code indicates the client must take additional action to complete the request. 105 | case redirection 106 | /// - clientError: This class of status code is intended for situations in which the client seems to have erred. 107 | case clientError 108 | /// - serverError: This class of status code indicates the server failed to fulfill an apparently valid request. 109 | case serverError 110 | /// - undefined: The class of the status code cannot be resolved. 111 | case undefined 112 | } 113 | /// - ok: Standard response for successful HTTP requests. 114 | case ok = 200 115 | /// - created: The request has been fulfilled, resulting in the creation of a new resource. 116 | case created = 201 117 | /// - accepted: The request has been accepted for processing, but the processing has not been completed. 118 | case accepted = 202 119 | /// - nonAuthoritativeInformation: The server is a transforming proxy (e.g. a Web accelerator) that received a 200 OK from its origin, but is returning a modified version of the origin's response. 120 | case nonAuthoritativeInformation = 203 121 | /// - noContent: The server successfully processed the request and is not returning any content. 122 | case noContent = 204 123 | /// - resetContent: The server successfully processed the request, but is not returning any content. 124 | case resetContent = 205 125 | /// - partialContent: The server is delivering only part of the resource (byte serving) due to a range header sent by the client. 126 | case partialContent = 206 127 | /// - multiStatus: The message body that follows is an XML message and can contain a number of separate response codes, depending on how many sub-requests were made. 128 | case multiStatus = 207 129 | /// - alreadyReported: The members of a DAV binding have already been enumerated in a previous reply to this request, and are not being included again. 130 | case alreadyReported = 208 131 | /// - IMUsed: The server has fulfilled a request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance. 132 | case IMUsed = 226 133 | /// - badRequest: The server cannot or will not process the request due to an apparent client error. 134 | case badRequest = 400 135 | /// - unauthorized: Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. 136 | case unauthorized = 401 137 | /// - paymentRequired: The content available on the server requires payment. 138 | case paymentRequired = 402 139 | /// - forbidden: The request was a valid request, but the server is refusing to respond to it. 140 | case forbidden = 403 141 | /// - notFound: The requested resource could not be found but may be available in the future. 142 | case notFound = 404 143 | /// - methodNotAllowed: A request method is not supported for the requested resource. e.g. a GET request on a form which requires data to be presented via POST 144 | case methodNotAllowed = 405 145 | /// - notAcceptable: The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request. 146 | case notAcceptable = 406 147 | /// - proxyAuthenticationRequired: The client must first authenticate itself with the proxy. 148 | case proxyAuthenticationRequired = 407 149 | /// - requestTimeout: The server timed out waiting for the request. 150 | case requestTimeout = 408 151 | /// - conflict: Indicates that the request could not be processed because of conflict in the request, such as an edit conflict between multiple simultaneous updates. 152 | case conflict = 409 153 | /// - gone: Indicates that the resource requested is no longer available and will not be available again. 154 | case gone = 410 155 | /// - lengthRequired: The request did not specify the length of its content, which is required by the requested resource. 156 | case lengthRequired = 411 157 | /// - preconditionFailed: The server does not meet one of the preconditions that the requester put on the request. 158 | case preconditionFailed = 412 159 | /// - payloadTooLarge: The request is larger than the server is willing or able to process. 160 | case payloadTooLarge = 413 161 | /// - URITooLong: The URI provided was too long for the server to process. 162 | case URITooLong = 414 163 | /// - unsupportedMediaType: The request entity has a media type which the server or resource does not support. 164 | case unsupportedMediaType = 415 165 | /// - rangeNotSatisfiable: The client has asked for a portion of the file (byte serving), but the server cannot supply that portion. 166 | case rangeNotSatisfiable = 416 167 | /// - expectationFailed: The server cannot meet the requirements of the Expect request-header field. 168 | case expectationFailed = 417 169 | /// - teapot: This HTTP status is used as an Easter egg in some websites. 170 | case teapot = 418 171 | /// - misdirectedRequest: The request was directed at a server that is not able to produce a response. 172 | case misdirectedRequest = 421 173 | /// - unprocessableEntity: The request was well-formed but was unable to be followed due to semantic errors. 174 | case unprocessableEntity = 422 175 | /// - locked: The resource that is being accessed is locked. 176 | case locked = 423 177 | /// - failedDependency: The request failed due to failure of a previous request (e.g., a PROPPATCH). 178 | case failedDependency = 424 179 | /// - upgradeRequired: The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field. 180 | case upgradeRequired = 426 181 | /// - preconditionRequired: The origin server requires the request to be conditional. 182 | case preconditionRequired = 428 183 | /// - tooManyRequests: The user has sent too many requests in a given amount of time. 184 | case tooManyRequests = 429 185 | /// - requestHeaderFieldsTooLarge: The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large. 186 | case requestHeaderFieldsTooLarge = 431 187 | /// - noResponse: Used to indicate that the server has returned no information to the client and closed the connection. 188 | case noResponse = 444 189 | /// - unavailableForLegalReasons: A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource. 190 | case unavailableForLegalReasons = 451 191 | /// - SSLCertificateError: An expansion of the 400 Bad Request response code, used when the client has provided an invalid client certificate. 192 | case SSLCertificateError = 495 193 | /// - SSLCertificateRequired: An expansion of the 400 Bad Request response code, used when a client certificate is required but not provided. 194 | case SSLCertificateRequired = 496 195 | /// - HTTPRequestSentToHTTPSPort: An expansion of the 400 Bad Request response code, used when the client has made a HTTP request to a port listening for HTTPS requests. 196 | case HTTPRequestSentToHTTPSPort = 497 197 | /// - clientClosedRequest: Used when the client has closed the request before the server could send a response. 198 | case clientClosedRequest = 499 199 | /// - internalServerError: A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. 200 | case internalServerError = 500 201 | /// - notImplemented: The server either does not recognize the request method, or it lacks the ability to fulfill the request. 202 | case notImplemented = 501 203 | /// - badGateway: The server was acting as a gateway or proxy and received an invalid response from the upstream server. 204 | case badGateway = 502 205 | /// - serviceUnavailable: The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state. 206 | case serviceUnavailable = 503 207 | /// - gatewayTimeout: The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. 208 | case gatewayTimeout = 504 209 | /// - HTTPVersionNotSupported: The server does not support the HTTP protocol version used in the request. 210 | case HTTPVersionNotSupported = 505 211 | /// - variantAlsoNegotiates: Transparent content negotiation for the request results in a circular reference. 212 | case variantAlsoNegotiates = 506 213 | /// - insufficientStorage: The server is unable to store the representation needed to complete the request. 214 | case insufficientStorage = 507 215 | /// - loopDetected: The server detected an infinite loop while processing the request. 216 | case loopDetected = 508 217 | /// - notExtended: Further extensions to the request are required for the server to fulfill it. 218 | case notExtended = 510 219 | /// - networkAuthenticationRequired: The client needs to authenticate to gain network access. 220 | case networkAuthenticationRequired = 511 221 | /// The class (or group) which the status code belongs to. 222 | var responseType: ResponseType { 223 | switch self.rawValue { 224 | case 100..<200: 225 | return .informational 226 | case 200..<300: 227 | return .success 228 | case 300..<400: 229 | return .redirection 230 | case 400..<500: 231 | return .clientError 232 | case 500..<600: 233 | return .serverError 234 | default: 235 | return .undefined 236 | } 237 | } 238 | } 239 | 240 | extension HTTPURLResponse { 241 | var status: HTTPStatusCode? { 242 | return HTTPStatusCode(rawValue: statusCode) 243 | } 244 | /// This function check the HTTP response code 245 | func checkResponseCode() throws { 246 | let tmpStatusCode = HTTPStatusCode(rawValue: statusCode) 247 | if tmpStatusCode?.responseType == .success { 248 | } else if tmpStatusCode?.responseType == .clientError { 249 | try checksClientErrors() 250 | } else if tmpStatusCode?.responseType == .serverError { 251 | try checksServerErrors() 252 | } else { 253 | throw HTTPError.undefinedError 254 | } 255 | } 256 | /// This function checks all the HTTP error regarding the server 257 | private func checksServerErrors() throws { 258 | if status == .internalServerError { 259 | throw HTTPError.errorCode500InternalServerError 260 | } else if status == .notImplemented { 261 | throw HTTPError.errorCode501NotImplemented 262 | } else if status == .badGateway { 263 | throw HTTPError.errorCode502BadGateway 264 | } else if status == .serviceUnavailable { 265 | throw HTTPError.errorCode503ServiceUnavailable 266 | } else if status == .gatewayTimeout { 267 | throw HTTPError.errorCode504GatewayTimeout 268 | } else if status == .HTTPVersionNotSupported { 269 | throw HTTPError.errorCode505HTTPVersionNotSupported 270 | } else if status == .variantAlsoNegotiates { 271 | throw HTTPError.errorCode506VariantAlsoNegotiates 272 | } else if status == .insufficientStorage { 273 | throw HTTPError.errorCode507InsufficientStorage 274 | } else if status == .loopDetected { 275 | throw HTTPError.errorCode508LoopDetected 276 | } else if status == .notExtended { 277 | throw HTTPError.errorCode510NotExtended 278 | } else if status == .networkAuthenticationRequired { 279 | throw HTTPError.errorCode511NetworkAuthenticationRequired 280 | } 281 | } 282 | /// This function checks all the HTTP error regarding the client 283 | private func checksClientErrors() throws { 284 | if status == .badRequest { 285 | throw HTTPError.errorCode400BadRequest 286 | } else if status == .unauthorized { 287 | throw HTTPError.errorCode401Unauthorized 288 | } else if status == .paymentRequired { 289 | throw HTTPError.errorCode402PaymentRequired 290 | } else if status == .forbidden { 291 | throw HTTPError.errorCode403Forbidden 292 | } else if status == .notFound { 293 | throw HTTPError.errorCode404NotFound 294 | } else if status == .methodNotAllowed { 295 | throw HTTPError.errorCode405MethodNotAllowed 296 | } else if status == .notAcceptable { 297 | throw HTTPError.errorCode406NotAcceptable 298 | } else if status == .proxyAuthenticationRequired { 299 | throw HTTPError.errorCode407ProxyAuthenticationRequired 300 | } else if status == .requestTimeout { 301 | throw HTTPError.errorCode408RequestTimeout 302 | } else if status == .conflict { 303 | throw HTTPError.errorCode409Conflict 304 | } else if status == .gone { 305 | throw HTTPError.errorCode410Gone 306 | } else if status == .lengthRequired { 307 | throw HTTPError.errorCode411LenghtRequired 308 | } else if status == .preconditionFailed { 309 | throw HTTPError.errorCode412PreconditionFailed 310 | } else if status == .payloadTooLarge { 311 | throw HTTPError.errorCode413PayloadTooLarge 312 | } else if status == .URITooLong { 313 | throw HTTPError.errorCode414URITooLong 314 | } else if status == .unsupportedMediaType { 315 | throw HTTPError.errorCode415UnsupportedMediaType 316 | } else if status == .rangeNotSatisfiable { 317 | throw HTTPError.errorCode416RangeNotSatisfiable 318 | } else if status == .expectationFailed { 319 | throw HTTPError.errorCode417ExpectationFailed 320 | } else if status == .teapot { 321 | throw HTTPError.errorCode418Teapot 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderlyStudio/FMProKit/70c85f5cdf73bea8d8759b9a31f352540d6ad64b/Sources/FMProKit/FMDataAPI/.DS_Store -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/Extensions/CRUD/createRecordExtensionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 24/12/22. 6 | // 7 | 8 | import Foundation 9 | public extension FMDataAPI { 10 | /// The function that creates a new element inside a specified table 11 | /// - Parameters: 12 | /// - table: The name of the database table 13 | /// - data: The object of generic type that will be pushed into the database 14 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 15 | /// - Throws: an HTTPError.errorCode_500_internalServerError error when using the wrong table name or when inserting wrong data inside the table 16 | func createRecord(table: String, data: T) async throws { 17 | guard !table.isEmpty else { 18 | throw FMProErrors.tableNameMissing 19 | } 20 | 21 | let urlTmp = "\(baseUri)/layouts/\(table)/records" 22 | let insert = Insert(fieldData: data) 23 | 24 | do { 25 | _ = try await executeRequest(urlTmp: urlTmp, method: .post, data: insert) 26 | 27 | } catch HTTPError.errorCode401Unauthorized { 28 | try await fetchToken() 29 | try await createRecord(table: table, data: insert) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/Extensions/CRUD/deleteRecordExtensionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 24/12/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension FMDataAPI { 11 | /// The is used in order to delete a single record of a specific table using its recordID 12 | /// - Parameters: 13 | /// - table: The name of the table where is needed to delete the element 14 | /// - id: The recordID of the record to delete 15 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 16 | /// - Throws: an HTTPError.errorCode_500_internalServerError error when using the wrong table name or when inserting wrong data inside the table 17 | func deleteRecord(table: String, recordID: Int) async throws { 18 | if table.isEmpty { 19 | throw FMProErrors.tableNameMissing 20 | } 21 | 22 | let urlTmp = "\(baseUri)/layouts/\(table)/records/\(recordID)" 23 | 24 | do { 25 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 26 | 27 | } catch HTTPError.errorCode401Unauthorized { 28 | try await fetchToken() 29 | try await deleteRecord(table: table, recordID: recordID) 30 | } 31 | } 32 | /// The is used in order to delete all the records of a specific table corresponding to "data" matching data on the database 33 | /// - Parameters: 34 | /// - table: The name of the table where is needed to fetch the rows 35 | /// - data: It contains the data considered as a generic matching database data 36 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 37 | /// - Throws: an HTTPError.errorCode_500_internalServerError error when using the wrong table name or when inserting wrong data inside the table 38 | func deleteRecord(table: String, data: T) async throws { 39 | if table.isEmpty { 40 | throw FMProErrors.tableNameMissing 41 | } 42 | 43 | let recordsToDelete = try await findRecordIds(table: table, data: data) 44 | 45 | for delete in recordsToDelete { 46 | let urlTmp = "\(baseUri)/layouts/\(table)/records/\(delete)" 47 | 48 | do { 49 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 50 | 51 | } catch HTTPError.errorCode401Unauthorized { 52 | try await fetchToken() 53 | try await deleteRecord(table: table, data: data) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/Extensions/CRUD/editRecordExtensionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 24/12/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension FMDataAPI { 11 | /// The function is used to filter records and edit the ones that match the condition 12 | /// - Parameters: 13 | /// - table: The name of the table where we want to edit the data 14 | /// - findData: The data matching the record to change in the database 15 | /// - editData: The new data to overwrite 16 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 17 | /// - Throws: an HTTPError.errorCode_500_internalServerError error when using the wrong table name or when inserting wrong data inside the table 18 | func editRecord(table: String, findData: T, editData: T) async throws { 19 | if table.isEmpty { 20 | throw FMProErrors.tableNameMissing 21 | } 22 | 23 | let recordToEdit = try await findRecordIds(table: table, data: findData) 24 | 25 | for edit in recordToEdit { 26 | let url = "\(baseUri)/layouts/\(table)/records/\(edit)" 27 | let insert = Insert(fieldData: editData) 28 | 29 | do { 30 | _ = try await executeRequest(urlTmp: url, method: .patch, data: insert) 31 | 32 | } catch HTTPError.errorCode401Unauthorized { 33 | try await fetchToken() 34 | try await editRecord(table: table, findData: findData, editData: editData) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/Extensions/CRUD/getTableExtensionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 24/12/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension FMDataAPI { 11 | /// The function is used to get N record from a starting Offset in the defined table 12 | /// - Parameters: 13 | /// - table: The name of the table where is needed to fetch the rows 14 | /// - offset: The starting point from where fetching data 15 | /// - numberOfElements: The number of records that should be fetched 16 | /// - Returns: An array of Model type after fetching all the data 17 | func getTable(table: String, offset: Int = 1, numberOfElements: Int = 100) async throws -> [T] { 18 | if table.isEmpty { 19 | throw FMProErrors.tableNameMissing 20 | } 21 | 22 | let urlTmp = "\(baseUri)/layouts/\(table)/records?_offset=\(offset)&_limit=\(numberOfElements)" 23 | do { 24 | let data = try await executeRequest(urlTmp: urlTmp, method: .get,isData: true) 25 | let fetchedData = try JSONDecoder().decode(DataModel.self, from: data) 26 | return fetchedData.response.data.map { $0.fieldData } 27 | } catch HTTPError.errorCode401Unauthorized { 28 | try await fetchToken() 29 | return try await getTable(table: table, offset: offset, numberOfElements: numberOfElements) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/Extensions/metadataExtensionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 24/12/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension FMDataAPI { 11 | /// returns all the information related to the product 12 | /// - Returns: return a ProductInfo object 13 | func getProductInfo() async throws -> ProductInfo { 14 | let url = "https://\(hostname)/fmi/data/\(protocolVersion.rawValue)/productinfo" 15 | 16 | do { 17 | let data: Data = try await executeRequest(urlTmp: url, method: .get) 18 | let fetchedData = try JSONDecoder().decode(ProductInfoMetadata.self, from: data) 19 | 20 | return fetchedData.response.productInfo 21 | 22 | } catch HTTPError.errorCode401Unauthorized { 23 | try await fetchToken() 24 | return try await getProductInfo() 25 | } 26 | } 27 | 28 | /// returns all the names of the databases on the server 29 | /// - Returns: return a Databases object 30 | func getDBNames() async throws -> [Databases] { 31 | let url = "https://\(hostname)/fmi/data/\(protocolVersion.rawValue)/databases" 32 | 33 | do { 34 | let data: Data = try await executeRequest(urlTmp: url, method: .get) 35 | let fetchedData = try JSONDecoder().decode(DatabasesMetadata.self, from: data) 36 | 37 | return fetchedData.response.databases 38 | } catch HTTPError.errorCode401Unauthorized { 39 | try await fetchToken() 40 | return try await getDBNames() 41 | } 42 | } 43 | 44 | /// Return a list of all the scripts on the database. 45 | /// - Parameters: 46 | /// - database: The name of the database 47 | /// - Returns: an array of Script object 48 | func getScripts(database: String) async throws -> [Scripts] { 49 | let urlTmp = "https://\(hostname)/fmi/data/vLatest/databases/\(database)/scripts" 50 | 51 | do { 52 | let data: Data = try await executeRequest(urlTmp: urlTmp, method: .get) 53 | let fetchedData = try JSONDecoder().decode(ScrpitsMetaData.self, from: data) 54 | return fetchedData.response.scripts 55 | } catch HTTPError.errorCode401Unauthorized { 56 | try await fetchToken() 57 | return try await getScripts(database: database) 58 | } 59 | } 60 | 61 | /// Returns a list of all the layouts inside a database 62 | /// - Parameters: 63 | /// - database: The name of the database 64 | /// - Returns: An array of Layouts object 65 | func getLayouts(database: String) async throws -> [Layouts] { 66 | let urlTmp = "https://\(hostname)/fmi/data/vLatest/databases/\(database)/layouts" 67 | 68 | do { 69 | let data: Data = try await executeRequest(urlTmp: urlTmp, method: .get) 70 | let fetchedData = try JSONDecoder().decode(LayoutsMetaData.self, from: data) 71 | 72 | return fetchedData.response.layouts 73 | } catch HTTPError.errorCode401Unauthorized { 74 | try await fetchToken() 75 | return try await getLayouts(database: database) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/Extensions/openRequestExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 15/01/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension FMDataAPI { 11 | func executeRequest(request: URLRequest, data: T) async throws -> Data { 12 | switch request.httpMethod { 13 | case "POST": 14 | if request.description.contains("/_find") { 15 | let query = Query(query: [data]) 16 | do { 17 | let encoded = try JSONEncoder().encode(query) 18 | let (data, response) = try await URLSession.shared.upload(for: request, from: encoded) 19 | 20 | try (response as? HTTPURLResponse)?.checkResponseCode() 21 | 22 | return data 23 | } catch HTTPError.errorCode401Unauthorized { 24 | try await fetchToken() 25 | return try await executeRequest(request: request,data: data) 26 | } 27 | } else { 28 | fallthrough 29 | } 30 | default: 31 | let insert = Insert(fieldData: data) 32 | do { 33 | let encoded = try JSONEncoder().encode(insert) 34 | let (data, response) = try await URLSession.shared.upload(for: request, from: encoded) 35 | 36 | try (response as? HTTPURLResponse)?.checkResponseCode() 37 | 38 | return data 39 | }catch HTTPError.errorCode401Unauthorized { 40 | try await fetchToken() 41 | return try await executeRequest(request: request,data: data) 42 | } 43 | } 44 | } 45 | 46 | func executeRequest(request: URLRequest) async throws -> Data { 47 | switch request.httpMethod { 48 | case "GET": 49 | do { 50 | let (data, response) = try await URLSession.shared.data(for: self.request) 51 | 52 | try (response as? HTTPURLResponse)?.checkResponseCode() 53 | 54 | return data 55 | 56 | } catch HTTPError.errorCode401Unauthorized { 57 | try await fetchToken() 58 | return try await executeRequest(request: request) 59 | } 60 | case "DELETE": 61 | do { 62 | let (data, response) = try await URLSession.shared.data(for: request) 63 | 64 | try (response as? HTTPURLResponse)?.checkResponseCode() 65 | 66 | return data 67 | 68 | }catch HTTPError.errorCode401Unauthorized { 69 | try await fetchToken() 70 | return try await executeRequest(request: request) 71 | } 72 | default: 73 | do { 74 | let (data, response) = try await URLSession.shared.data(for: request) 75 | 76 | try (response as? HTTPURLResponse)?.checkResponseCode() 77 | return data 78 | } catch HTTPError.errorCode401Unauthorized { 79 | try await fetchToken() 80 | return try await executeRequest(request: request) 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/Extensions/scriptExtensionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 24/12/22. 6 | // 7 | 8 | import Foundation 9 | public extension FMDataAPI { 10 | /// Run a script created on the filemaker database 11 | /// - Parameters: 12 | /// - table: the name of the table 13 | /// - scriptName: the name of the script 14 | func runScript(table: String, scriptName: String) async throws -> ScriptDecoder { 15 | if table.isEmpty { 16 | throw FMProErrors.tableNameMissing 17 | } 18 | let urlTmp = "\(baseUri)/layouts/\(table)/script/\(scriptName)" 19 | 20 | do { 21 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 22 | return try JSONDecoder().decode(ScriptDecoder.self, from: data) 23 | } catch HTTPError.errorCode401Unauthorized { 24 | try await fetchToken() 25 | return try await runScript(table: table, scriptName: scriptName) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/Extensions/supportFunctionsExtensionData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 24/12/22. 6 | // 7 | 8 | import Foundation 9 | public extension FMDataAPI { 10 | /// The function is used to search records that match some given values 11 | /// - Parameters: 12 | /// - table: The name of the table where is needed to fetch the rows 13 | /// - data: Data that matches one or more records on the database 14 | /// - Returns: All the recordIDs that match the values 15 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 16 | /// - Throws: an HTTPError.errorCode_500_internalServerError error when using the wrong table name or when inserting wrong data inside the table 17 | func findRecordIds(table: String, data: T) async throws -> [String] { 18 | if table.isEmpty { 19 | throw FMProErrors.tableNameMissing 20 | } 21 | 22 | let url = "\(baseUri)/layouts/\(table)/_find" 23 | let query = Query(query: [data]) 24 | 25 | do { 26 | let fetchedData = try await executeRequest(urlTmp: url, method: .post, data: query) 27 | let fetchedIds = try JSONDecoder().decode(DataModel.self, from: fetchedData) 28 | 29 | return fetchedIds.response.data.map { $0.recordId } 30 | } catch { 31 | try await fetchToken() 32 | return try await findRecordIds(table: table, data: data) 33 | } 34 | } 35 | 36 | /// The function is used to search a record that matches a recordID 37 | /// - Parameters: 38 | /// - table: The name of the table where is needed to fetch the row 39 | /// - recordID: RecordID that matches one record on the database 40 | /// - Returns: An array with only one record matching the recordID 41 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 42 | /// - Throws: an HTTPError.errorCode_500_internalServerError error when using the wrong table name or when inserting wrong data inside the table 43 | func findRecordUsingID(table: String, recordID: String) async throws -> [T] { 44 | if table.isEmpty { 45 | throw FMProErrors.tableNameMissing 46 | } 47 | 48 | let url = "\(baseUri)/layouts/\(table)/records/\(recordID)" 49 | let data: Data 50 | 51 | do { 52 | data = try await executeRequest(urlTmp: url, method: .get) 53 | 54 | 55 | } catch { 56 | try await fetchToken() 57 | return try await findRecordUsingID(table: table, recordID: recordID) 58 | } 59 | 60 | let fetchedRecord = try JSONDecoder().decode(DataModel.self, from: data) 61 | 62 | return fetchedRecord.response.data.map { $0.fieldData } 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/FMDataAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FMDataAPI.swift 3 | // FilemakerAPI 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 06/10/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public class FMDataAPI: APIProtocol { 11 | /// a constant contains the server domain 12 | let hostname: String 13 | /// a constant contains the actual name of your database 14 | let database: String 15 | /// a variable is used to build the first common part URI for APIs 16 | let baseUri: String 17 | /// a constant contains the username credential to access the database 18 | var username: String 19 | /// a constant contains the password credential to access the database 20 | var password: String 21 | /// a variable contains the database credentials in the format "USERNAME:PASSWORD" 22 | var authData: String 23 | /// a variable contains the token that FileMaker generates to use the Data APIs 24 | var bearerToken: String 25 | /// a variable contains the HTTP request with headers, URL, AUTH and body 26 | var request: URLRequest 27 | /// a variable contains the HTTP response in JSON format 28 | var responseJSON = Data() 29 | /// the version that you want to use 30 | var protocolVersion: ProtocolVersion 31 | 32 | /// The class initializer used to setup all the information used to access and interface the Filemaker Database using OData. 33 | /// Once all the information has been collected the _BASE_URI_ is created using _HOSTNAME_ and _DATABASE_ meanwhile _USERNAME_ and _PASSWORD_ are used in order to create _AUTH_DATA_ 34 | /// All the parameters has been encoded using _PercentEncoding_ with _urlQueryAllowed_ 35 | /// - Parameters: 36 | /// - server: the URL of the Filemaker Database passed as a String 37 | /// - database: the Database name of the Filemaker Database passed as a String 38 | /// - username: the Username used to acces the Filemaker Database passed as a String 39 | /// - password: the Password used to acces the Filemaker Database passed as a String 40 | public required init(server: String, database: String, username: String, password: String, version: ProtocolVersion = .vLatest) { 41 | self.hostname = server 42 | self.database = database 43 | self.username = username 44 | self.password = password 45 | self.protocolVersion = version 46 | self.authData = "" 47 | self.baseUri = "https://\(hostname)/fmi/data/\(version.rawValue)/databases/\(database)" 48 | self.bearerToken = "" 49 | self.request = URLRequest(url: URL(string: "https://")!) 50 | self.request.setValue("Basic \(authData)", forHTTPHeaderField: "Authorization") 51 | } 52 | 53 | /// The function is used in order to retrieve the access token to the database 54 | func fetchToken() async throws { 55 | guard let stringURL = "https://\(self.hostname)/fmi/data/vLatest/databases/\(self.database)/sessions".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), 56 | let tokenRequestURL = URL(string: stringURL) else { 57 | throw URLError(.badURL) 58 | } 59 | 60 | guard let basicAuth = (username + ":" + password).data(using: .utf8)?.base64EncodedString() else { 61 | throw AuthError.authorizationEncodingError 62 | } 63 | 64 | var tokenRequest = URLRequest(url: tokenRequestURL) 65 | tokenRequest.addValue("Basic \(basicAuth)", forHTTPHeaderField: "Authorization") 66 | tokenRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") 67 | tokenRequest.httpMethod = "POST" 68 | 69 | let (data, response) = try await URLSession.shared.data(for: tokenRequest) 70 | 71 | guard (response as? HTTPURLResponse)?.statusCode != 401 else { 72 | throw FetchError.tokenRequestError 73 | } 74 | 75 | let fetchedData = try JSONDecoder().decode(Token.self, from: data) 76 | 77 | self.authData = "Bearer \(fetchedData.response?.token ?? "")" 78 | } 79 | 80 | /// Returns the last URLRequest called using the package 81 | /// - Returns: an URLRequest object 82 | public func getRequest() -> URLRequest { 83 | return request 84 | } 85 | 86 | // update and not set 87 | public func updateUsernameAndPassword(username: String, password: String) async throws { 88 | self.username = username 89 | self.password = password 90 | try await fetchToken() 91 | } 92 | 93 | /// Returns the last response in JSON format using the package 94 | /// - Returns: an Object of Data type 95 | public func getResponseJSON() -> Data { 96 | return responseJSON 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/JSON Helper/JSONFileFMData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONFileFMData.swift 3 | // FilemakerAPI 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 06/10/22. 6 | // 7 | 8 | import Foundation 9 | 10 | class DataModel: Codable { 11 | var response: Table 12 | var messages: [Message] 13 | } 14 | class MessagesModel: Codable { 15 | var messages: [Message] 16 | } 17 | 18 | class Table: Codable { 19 | var data: [FieldData] 20 | var dataInfo: DataInfo 21 | } 22 | 23 | public class FieldData: Codable { 24 | var fieldData: T 25 | var recordId: String 26 | var modId: String 27 | } 28 | 29 | class Query: Codable { 30 | var query: [T] 31 | 32 | public init(query: [T]) { 33 | self.query = query 34 | } 35 | } 36 | 37 | class Insert: Codable { 38 | var fieldData: T 39 | 40 | public init(fieldData: T) { 41 | self.fieldData = fieldData 42 | } 43 | } 44 | 45 | class DataInfo: Codable { 46 | var database: String 47 | var layout: String 48 | var table: String 49 | var totalRecordCount: Int 50 | var foundCount: Int 51 | var returnedCount: Int 52 | } 53 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/JSON Helper/MetadataModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 08/12/22. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - ProductInfoMetadata 11 | public struct ProductInfoMetadata: Codable { 12 | let response: Response 13 | let messages: [Message] 14 | } 15 | 16 | // MARK: - Message 17 | public struct Message: Codable { 18 | let code, message: String 19 | } 20 | 21 | // MARK: - Response 22 | public struct Response: Codable { 23 | let productInfo: ProductInfo 24 | } 25 | 26 | // MARK: - ProductInfo 27 | public struct ProductInfo: Codable { 28 | let name, buildDate, version, dateFormat: String 29 | let timeFormat, timeStampFormat: String 30 | } 31 | 32 | // MARK: - DatabasesMetadata 33 | struct DatabasesMetadata: Codable { 34 | let response: ResponseDatabase 35 | } 36 | 37 | // MARK: - Response 38 | struct ResponseDatabase: Codable { 39 | let databases: [Databases] 40 | } 41 | 42 | // MARK: - Databasis 43 | public struct Databases: Codable { 44 | let name: String 45 | } 46 | 47 | public struct ScrpitsMetaData: Codable { 48 | let response: ResponseScripts 49 | let messages: [Message] 50 | } 51 | 52 | public struct ResponseScripts: Codable { 53 | let scripts: [Scripts] 54 | } 55 | 56 | public struct LayoutsMetaData: Codable { 57 | let response: ResponseLayouts 58 | let messages: [Message] 59 | } 60 | 61 | public struct ResponseLayouts: Codable { 62 | let layouts: [Layouts] 63 | } 64 | 65 | public class Scripts: Codable { 66 | var name: String 67 | var isFolder: Bool 68 | var folderScriptNames: [Scripts]? 69 | } 70 | 71 | public class Layouts: Codable { 72 | var name: String 73 | var table: String? 74 | var isFolder: Bool? 75 | var folderLayoutNames: [Layouts]? 76 | } 77 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/JSON Helper/Script.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 28/03/23. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - ScriptDecoder 11 | public struct ScriptDecoder: Codable { 12 | let response: ScriptResponse 13 | let messages: [Message] 14 | } 15 | 16 | // MARK: - Response 17 | struct ScriptResponse: Codable { 18 | let scriptError: String 19 | } 20 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMDataAPI/JSON Helper/TokenModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenModel.swift 3 | // FilemakerAPI 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 06/10/22. 6 | // 7 | 8 | import Foundation 9 | 10 | class TokenModel: Codable { 11 | var token: String? 12 | } 13 | 14 | class Token: Codable { 15 | var response: TokenModel? 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/CRUD/createRecordExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// Create a new record inside a specific table 12 | /// - Parameters: 13 | /// - table: The name of the table in wich perform the action 14 | /// - data: The generic object that will be inserted in the table 15 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 16 | /// - Throws: an HTTPError.errorCode_400_badRequest error when using the wrong table name or when inserting wrong data inside the table 17 | func createRecord(table: String, data: T) async throws { 18 | guard !table.isEmpty else { 19 | throw FMProErrors.tableNameMissing 20 | } 21 | 22 | let urlTmp = "\(baseUri)/\(table)" 23 | 24 | _ = try await executeRequest(urlTmp: urlTmp, method: .post, data: data) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/CRUD/deleteRecordExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// Delete the record inside a specific table using its id 12 | /// - Parameters: 13 | /// - table: The name of the table in wich perform the action 14 | /// - id: The Primary key (PK) of the searched record 15 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the record is missing 16 | /// - Throws: an HTTPError.errorCode_400_badRequest error when using the wrong data, or model, to update the table 17 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 18 | /// - Attention: This function is for Text id and not for the Numeric ones 19 | func deleteRecord(table: String, id: String) async throws { 20 | guard !table.isEmpty else { 21 | throw FMProErrors.tableNameMissing 22 | } 23 | 24 | let urlTmp = "\(baseUri)/\(table)('\(id)')" 25 | 26 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 27 | } 28 | 29 | /// Delete the record inside a specific table using its id 30 | /// - Parameters: 31 | /// - table: The name of the table in wich perform the action 32 | /// - id: The Primary key (PK) of the searched record 33 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the record is missing 34 | /// - Throws: an HTTPError.errorCode_400_badRequest error when using the wrong data, or model, to update the table 35 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 36 | /// - Attention: This function is for UUID id and not for the Number or Text ones 37 | func deleteRecord(table: String, id: UUID) async throws { 38 | guard !table.isEmpty else { 39 | throw FMProErrors.tableNameMissing 40 | } 41 | 42 | let urlTmp = "\(baseUri)/\(table)('\(id.uuidString)')" 43 | 44 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 45 | } 46 | 47 | /// Delete the record inside a specific table using its id 48 | /// - Parameters: 49 | /// - table: The name of the table in wich perform the action 50 | /// - id: The Primary key (PK) of the searched record 51 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the record is missing 52 | /// - Throws: an HTTPError.errorCode_400_badRequest error when using the wrong data, or model, to update the table 53 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 54 | /// - Attention: This function is for Numeric id and not for the Text ones 55 | func deleteRecord(table: String, id: Int) async throws { 56 | 57 | guard !table.isEmpty else { 58 | throw FMProErrors.tableNameMissing 59 | } 60 | 61 | let urlTmp = "\(baseUri)/\(table)(\(id))" 62 | 63 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 64 | } 65 | 66 | /// Delete all the records inside a specific table matching a query 67 | /// - Parameters: 68 | /// - table: The name of the table in wich perform the action 69 | /// - query: An OData query used to filter the API call 70 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the apex are not used correctly or in general the query is not correct 71 | /// - Throws: an HTTPError.errorCode_404_notFound error when the table name is not correct 72 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 73 | /// - Attention: the _?_ after the table name is already inserted 74 | /// - Attention: when inserting a filter clause write it this way: “$filter= conditions”
Specify that condition should be defined this way: “searchedField confrontationOperator value” 75 | /// - Attention: in case of a Text, value must be in write in single quotes like this: ‘value’ , in case of a Number it doesn't need quotes. 76 | func deleteRecordUsingQuery(table: String, query: String) async throws { 77 | guard !table.isEmpty else { 78 | throw FMProErrors.tableNameMissing 79 | } 80 | 81 | let urlTmp = "\(baseUri)/\(table)?\(query)" 82 | 83 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 84 | } 85 | 86 | /// Delete all the records inside a specific table matching a filter query 87 | /// - Parameters: 88 | /// - table: The name of the table in wich perform the action 89 | /// - field: The field used to filter the table 90 | /// - value: The value of the field that need to match in the query 91 | /// - filterOption: The filter option for the query 92 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the apex are not used correctly or in general the query is not correct, the name of the field is not correct or missing 93 | /// - Throws: an HTTPError.errorCode_404_notFound error when The name of the table in wich perform the action is wrong 94 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 95 | func deleteRecord(table: String, field: String, filterOption: FilterOption, value: T) async throws { 96 | guard !table.isEmpty else { 97 | throw FMProErrors.tableNameMissing 98 | } 99 | 100 | var urlTmp = "" 101 | if var _ = value as? String { 102 | urlTmp = "\(baseUri)/\(table)?$filter= \(field) \(filterOption.rawValue) '\(value)'" 103 | } else { 104 | urlTmp = "\(baseUri)/\(table)?$filter= \(field) \(filterOption.rawValue) \(value)" 105 | } 106 | 107 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/CRUD/editRecordExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// Edit a record inside the specified table using its id 12 | /// - Parameters: 13 | /// - record: the record of model T type with all the new data 14 | /// - table: The name of the table in wich perform the action 15 | /// - id: The Primary key (PK) of the searched record 16 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the record is missing 17 | /// - Throws: an HTTPError.errorCode_400_badRequest error when using the wrong data, or model, to update the table 18 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 19 | /// - Attention: This function is for Text id and not for the Number ones 20 | func editRecord(table: String, id: String, data: T) async throws { 21 | guard !table.isEmpty else { 22 | throw FMProErrors.tableNameMissing 23 | } 24 | 25 | let urlTmp = "\(baseUri)/\(table)('\(id)')" 26 | 27 | _ = try await executeRequest(urlTmp: urlTmp, method: .patch, data: data) 28 | } 29 | 30 | /// Edit a record inside the specified table using its id 31 | /// - Parameters: 32 | /// - record: the record of model T type with all the new data 33 | /// - table: The name of the table in wich perform the action 34 | /// - id: The Primary key (PK) of the searched record 35 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the record is missing 36 | /// - Throws: an HTTPError.errorCode_400_badRequest error when using the wrong data, or model, to update the table 37 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 38 | /// - Attention: This function is for Text id and not for the Number ones 39 | func editRecord(table: String, id: UUID, data: T) async throws { 40 | 41 | guard !table.isEmpty else { 42 | throw FMProErrors.tableNameMissing 43 | } 44 | 45 | let urlTmp = "\(baseUri)/\(table)('\(id.uuidString)')" 46 | 47 | _ = try await executeRequest(urlTmp: urlTmp, method: .patch, data: data) 48 | } 49 | 50 | /// Edit a record inside the specified table using its id 51 | /// - Parameters: 52 | /// - record: the record of model T type with all the new data 53 | /// - table: The name of the table in wich perform the action 54 | /// - id: The Primary key (PK) of the searched record 55 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the record is missing 56 | /// - Throws: an HTTPError.errorCode_400_badRequest error when using the wrong data, or model, to update the table 57 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 58 | func editRecord(table: String, id: Int, data: T) async throws { 59 | guard !table.isEmpty else { 60 | throw FMProErrors.tableNameMissing 61 | } 62 | 63 | let urlTmp = "\(baseUri)/\(table)(\(id))" 64 | 65 | _ = try await executeRequest(urlTmp: urlTmp, method: .patch, data: data) 66 | } 67 | 68 | /// Edit all the records inside the specified table matching a query 69 | /// - Parameters: 70 | /// - record: the record of model T type with all the new data 71 | /// - table: The name of the table in wich perform the action 72 | /// - query: An OData query used to filter the API call 73 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the apex are not used correctly or in general the query is not correct 74 | /// - Throws: an HTTPError.errorCode_404_notFound error when the table name is not correct 75 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 76 | /// - Attention: the _?_ after the table name is already inserted 77 | /// - Attention: when inserting a filter clause write it this way: “$filter= conditions”
Specify that condition should be defined this way: “searchedField confrontationOperator value” 78 | /// - Attention: value is ‘value’ in case of a Text meanwhile value in case of a Number 79 | func editRecordUsingQuery(table: String, query: String, data: T) async throws { 80 | 81 | guard !table.isEmpty else { 82 | throw FMProErrors.tableNameMissing 83 | } 84 | 85 | let urlTmp = "\(baseUri)/\(table)?\(query)" 86 | 87 | _ = try await executeRequest(urlTmp: urlTmp, method: .patch, data: data) 88 | } 89 | 90 | /// Edit all the records inside the specified table matching a filter query 91 | /// - Parameters: 92 | /// - record: the record of model T type with all the new data 93 | /// - table: The name of the table in wich perform the action 94 | /// - query: An OData query used to filter the API call 95 | /// - field: The field used to filter the table 96 | /// - value: The value of the field that need to match in the query 97 | /// - filterOption: The filter option for the query 98 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the apex are not used correctly or in general the query is not correct, the name of the field is not correct or missing 99 | /// - Throws: an HTTPError.errorCode_404_notFound error when The name of the table in wich perform the action is wrong 100 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 101 | /// - Throws: a FMProErrors.fieldNameMissing error when the field parameter is empty 102 | func editRecord(table: String, field: String, filterOption: FilterOption, value: U, data: T) async throws { 103 | 104 | guard !table.isEmpty else { 105 | throw FMProErrors.tableNameMissing 106 | } 107 | guard !field.isEmpty else { 108 | throw FMProErrors.fieldNameMissing 109 | } 110 | 111 | var urlTmp = "" 112 | if var _ = value as? String { 113 | urlTmp = "\(baseUri)/\(table)?$filter= \(field) \(filterOption.rawValue) '\(value)'" 114 | } else { 115 | urlTmp = "\(baseUri)/\(table)?$filter= \(field) \(filterOption.rawValue) \(value)" 116 | } 117 | 118 | _ = try await executeRequest(urlTmp: urlTmp, method: .patch, data: data) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/CRUD/getTableExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// Fetches all the records inside a specific table and it decodes them using a struct/class 12 | /// - Parameters: 13 | /// - table: The name of the table in wich perform the action 14 | /// - Returns: An array of the generic type used to decode the records 15 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name 16 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 17 | func getTable(table: String) async throws -> [T] { 18 | guard !table.isEmpty else { 19 | throw FMProErrors.tableNameMissing 20 | } 21 | 22 | return try await executeQueryGet(query: "\(table)") 23 | } 24 | 25 | /// Fetches all the records inside a specific table matching a query and it decodes them using a struct/class 26 | /// - Parameters: 27 | /// - table: The name of the table in wich perform the action 28 | /// - query: An OData query used to filter the API call 29 | /// - Returns: An array of the generic type used to decode the records 30 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the apex are not used correctly or in general the query is not correct 31 | /// - Throws: an HTTPError.errorCode_404_notFound error when the table name is not correct 32 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 33 | /// - Attention: the _?_ after the table name is already inserted 34 | /// - Attention: when inserting a filter clause write it this way: “$filter= conditions”
Specify that condition should be defined this way: “searchedField confrontationOperator value” 35 | /// - Attention: value is ‘value’ in case of a Text meanwhile value in case of a Number 36 | func getTable(table: String, query: String) async throws -> [T] { 37 | 38 | guard !table.isEmpty else { 39 | throw FMProErrors.tableNameMissing 40 | } 41 | 42 | let urlTmp = "\(baseUri)/\(table)?\(query)" 43 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 44 | 45 | return try decodeJSONArray(data: data) 46 | } 47 | 48 | /// Fetches all the records inside a specific table matching a filter query and it decodes them using a struct/class 49 | /// - Parameters: 50 | /// - table: The name of the table in wich perform the action 51 | /// - field: The field used to filter the table 52 | /// - value: The value of the field that need to match in the query 53 | /// - filterOption: The filter option for the query 54 | /// - Returns: An array of the generic type used to decode the records 55 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the apex are not used correctly or in general the query is not correct, the name of the field is not correct or missing 56 | /// - Throws: an HTTPError.errorCode_404_notFound error when The name of the table in wich perform the action is wrong 57 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 58 | func getTable(table: String, field: String, filterOption: FilterOption, value: U) async throws -> [T] { 59 | guard !table.isEmpty else { 60 | throw FMProErrors.tableNameMissing 61 | } 62 | 63 | var urlTmp = "" 64 | if var _ = value as? String { 65 | urlTmp = "\(baseUri)/\(table)?$filter= \(field) \(filterOption.rawValue) '\(value)'" 66 | } else { 67 | urlTmp = "\(baseUri)/\(table)?$filter= \(field) \(filterOption.rawValue) \(value)" 68 | } 69 | 70 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 71 | 72 | return try decodeJSONArray(data: data) 73 | } 74 | 75 | /// Fetches all the records inside a specific table matching an order query and it decodes them using a struct/class 76 | /// - Parameters: 77 | /// - table: The name of the table in wich perform the action 78 | /// - fieldName: The name of the field 79 | /// - order: The order the field will be returned (desc/asc) 80 | /// - Returns: An array of the generic type used to decode the records 81 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the name of the field is not correct or missing 82 | /// - Throws: an HTTPError.errorCode_404_notFound error when The name of the table in wich perform the action is wrong 83 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 84 | func getTable(table: String, fieldName: String, order: Order) async throws -> [T] { 85 | guard !table.isEmpty else { 86 | throw FMProErrors.tableNameMissing 87 | } 88 | 89 | let urlTmp = "\(baseUri)/\(table)?$orderby=\(fieldName) \(order.rawValue)" 90 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 91 | 92 | return try decodeJSONArray(data: data) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/executeQueryExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// Execute a generic Delete query 12 | /// - Parameter query: An OData query used to filter the API call 13 | func executeQueryDelete(query: String) async throws { 14 | let urlTmp = "\(baseUri)/\(query)" 15 | 16 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 17 | } 18 | 19 | /// Execute a generic Get query 20 | /// - Parameter query: An OData query used to filter the API call 21 | /// - Returns: An array of Model type after fetching all the data 22 | func executeQueryGet(query: String) async throws -> [T] { 23 | let urlTmp = "\(baseUri)/\(query)" 24 | 25 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 26 | 27 | let fetchedData = try JSONDecoder().decode(JSONValue.self, from: data) 28 | return fetchedData.value 29 | } 30 | 31 | /// Execute a generic Patch query 32 | /// - Parameters: 33 | /// - query: An OData query used to filter the API call 34 | /// - record: the record of model T type with all the changes 35 | func executeQueryPatch(query: String, data: T) async throws { 36 | let urlTmp = "\(baseUri)/\(query)" 37 | 38 | _ = try await executeRequest(urlTmp: urlTmp, method: .patch, data: data) 39 | } 40 | 41 | /// Execute a generic Post query 42 | /// - Parameters: 43 | /// - query: An OData query used to filter the API call 44 | /// - data: It contains the data considered as a generic 45 | func executeQueryPost(query: String, data: T) async throws { 46 | let url = "\(baseUri)/\(query)" 47 | 48 | _ = try await executeRequest(urlTmp: url, method: .post, data: data) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/getFromTableExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// Fetches the first N records inside a specific table and it decodes them using a struct/class 12 | /// - Parameters: 13 | /// - table: The name of the table in wich perform the action 14 | /// - number: The number of element 15 | /// - Returns: An array of the generic type used to decode the records 16 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name 17 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 18 | /// - Throws: a FMProErrors.negativeNumber error when the number parameter is a negative number 19 | func getTopTable(table: String, number: Int) async throws -> [T] { 20 | 21 | guard !table.isEmpty else { 22 | throw FMProErrors.tableNameMissing 23 | } 24 | guard number >= 0 else { 25 | throw FMProErrors.negativeNumber 26 | } 27 | 28 | let urlTmp = "\(baseUri)/\(table)?$top=\(number)" 29 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 30 | 31 | return try decodeJSONArray(data: data) 32 | } 33 | 34 | /// Fetches all the records except the first N records inside a specific table and it decodes them using a struct/class 35 | /// - Parameters: 36 | /// - table: The name of the table in wich perform the action where is needed to fetch the rows 37 | /// - number: The number of element wanted to skip 38 | /// - Returns: An array of Model type after fetching all the data 39 | /// - Returns: An array of the generic type used to decode the records 40 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name 41 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 42 | /// - Throws: a FMProErrors.negativeNumber error when the number parameter is a negative number 43 | func getSkipTable(table: String, number: Int) async throws -> [T] { 44 | 45 | guard !table.isEmpty else { 46 | throw FMProErrors.tableNameMissing 47 | } 48 | guard number >= 0 else { 49 | throw FMProErrors.negativeNumber 50 | } 51 | 52 | let urlTmp = "\(baseUri)/\(table)?$skip=\(number)" 53 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 54 | 55 | return try decodeJSONArray(data: data) 56 | } 57 | 58 | /// Fetch a record using its id and it decodes it using a struct/class 59 | /// - Parameters: 60 | /// - table: The name of the table in wich perform the action 61 | /// - id: The Primary key (PK) of the searched record 62 | /// - Returns: return a single object of the generic type used to decode the records 63 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the recordId doesn't exist 64 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 65 | func getRecord(table: String, id: String) async throws -> T { 66 | 67 | guard !table.isEmpty else { 68 | throw FMProErrors.tableNameMissing 69 | } 70 | 71 | let urlTmp = "\(baseUri)/\(table)('\(id)')" 72 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 73 | 74 | return try decodeJSONSingleValue(data: data) 75 | } 76 | 77 | /// Fetch a record using its id and it decodes it using a struct/class 78 | /// - Parameters: 79 | /// - table: The name of the table in wich perform the action 80 | /// - id: The Primary key (PK) of the searched record 81 | /// - Returns: return a single object of the generic type used to decode the records 82 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the recordId doesn't exist 83 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 84 | func getRecord(table: String, id: UUID) async throws -> T { 85 | 86 | guard !table.isEmpty else { 87 | throw FMProErrors.tableNameMissing 88 | } 89 | 90 | let urlTmp = "\(baseUri)/\(table)('\(id.uuidString)')" 91 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 92 | 93 | return try decodeJSONSingleValue(data: data) 94 | } 95 | 96 | /// Fetch a record using its id and it decodes it using a struct/class 97 | /// - Parameters: 98 | /// - table: The name of the table in wich perform the action 99 | /// - id: The Primary key (PK) of the searched record 100 | /// - Returns: return a single object of the generic type used to decode the records 101 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name or the recordId doesn't exist 102 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 103 | func getRecord(table: String, id: Int) async throws -> T { 104 | 105 | guard !table.isEmpty else { 106 | throw FMProErrors.tableNameMissing 107 | } 108 | 109 | let urlTmp = "\(baseUri)/\(table)(\(id))" 110 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 111 | 112 | return try decodeJSONSingleValue(data: data) 113 | } 114 | 115 | /// Retrieves the number of element inside a table 116 | /// - Parameter table: The name of the table in wich perform the action 117 | /// - Returns: The number of elements inside the table 118 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name 119 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 120 | func getTableCount(table: String) async throws -> Int { 121 | guard !table.isEmpty else { 122 | throw FMProErrors.tableNameMissing 123 | } 124 | 125 | let urlTmp = "\(baseUri)/\(table)/$count" 126 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 127 | 128 | return try JSONDecoder().decode(Int.self, from: data) 129 | } 130 | 131 | /// Retrieves the value of a field of a specific record 132 | /// - Parameters: 133 | /// - table: The name of the table in wich perform the action 134 | /// - id: The Primary key (PK) of the searched record 135 | /// - field: The name of the field 136 | /// - Returns: return a single object of the generic type used to decode the records 137 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name, the wrong field name or the recordId doesn't exist 138 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 139 | func getField(table: String, id: String, field: String) async throws -> T { 140 | guard !table.isEmpty else { 141 | throw FMProErrors.tableNameMissing 142 | } 143 | guard !field.isEmpty else { 144 | throw FMProErrors.fieldNameMissing 145 | } 146 | 147 | let urlTmp = "\(baseUri)/\(table)('\(id)')/\(field)" 148 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 149 | 150 | return try decodeJSONSingleValue(data: data) 151 | } 152 | 153 | /// Retrieves the value of a field of a specific record 154 | /// - Parameters: 155 | /// - table: The name of the table in wich perform the action 156 | /// - id: The Primary key (PK) of the searched record 157 | /// - field: The name of the field 158 | /// - Returns: return a single object of the generic type used to decode the records 159 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name, the wrong field name or the recordId doesn't exist 160 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 161 | func getField(table: String, id: UUID, field: String) async throws -> T { 162 | guard !table.isEmpty else { 163 | throw FMProErrors.tableNameMissing 164 | } 165 | 166 | guard !field.isEmpty else { 167 | throw FMProErrors.fieldNameMissing 168 | } 169 | 170 | let urlTmp = "\(baseUri)/\(table)('\(id.uuidString)')/\(field)" 171 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 172 | 173 | return try decodeJSONSingleValue(data: data) 174 | } 175 | 176 | /// Retrieves the value of a field of a specific record 177 | /// - Parameters: 178 | /// - table: The name of the table in wich perform the action 179 | /// - id: The Primary key (PK) of the searched record 180 | /// - field: The name of the field 181 | /// - Returns: return a single object of the generic type used to decode the records 182 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name, the wrong field name or the recordId doesn't exist 183 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 184 | func getField(table: String, id: Int, field: String) async throws -> T { 185 | guard !table.isEmpty else { 186 | throw FMProErrors.tableNameMissing 187 | } 188 | 189 | guard !field.isEmpty else { 190 | throw FMProErrors.fieldNameMissing 191 | } 192 | 193 | let urlTmp = "\(baseUri)/\(table)(\(id))/\(field)" 194 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 195 | 196 | return try decodeJSONSingleValue(data: data) 197 | } 198 | 199 | /// Retrieves the value of a field of a specific record as a binary Data 200 | /// - Parameters: 201 | /// - table: The name of the table in wich perform the action 202 | /// - id: The Primary key (PK) of the searched record 203 | /// - field: The name of the field 204 | /// - Returns: return a single object of type Data 205 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name, the wrong field name or the recordId doesn't exist 206 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 207 | func getBinaryField(table: String, id: String, field: String) async throws -> Data { 208 | guard !table.isEmpty else { 209 | throw FMProErrors.tableNameMissing 210 | } 211 | 212 | guard !field.isEmpty else { 213 | throw FMProErrors.fieldNameMissing 214 | } 215 | 216 | let urlTmp = "\(baseUri)/\(table)('\(id)')/\(field)/$value" 217 | 218 | return try await executeRequest(urlTmp: urlTmp, method: .get) 219 | } 220 | 221 | /// Retrieves the value of a field of a specific record as a binary Data 222 | /// - Parameters: 223 | /// - table: The name of the table in wich perform the action 224 | /// - id: The Primary key (PK) of the searched record 225 | /// - field: The name of the field 226 | /// - Returns: return a single object of type Data 227 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name, the wrong field name or the recordId doesn't exist 228 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 229 | func getBinaryField(table: String, id: UUID, field: String) async throws -> Data { 230 | guard !table.isEmpty else { 231 | throw FMProErrors.tableNameMissing 232 | } 233 | 234 | guard !field.isEmpty else { 235 | throw FMProErrors.fieldNameMissing 236 | } 237 | 238 | let urlTmp = "\(baseUri)/\(table)('\(id.uuidString)')/\(field)/$value" 239 | 240 | return try await executeRequest(urlTmp: urlTmp, method: .get) 241 | } 242 | 243 | /// Retrieves the value of a field of a specific record as a binary Data 244 | /// - Parameters: 245 | /// - table: The name of the table in wich perform the action 246 | /// - id: The Primary key (PK) of the searched record 247 | /// - field: The name of the field 248 | /// - Returns: return a single object of type Data 249 | /// - Throws: an HTTPError.errorCode_404_notFound error when using the wrong table name, the wrong field name or the recordId doesn't exist 250 | /// - Throws: a FMProErrors.tableNameMissing error when the table parameter is empty 251 | func getBinaryField(table: String, id: Int, field: String) async throws -> Data { 252 | guard !table.isEmpty else { 253 | throw FMProErrors.tableNameMissing 254 | } 255 | 256 | guard !field.isEmpty else { 257 | throw FMProErrors.fieldNameMissing 258 | } 259 | 260 | let urlTmp = "\(baseUri)/\(table)(\(id))/\(field)/$value" 261 | 262 | return try await executeRequest(urlTmp: urlTmp, method: .get) 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/getMetadataExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// Return all the Metadata saved inside the database as Data. The data are encoded as an XML file 12 | /// - Returns: the encoded XML file 13 | func getMetadataAsData() async throws -> Data { 14 | let urlTmp = "\(baseUri)/$metadata" 15 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 16 | 17 | return data 18 | } 19 | 20 | /// Return all the Metadata saved inside the database as String 21 | /// - Returns: the XML file as a string 22 | func getMetadataAsString() async throws -> String? { 23 | let urlTmp = "\(baseUri)/$metadata" 24 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 25 | 26 | return String(data: data, encoding: String.Encoding.utf8) 27 | } 28 | 29 | /// Returns an array of TableValue that contains all the informations of all the tables inside the database 30 | /// - Returns: Returns an array of TableValue that contains all the informations of all the tables inside the database 31 | func getListOfTables() async throws -> [TableValue] { 32 | let urlTmp = "\(baseUri)" 33 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 34 | 35 | return try decodeJSONArray(data: data) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/joinTableExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// The function handles a simple join using the passed table in input based on the id of the record 12 | /// - Parameters: 13 | /// - table: The name of the table where is needed to fetch the rows 14 | /// - id: The id of the specified record 15 | /// - otherTables: The name of the second table where is needed to fetch the rows 16 | /// - Returns: An array of Model type after fetching all the data 17 | func getRelations(table: String, id: String, otherTables: String...) async throws -> [T] { 18 | guard !table.isEmpty && !otherTables.isEmpty && !otherTables.first!.isEmpty else { 19 | throw FMProErrors.tableNameMissing 20 | } 21 | 22 | var urlTmp = "\(baseUri)/\(table)('\(id)')" 23 | for otherTable in otherTables { 24 | let otherTableFormatted = otherTable 25 | urlTmp += "/\(otherTableFormatted)" 26 | } 27 | 28 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 29 | return try decodeJSONArray(data: data) 30 | } 31 | /// The function handles a simple join using the passed table in input based on the id of the record 32 | /// - Parameters: 33 | /// - table: The name of the table where is needed to fetch the rows 34 | /// - id: The id of the specified record 35 | /// - otherTables: The name of the second table where is needed to fetch the rows 36 | /// - Returns: An array of Model type after fetching all the data 37 | func getRelations(table: String, id: UUID, otherTables: String...) async throws -> [T] { 38 | guard !table.isEmpty && !otherTables.isEmpty && !otherTables.first!.isEmpty else { 39 | throw FMProErrors.tableNameMissing 40 | } 41 | 42 | var urlTmp = "\(baseUri)/\(table)('\(id.uuidString)')" 43 | for otherTable in otherTables { 44 | let otherTableFormatted = otherTable 45 | urlTmp += "/\(otherTableFormatted)" 46 | } 47 | 48 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 49 | return try decodeJSONArray(data: data) 50 | } 51 | func getRelations(table: String, id: Int, otherTables: String...) async throws -> [T] { 52 | guard !table.isEmpty && !otherTables.isEmpty && !otherTables.first!.isEmpty else { 53 | throw FMProErrors.tableNameMissing 54 | } 55 | 56 | var urlTmp = "\(baseUri)/\(table)(\(id))" 57 | for otherTable in otherTables { 58 | let otherTableFormatted = otherTable 59 | urlTmp += "/\(otherTableFormatted)" 60 | } 61 | 62 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 63 | return try decodeJSONArray(data: data) 64 | } 65 | /// The function handles a simple join using the passed table in input and a specified query 66 | /// - Parameters: 67 | /// - listOfTables: a list of the names of the tables needed for the crossJoin query 68 | /// - fetchedTable: the name of the table used to fetch all the data (must be the one used to decode in the model) 69 | /// - query: An OData query used to filter the API call 70 | /// - Returns: An array of Model type after fetching all the data 71 | func getTableCrossJoin(listOfTables: [String], fetchedTable: String, query: String) async throws -> [T] { 72 | var crossedJoinVariables = "" 73 | var tmpArray = listOfTables 74 | crossedJoinVariables.append(tmpArray.removeFirst()) 75 | for table in tmpArray { 76 | crossedJoinVariables = crossedJoinVariables + "," + table 77 | } 78 | let urlTmp = "\(baseUri)/$crossjoin(\(crossedJoinVariables))?$\(query)&$expand=\(fetchedTable)($select=*)" 79 | 80 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 81 | return try decodeJSONArray(data: data) 82 | } 83 | /// The function handles a simple join using the passed table in input and a specified query 84 | /// - Parameters: 85 | /// - listOfTables: a list of the names of the tables needed for the crossJoin query 86 | /// - fetchedTable: the name of the table used to fetch all the data (must be the one used to decode in the model) 87 | /// - Returns: An array of Model type after fetching all the data 88 | func getTableCrossJoin(listOfTables: [String], fetchedTable: String) async throws -> [T] { 89 | var crossedJoinVariables = "" 90 | var tmpArray = listOfTables 91 | crossedJoinVariables.append(tmpArray.removeFirst()) 92 | for table in tmpArray { 93 | crossedJoinVariables = crossedJoinVariables + "," + table 94 | } 95 | let urlTmp = "\(baseUri)/$crossjoin(\(crossedJoinVariables))?$$expand=\(fetchedTable)($select=*)" 96 | 97 | let data = try await executeRequest(urlTmp: urlTmp, method: .get) 98 | return try decodeJSONArray(data: data) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/modifyingSchemaExtensionOData.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public extension FMODataAPI { 4 | /// Create inside the database schema a new table using the tableName and a list of columns with the properties 5 | /// - Parameters: 6 | /// - tableName: the name of the table 7 | /// - listOfColumns: the list of columns 8 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the name of table already exist or someone else is modifying the schema already 9 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 10 | /// - Throws: a CommonErrors.emptyListOfFields error when the listOfColumns parameter is empty 11 | func createNewTable(tableName: String, listOfColumns: [CreationTableField]) async throws { 12 | guard !listOfColumns.isEmpty else { 13 | throw FMProErrors.emptyListOfFields 14 | } 15 | guard !tableName.isEmpty else { 16 | throw FMProErrors.tableNameMissing 17 | } 18 | 19 | let tmpTable = NewDBTable(tableName: tableName, fields: listOfColumns) 20 | let urlTmp = "\(baseUri)/FileMaker_Tables" 21 | 22 | _ = try await executeRequest(urlTmp: urlTmp, method: .post, data: tmpTable) 23 | } 24 | 25 | /// Add a list of columns to an existent table 26 | /// - Parameters: 27 | /// - tableName: the name of the table 28 | /// - listOfColumns: the list of columns 29 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the name of field already exist or someone else is modifying the schema already 30 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 31 | /// - Throws: a CommonErrors.emptyListOfFields error when the listOfColumns parameter is empty 32 | func addColumnToTable(tableName: String, listOfColumns: [CreationTableField]) async throws { 33 | guard !tableName.isEmpty else { 34 | throw FMProErrors.tableNameMissing 35 | } 36 | guard !listOfColumns.isEmpty else { 37 | throw FMProErrors.emptyListOfFields 38 | } 39 | 40 | let fields = AddFieldToTable(fields: listOfColumns) 41 | let urlTmp = "\(baseUri)/FileMaker_Tables/\(tableName)" 42 | 43 | _ = try await executeRequest(urlTmp: urlTmp, method: .patch, data: fields) 44 | } 45 | 46 | /// Deletes the table using its name 47 | /// - Parameter tableName: the name of the table 48 | /// - Throws: an HTTPError.errorCode_404_notFound error when the name of table is wrong or someone else is modifying the schema already 49 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 50 | func deleteTable(tableName: String) async throws { 51 | guard !tableName.isEmpty else { 52 | throw FMProErrors.tableNameMissing 53 | } 54 | 55 | let urlTmp = "\(baseUri)/FileMaker_Tables/\(tableName)" 56 | 57 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 58 | } 59 | 60 | /// Delete a column using its name from a specified table 61 | /// - Parameters: 62 | /// - tableName: the name of the table 63 | /// - fieldName: the name of the column 64 | /// - Throws: an HTTPError.errorCode_404_notFound error when the name of table is wrong, the field name is wrong or someone else is modifying the schema already 65 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 66 | /// - Throws: a CommonErrors.fieldNameMissing error when the fieldname parameter is empty 67 | func deleteColumnFromTable(tableName: String, fieldName: String) async throws { 68 | guard !tableName.isEmpty else { 69 | throw FMProErrors.tableNameMissing 70 | } 71 | guard !fieldName.isEmpty else { 72 | throw FMProErrors.fieldNameMissing 73 | } 74 | 75 | let urlTmp = "\(baseUri)/FileMaker_Tables/\(tableName)/\(fieldName)" 76 | 77 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 78 | } 79 | 80 | /// Create an index for a specified table 81 | /// - Parameters: 82 | /// - tableName: the name of the table 83 | /// - index: the name of the index 84 | /// - Throws: an HTTPError.errorCode_404_notFound error when the name of table doesn't exist 85 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the name of the field doesn't exist,the field is already an index or someone else is modifying the schema already 86 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 87 | /// - Throws: a CommonErrors.indexNameMissing error when the index parameter is empty 88 | func creatFieldIndex(tableName: String, index: String) async throws { 89 | guard !tableName.isEmpty else { 90 | throw FMProErrors.tableNameMissing 91 | } 92 | guard !index.isEmpty else { 93 | throw FMProErrors.indexNameMissing 94 | } 95 | 96 | let index = IndexValue(indexName: index) 97 | let urlTmp = "\(baseUri)/FileMaker_Indexes/\(tableName)" 98 | 99 | _ = try await executeRequest(urlTmp: urlTmp, method: .post, data: index) 100 | } 101 | 102 | /// Delete an index from a table using its name 103 | /// - Parameters: 104 | /// - tableName: the name of the table 105 | /// - index: the name of the index 106 | /// - Throws: an HTTPError.errorCode_400_badRequest error when the name of the field doesn't exist,the field is not an index or someone else is modifying the schema already 107 | /// - Throws: a CommonErrors.tableNameMissing error when the table parameter is empty 108 | /// - Throws: a CommonErrors.indexNameMissing error when the index parameter is empty 109 | func deleteIndexFromTable(tableName: String, index: String) async throws { 110 | guard !tableName.isEmpty else { 111 | throw FMProErrors.tableNameMissing 112 | } 113 | guard !index.isEmpty else { 114 | throw FMProErrors.indexNameMissing 115 | } 116 | 117 | let urlTmp = "\(baseUri)/FileMaker_Indexes/\(tableName)/\(index)" 118 | 119 | _ = try await executeRequest(urlTmp: urlTmp, method: .delete) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/Extensions/scriptExtensionOData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension FMODataAPI { 11 | /// Run a script created on the filemaker database 12 | /// - Parameters: 13 | /// - scriptName: the name of the script 14 | /// - scriptParameterValue: a generic value passed to the script that can be a String, a Numeric or an object 15 | func runScript(scriptName: String, scriptParameterValue: T?) async throws -> ScriptResult { 16 | let urlTmp = "\(baseUri)/Script.\(scriptName)" 17 | 18 | if scriptParameterValue == nil { 19 | let data = try await executeRequest(urlTmp: urlTmp, method: .post) 20 | let fetchedData = try JSONDecoder().decode(Scripter.self, from: data) 21 | return fetchedData.scriptResult 22 | } else { 23 | let data = try await executeRequest(urlTmp: urlTmp, method: .post, data: ScriptCaller(scriptParameterValue: scriptParameterValue)) 24 | let fetchedData = try JSONDecoder().decode(Scripter.self, from: data) 25 | return fetchedData.scriptResult 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/FMODataAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FMODataAPI.swift 3 | // FMApi 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 05/10/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// The class responsible to interface with the Filemaker Database using OData and API calls 12 | public class FMODataAPI: APIProtocol { 13 | /// a constant contains the server domain 14 | let hostname: String 15 | /// a constant contains the actual name of your database 16 | let database: String 17 | /// a variable is used to build the first common part URI for APIs 18 | let baseUri: String 19 | /// a variable contains the username credential to access the database 20 | var username: String 21 | /// a variable contains the password credential to access the database 22 | var password: String 23 | /// a variable contains the HTTP response in JSON format 24 | var responseJSON = Data() 25 | /// a variable describing the protocol version of OData 26 | var protocolVersion: ProtocolVersion 27 | /// used to access to the database 28 | var authData: String 29 | 30 | var request: URLRequest 31 | /// The class initializer used to setup all the information used to access and interface the Filemaker Database using OData. 32 | /// - Parameters: 33 | /// - server: the URL of the Filemaker Database passed as a String 34 | /// - database: the Database name of the Filemaker Database passed as a String 35 | /// - username: the Username used to acces the Filemaker Database passed as a String 36 | /// - password: the Password used to acces the Filemaker Database passed as a String 37 | /// - Attention: All the parameters has been encoded using _PercentEncoding_ with _urlQueryAllowed_ 38 | public required init(server: String, database: String, username: String, password: String, version: ProtocolVersion = .v4 ) { 39 | self.protocolVersion = version 40 | self.hostname = server 41 | self.database = database 42 | self.username = username 43 | self.password = password 44 | self.baseUri = "https://\(hostname)/fmi/odata/\(version.rawValue)/\(database)" 45 | self.request = URLRequest(url: URL(string: "https://")!) 46 | self.authData = "Basic \((username + ":" + password).data(using: .utf8)!.base64EncodedString())" 47 | } 48 | 49 | /// Returns the last URLRequest called using the package 50 | /// - Returns: an URLRequest object 51 | public func getRequest() -> URLRequest? { 52 | return request 53 | } 54 | 55 | /// Update the credentials used to access the database 56 | /// - Parameters: 57 | /// - username: a variable contains the username credential to access the database 58 | /// - password: a variable contains the password credential to access the database 59 | public func updateUsernameAndPassword(username: String, password: String) { 60 | self.username = username 61 | self.password = password 62 | self.authData = "Basic \((username + ":" + password).data(using: .utf8)!.base64EncodedString())" 63 | } 64 | 65 | /// Returns the last response in JSON format using the package 66 | /// - Returns: an Object of Data type 67 | public func getResponseJSON() -> Data { 68 | return responseJSON 69 | } 70 | 71 | /// decode the generic struct as an array of generics 72 | /// - Parameter data: the data that needed to be decoded 73 | /// - Returns: a list of generics item 74 | public func decodeJSONArray(data: Data) throws ->[T] { 75 | let fetchedData = try JSONDecoder().decode(JSONValue.self, from: data) 76 | return fetchedData.value 77 | } 78 | 79 | /// decode the generic struct as a generic object 80 | /// - Parameter data: the data that needed to be decoded 81 | /// - Returns: a generics item 82 | public func decodeJSONSingleValue(data: Data) throws -> T { 83 | let fetchedData = try JSONDecoder().decode(JSONSingleValue.self, from: data) 84 | return fetchedData.value 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/JSON support/JSONSupportDBModification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 04/12/22. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - NewDBTable 11 | /// A struct used to create a new Table inside a FM Database using OData 12 | struct NewDBTable: Codable { 13 | let tableName: String 14 | let fields: [CreationTableField] 15 | } 16 | 17 | /// A struct used to create a list of fields inside a FM Database using OData 18 | struct AddFieldToTable: Codable { 19 | let fields: [CreationTableField] 20 | } 21 | 22 | // MARK: - Field 23 | /// A struct used to create a new fields inside a FM Database using OData 24 | public struct CreationTableField: Codable { 25 | let name, type: String 26 | let primary, unique: Bool 27 | let fieldDefault: String 28 | let nullable, global: Bool 29 | let externalSecurePath: String 30 | 31 | enum CodingKeys: String, CodingKey { 32 | case name, type, primary, unique 33 | case fieldDefault = "default" 34 | case nullable, global, externalSecurePath 35 | } 36 | 37 | public init(name: String, type: DBType, primary: Bool = false, unique: Bool = false, fieldDefault: DefaultType = .empty, nullable: Bool = false, global: Bool = false, externalSecurePath: String = "") { 38 | self.name = name 39 | self.type = type.rawValue 40 | self.primary = primary 41 | self.unique = unique 42 | self.fieldDefault = fieldDefault.rawValue 43 | self.nullable = nullable 44 | self.global = global 45 | 46 | if type == .blob { 47 | self.externalSecurePath = externalSecurePath 48 | } else { 49 | self.externalSecurePath = "" 50 | } 51 | } 52 | 53 | public init(name: String, customType: String, primary: Bool = false, unique: Bool = false, fieldDefault: DefaultType = .empty, nullable: Bool = false, global: Bool = false, externalSecurePath: String = "") { 54 | self.name = name 55 | self.type = customType 56 | self.primary = primary 57 | self.unique = unique 58 | self.fieldDefault = fieldDefault.rawValue 59 | self.nullable = nullable 60 | self.global = global 61 | 62 | if customType == "BLOB" { 63 | self.externalSecurePath = externalSecurePath 64 | } else { 65 | self.externalSecurePath = "" 66 | } 67 | } 68 | } 69 | 70 | /// An enumeration showing all the type of the fields in FileMaker 71 | public enum DBType: String { 72 | case numeric = "NUMERIC" 73 | case decimal = "DECIMAL" 74 | case int = "INT" 75 | case date = "DATE" 76 | case time = "TIME" 77 | case timestamp = "TIMESTAMP" 78 | case varChar = "VARCHAR" 79 | case characterVarying = "CHARACTER VARYING" 80 | case blob = "BLOB" 81 | case varBinary = "VARBINARY" 82 | case longVarBinary = "LONGVARBINARY" 83 | case binaryVarying = "BINARY VARYING" 84 | case null = "NULL" 85 | } 86 | 87 | /// An enumeration showing all the value of default of a field in FileMaker 88 | public enum DefaultType: String { 89 | case user = "USER" 90 | case username = "USERNAME" 91 | case currentUser = "CURRENT_USER" 92 | case currentDate = "CURRENT_DATE" 93 | case curDate = "CURDATE" 94 | case currentTime = "CURRENT_TIME" 95 | case curTime = "CURTIME" 96 | case currentTimestamp = "CURRENT_TIMESTAMP" 97 | case curTimestamp = "CURTIMESTAMP" 98 | case empty = "" 99 | } 100 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/JSON support/JSONSupportScript.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 05/12/22. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - ScriptCaller 11 | struct ScriptCaller: Codable { 12 | let scriptParameterValue: T 13 | } 14 | 15 | // MARK: - ScriptResult 16 | struct Scripter: Codable { 17 | let scriptResult: ScriptResult 18 | } 19 | 20 | // MARK: - ScriptResult 21 | /// The result of the call of a script using API 22 | /// The code indicates the status of the call 23 | /// The resultParametere returns the value returned by the script if any 24 | /// The message explains the error if any occured 25 | public struct ScriptResult: Codable { 26 | let code: Int? 27 | let resultParameter: String? 28 | let message: String? 29 | } 30 | -------------------------------------------------------------------------------- /Sources/FMProKit/FMOdataAPI/JSON support/JSONValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // 4 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 02/10/22. 5 | // 6 | 7 | import Foundation 8 | 9 | struct JSONValue : Codable { 10 | var value: [T] 11 | } 12 | 13 | struct JSONSingleValue : Codable { 14 | var value: T 15 | } 16 | 17 | public struct TableValue: Codable { 18 | var name: String? 19 | var kind: String? 20 | var url: String? 21 | } 22 | 23 | struct IndexValue: Codable { 24 | var indexName: String 25 | } 26 | -------------------------------------------------------------------------------- /Sources/FMProKit/FileMakerErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Gianluca Annina on 08/07/23. 6 | // 7 | 8 | import Foundation 9 | enum FMError: Error { 10 | case errorCodeMinus1Unknown 11 | case errorCode1UserCanceledAction 12 | case errorCode2MemoryError 13 | case errorCode3CommandIsUnaivalable 14 | case errorCode4CommandIsUnknown 15 | case errorCode5CommandIsInvalid 16 | case errorCode6FileReadOnly 17 | case errorCode7RunningOutOfMemory 18 | case errorCode9InsufficientPrivileges 19 | case errorCode10RequestedDataIsMissing 20 | case errorCode11NameIsNotValid 21 | case errorCode12NameAlreadyExists 22 | case errorCode13FileOrObjectIsInUse 23 | case errorCode14OutOfRange 24 | case errorCode15CantDivideByZero 25 | case errorCode16OperationFailedRequestRetry 26 | case errorCode17AttemptToConvertForeignCharacterSetToUTF16Failed 27 | case errorCode18ClientMustProvideAccountInformationToProceed 28 | case errorCode19StringContainsCharactersOtherThanASCII 29 | case errorCode20CommandOrOperationCanceledByTriggeredScript 30 | case errorCode21RequestNotSupported 31 | case errorCode100FileMissing 32 | case errorCode101RecordMissing 33 | case errorCode102FieldMissing 34 | case errorCode103RelationshipMissing 35 | case errorCode104ScriptMissing 36 | case errorCode105LayoutMissing 37 | case errorCode106TableMissing 38 | case errorCode107IndexMissing 39 | case errorCode108ValueListMissing 40 | case errorCode109PrivilegeSetMissing 41 | case errorCode110RelatedTableMissing 42 | case errorCode111FieldRepetitionInvalid 43 | case errorCode112WindowMissing 44 | case errorCode113FunctionMissing 45 | case errorCode114FileReferenceMissing 46 | case errorCode115MenuSetMissing 47 | case errorCode116LayoutObjectMissing 48 | case errorCode117DataSourceMissing 49 | case errorCode118ThemeMissing 50 | case errorCode130FilesDamagedOrMissing 51 | case errorCode131LanguagePackFilesMissing 52 | case errorCode200RecordAccessDenied 53 | case errorCode201FieldCannotBeModified 54 | case errorCode202FieldAccesDenied 55 | case errorCode203NoRecordsInFileToPrintOrPasswordDoesNotAllowPrintAccess 56 | case errorCode205UserDoesNotHaveAccessPrivilegesToCreateNewRecords 57 | case errorCode206UserDoesNotHavePasswordChangePrivileges 58 | case errorCode207UserDoesNotHavePrivilegesToChangeDatabaseSchema 59 | case errorCode208PasswordDoesNotContainEnoughCharacters 60 | case errorCode209NewPasswordMustBeDifferentFromExistingOne 61 | case errorCode210UserAccountIsInactive 62 | case errorCode211PasswordHasExpired 63 | case errorCode212InvalidUserAccountOrPassword 64 | case errorCode214TooManyLoginAttempts 65 | case errorCode215AdministratorPrivilegesCannotBeDuplicated 66 | case errorCode216GuestAccountCannotBeDuplicated 67 | case errorCode217UserDoesNotHaveSufficientPrivilegesToModifyAdministratorAccountù 68 | case errorCode218PasswordAndVerifyPasswordDoNotMatch 69 | case errorCode219CannotOpenFile 70 | case errorCode300FileIsLockedOrInUse 71 | case errorCode301RecordIsInUseByAnotherUser 72 | case errorCode302TableIsInUseByAnotherUser 73 | case errorCode303DatabaseSchemaIsInUseByAnotherUser 74 | case errorCode304LayoutIsInUseByAnotherUser 75 | case errorCode306RecordModificationIDDoesNotMatch 76 | case errorCode307TransactionCouldNotBeLockedBecauseOfACommunicationErrorWithTheHost 77 | case errorCode308ThemeIsLockedAndInUseByAnotherUser 78 | case errorCode400FindCriteriaAreEmpty 79 | case errorCode401NoRecordsMatchTheRequest 80 | case errorCode402SelectedFieldIsNotAMatchFieldForALookup 81 | case errorCode404SortOrderIsInvalid 82 | case errorCode405NumberOfRecordsSpecifiedExceedsNumberOfRecordsThatCanBeOmitted 83 | case errorCode406ReplaceOrReserializeCriteriaAreInvalid 84 | case errorCode407OneOrBothMatchFieldsAreMissing 85 | case errorCode408SpecifiedFieldHasInappropriateDataTypeForThisOperation 86 | case errorCode409ImportOrderIsInvalid 87 | case errorCode410ExportOrderIsInvalid 88 | case errorCode412WrongVersionOfFileMakerProUsedToRecoverFile 89 | case errorCode413SpecifiedFieldHasInappropiateFieldType 90 | case errorCode414LayoutCannotDisplayTheResult 91 | case errorCode415OneOrMoreRequiredRelatedRecordsAreNotAvailable 92 | case errorCode416APrimaryKeyIsRequiredFromTheDataSourceTable 93 | case errorCode417FileIsNotASupportedDataSource 94 | case errorCode418InternalFailureInInsertOperationIntoAField 95 | case errorCode500DateValueDoesNotMeetValidationEntryOptions 96 | case errorCode501TimeValueDoesNotMeetValidationEntryOptions 97 | case errorCode502NumberValueDoesNotMeetValidationEntryOptions 98 | case errorCode503ValueInFieldIsNotWithinTheRangeSpecifiedInValidationEntryOption 99 | case errorCode504ValueInFieldIsNotUnique 100 | case errorCode505ValueInFieldIsNotAnExistingValueInTheFile 101 | case errorCode506ValueInFieldIsNotListedInTheValueListSpecifiedInValidationEntry 102 | case errorCode507ValueInFieldFailedCalculationTestOfValidationEntryOption 103 | case errorCode508InvalidValueEnteredInFindMode 104 | case errorCode509FieldRequiresAValidValue 105 | case errorCode510RelatedValueIsEmptyOrUnavailable 106 | case errorCode511ValueInFieldExceedsMaximumFieldSize 107 | case errorCode512RecordWasAlreadyModifiedByAnotherUser 108 | case errorCode513NoValidationWasSpecifiedButDataCannotFitIntoTheField 109 | case errorCode600PrintErrorHasOccured 110 | case errorCode601CombinedHeaderAndFooterExceedOnePage 111 | case errorCode602BodyDoesNotFitOnAPageForCurrentColumnSetup 112 | case errorCode603PrintConnectionList 113 | case errorCode700FileIsOfTheWrongFileTypeForImport 114 | case errorCode706EPSFileHasNoPreviewImage 115 | case errorCode707GraphicTranslator 116 | case errorCode708CannotImportTheFile 117 | case errorCode711ImportTranslatorCannotBeFound 118 | case errorCode714PasswordPrivilegesDoNotAllow 119 | case errorCode715SpecifiedExcerlWorksheetOrNamedRangeIsMissing 120 | case errorCode716SQLQueryIsNotAllowedForODBCImport 121 | case errorCode717ThereIsNotEnoughXMLOrXSLInformationToProceedWithTheImportOrExport 122 | case errorCode718ErrorInParsingXMLFileUsingXerces 123 | case errorCode719ErrorInTransformingXMLUsingXSLUsingXalan 124 | case errorCode720ErrorWhenExportingFormatDoesNotSupportRepeatingFields 125 | case errorCode721UknownErrorOccuredInTheParserOrTheTransformer 126 | case errorCode722CannotImportDataIntoFileThatHasNoFields 127 | case errorCode723YouDoNotHavePermissionToAddRecordsToOrModifyRecordsInTheTargetTable 128 | case errorCode724YouDoNotHavePermissionToAddRecordsToTheTargetTable 129 | case errorCode725YouDoNotHavePermissionToModifyRecordsToTheTargetTable 130 | case errorCode726SourceFileHasMoreRecordsThanTheTargetTable 131 | case errorCode727TargetTableHasMoreRecordsThanTheSourceFile 132 | case errorCode729ErrorsOccurredDuringImport 133 | case errorCode730UnsupportedExcelVersion 134 | case errorCode731FileYouAreImportingFromContainsNoData 135 | case errorCode732ThisFileCannotBeInsertedBecauseItContainsOtherFiles 136 | case errorCode733ATableCannotBeImportedIntoItself 137 | case errorCode734ThisFileTypeCannotBeDisplayedAsAPicture 138 | case errorCode735ThisFileTypeCannotBeDisplayedAsAPicture 139 | case errorCode736TooMuchDataToExportToThisFormat 140 | case errorCode738TheThemeYouAreImportingAlreadyExists 141 | case errorCode800UnableToCreateFileOnDisk 142 | case errorCode801UnableToCreateTemporaryFileOnSystemDisk 143 | case errorCode802UnableToOpenFile 144 | case errorCode803FileIsSingleUserOrHostCannotBeFound 145 | case errorCode804FileCannotBeOpenedAsReadOnlyInItsCurrentState 146 | case errorCode805FileIsDamaged 147 | case errorCode806FileCannotBeOpenedWithThisVersionOfAFileMakerClient 148 | case errorCode807FileIsNotAFileMakerProFile 149 | case errorCode808CannotOpenFileBecauseAccessPrivilegesAreDamaged 150 | case errorCode809DiskOrVolumeIsFull 151 | case errorCode810DiskOrVolumeIsLocked 152 | case errorCode811TemporaryFileCannotBeOpenedAsFileMakerProfile 153 | case errorCode812ExceededHostCapacity 154 | case errorCode813RecordSynchronizationErrorOnNetwork 155 | case errorCode814FileCannotBeOpenedBecauseMaximumNumberIsOpen 156 | case errorCode815CouldNotOpenLookupFile 157 | case errorCode816UnableToConvertFile 158 | case errorCode817UnableToOpenFileBecauseItDoesNotBelongToThisSolution 159 | case errorCode819CannotSaveALocalCopyOfARemoteFile 160 | case errorCode820FileIsBeingClosed 161 | case errorCode821HostForcedADisconnect 162 | case errorCode822FileMakerProFilesNotFound 163 | case errorCode823CannotSetFileToSingleUser 164 | case errorCode824FileIsDamagedOrNotAFileMakerProFile 165 | case errorCode825FileIsNotAuthorizedToReferenceTheProtectedFile 166 | case errorCode826FilePathSpecifiedIsNotAValidPath 167 | case errorCode827FileWasNotCreatedBecauseTheSourceContainedNoDataOrIsAReference 168 | case errorCode850PathIsNotValidForTheOperatingSystem 169 | case errorCode851CannotDeleteAnExternalFileFromDisk 170 | case errorCode852CannotWriteAFileToTheExternalStorage 171 | case errorCode853OneOrMoreContainersFailedToTransfer 172 | case errorCode870CannotModifyFileBecauseAnotherUserIsModifyingIt 173 | case errorCode871ErrorOccurredLoadingCoreMLModel 174 | case errorCode872CoreMLModelWasNotLoadedBecauseItContainedAnUnsupportedInputOrOutputParameter 175 | case errorCode900GeneralSpellingEngineError 176 | case errorCode901MainSpellingDictionaryNotInstalled 177 | case errorCode903CommandCannotBeUsedInASharedFile 178 | case errorCode905CommandRequiresAFieldToBeActive 179 | case errorCode906CurrentFileIsNotShared 180 | case errorCode920CannotInitializeSpellingEngine 181 | case errorCode921UserDictionaryCannotBeLoadedForEditing 182 | case errorCode922UserDictionaryCannotBeFound 183 | case errorCode923UserDictionaryIsReadOnly 184 | case errorCode951AnUnexpectedErrorOccured 185 | case errorCode952InvalidFileMakerDataAPIToken 186 | case errorCode953ExceededLimitOnDataTheFileMakerDataAPIAndODataCanTranmit 187 | case errorCode954UnsupportedXMLGrammar 188 | case errorCode955NoDatabaseName 189 | case errorCode956MaximumNumberOfDatabaseOrAdminAPISessionsExceeded 190 | case errorCode957ConflictingCommands 191 | case errorCode958ParameterMissing 192 | case errorCode959CustomWebPublishingTechnologyIsDisabled 193 | case errorCode960ParameterIsInvalid 194 | case errorCode1200GenericCalculationError 195 | case errorCode1201TooFewParametersInTheFunction 196 | case errorCode1202TooManyParametersInTheFunction 197 | case errorCode1203UnexpectedEndOfCalculation 198 | case errorCode1204NumberOrTextConstantOrFieldNameOrBracketExpected 199 | case errorCode1205CommenIsNotTerminatedWithSlash 200 | case errorCode1206TextConstantMustEndWithAQuotationMark 201 | case errorCode1207UnbalancedParenthesis 202 | case errorCode1208OperatorMissingOrFunctionNotFoundOrBracketNotExpected 203 | case errorCode1209NameIsMissing 204 | case errorCode1210PlugInFunctionOrScriptStepHasAlreadyBeenRegistered 205 | case errorCode1211ListUsageIsNotAllowedInThisFun 206 | case errorCode1212AnOperatorIsExpectedHere 207 | case errorCode1213ThisVariableHasAlreadyBeenDefinedInTheLetFunction 208 | case errorCode1214AFunctionParameterIsAnInvalidGetFunctionParameter 209 | case errorCode1215ThisParameterIsAnInvalidGetFunctionParameter 210 | case errorCode1216OnlySummaryFieldsAreAllowedAsFirstArgumentInGetSummary 211 | case errorCode1217BreakFieldIsInvalid 212 | case errorCode1218CannotEvaluateTheNumber 213 | case errorCode1219AFieldCannotBeUsedInItsOwnFormula 214 | case errorCode1220FieldTypeMustBeNormalOrCalculated 215 | case errorCode1221DataTypeMustBeNumberOrDateOrTimeOrTimestamp 216 | case errorCode1222CalculationCannotBeStored 217 | case errorCode1223FunctionReferredToIsNotYetImplemented 218 | case errorCode1224FunctionReferredToDoesNotExist 219 | case errorCode1225FunctionReferredToIsNotSupportedInThisContext 220 | case errorCode1300TheSpecifiedNameCannotBeUsed 221 | case errorCode1301AParameterOfTheImportedOrPastedFunctionHasTheSameNameAsAFunctionInTheSameFile 222 | case errorCode1400ODBCConnectionErrors 223 | case errorCode1500SMTPConnectionErrors 224 | case errorCode1626ProtocolIsNotSupported 225 | case errorCode1627AuthenticationFailed 226 | case errorCode1628ThereWasAnErrorWithSSL 227 | case errorCode1629ConnectionTimedOut 228 | case errorCode1630URLFormatIsIncorrect 229 | case errorCode1631ConnectionFailed 230 | case errorCode1632TheCertificateHasExpired 231 | case errorCode1633TheCertificateIsSelfSigned 232 | case errorCode1634ACertificateVerificationErrorOccured 233 | case errorCode1635ConnectionIsUnencrypted 234 | case errorCode1700ResourceDoesNotExist 235 | case errorCode1701HostIsCurrentlyEnableToReceiveRequests 236 | case errorCode1702AuthenticationInformationWasNotProvidedInTheCorrectFormat 237 | case errorCode1703InvalidUsernameOrPasswordOrJSONWebToken 238 | case errorCode1704ResourceDoesNotSupportTheSpecifiedHTTPVerb 239 | case errorCode1705RequiredHTTPHeaderWasNotSpecified 240 | case errorCode1706ParameterIsNotSupported 241 | case errorCode1707RequiredParameterWasNotSpecifiedInTheRequest 242 | case errorCode1708ParameterValueIsInvalid 243 | case errorCode1709OperationIsInvalidForTheResourceCurrentStatus 244 | case errorCode1710JSONInputIsNotSyntacticallyValid 245 | case errorCode1711HostLicenseHasExpired 246 | case errorCode1712PrivateKeyFileAlreadyExists 247 | case errorCode1713TheAPIRequestIsNotSupportedForThisOperatingSystem 248 | case errorCode1714ExternalGroupNameIsInvalid 249 | case errorCode1715ExternalServerAccountSignInIsNotEnabled 250 | 251 | case undefinedError 252 | } 253 | 254 | 255 | func checkResponseCode(messages:[Message]) throws { 256 | let messageCode = Int(messages.first?.code ?? "0") 257 | if messageCode == -1 { 258 | throw FMError.errorCodeMinus1Unknown 259 | } else if messageCode == 0 { 260 | 261 | } else if messageCode == 1 { 262 | throw FMError.errorCode1UserCanceledAction 263 | } else if messageCode == 2 { 264 | throw FMError.errorCode2MemoryError 265 | } else if messageCode == 3 { 266 | throw FMError.errorCode3CommandIsUnaivalable 267 | } else if messageCode == 4 { 268 | throw FMError.errorCode4CommandIsUnknown 269 | } else if messageCode == 5 { 270 | throw FMError.errorCode5CommandIsInvalid 271 | } else if messageCode == 6 { 272 | throw FMError.errorCode6FileReadOnly 273 | } else if messageCode == 7 { 274 | throw FMError.errorCode7RunningOutOfMemory 275 | } else if messageCode == 9 { 276 | throw FMError.errorCode9InsufficientPrivileges 277 | } else if messageCode == 10 { 278 | throw FMError.errorCode10RequestedDataIsMissing 279 | } else if messageCode == 11 { 280 | throw FMError.errorCode11NameIsNotValid 281 | } else if messageCode == 12 { 282 | throw FMError.errorCode12NameAlreadyExists 283 | } else if messageCode == 13 { 284 | throw FMError.errorCode13FileOrObjectIsInUse 285 | } else if messageCode == 14 { 286 | throw FMError.errorCode14OutOfRange 287 | } else if messageCode == 15 { 288 | throw FMError.errorCode15CantDivideByZero 289 | } else if messageCode == 16 { 290 | throw FMError.errorCode16OperationFailedRequestRetry 291 | } else if messageCode == 17 { 292 | throw FMError.errorCode17AttemptToConvertForeignCharacterSetToUTF16Failed 293 | } else if messageCode == 18 { 294 | throw FMError.errorCode18ClientMustProvideAccountInformationToProceed 295 | } else if messageCode == 19 { 296 | throw FMError.errorCode19StringContainsCharactersOtherThanASCII 297 | } else if messageCode == 20 { 298 | throw FMError.errorCode20CommandOrOperationCanceledByTriggeredScript 299 | } else if messageCode == 21 { 300 | throw FMError.errorCode21RequestNotSupported 301 | } else if messageCode == 100 { 302 | throw FMError.errorCode100FileMissing 303 | } else if messageCode == 101 { 304 | throw FMError.errorCode101RecordMissing 305 | } else if messageCode == 102 { 306 | throw FMError.errorCode102FieldMissing 307 | } else if messageCode == 103 { 308 | throw FMError.errorCode103RelationshipMissing 309 | } else if messageCode == 104 { 310 | throw FMError.errorCode104ScriptMissing 311 | } else if messageCode == 105 { 312 | throw FMError.errorCode105LayoutMissing 313 | } else if messageCode == 106 { 314 | throw FMError.errorCode106TableMissing 315 | } else if messageCode == 107 { 316 | throw FMError.errorCode107IndexMissing 317 | } else if messageCode == 108 { 318 | throw FMError.errorCode108ValueListMissing 319 | } else if messageCode == 109 { 320 | throw FMError.errorCode109PrivilegeSetMissing 321 | } else if messageCode == 110 { 322 | throw FMError.errorCode110RelatedTableMissing 323 | } else if messageCode == 111 { 324 | throw FMError.errorCode111FieldRepetitionInvalid 325 | } else if messageCode == 112 { 326 | throw FMError.errorCode112WindowMissing 327 | } else if messageCode == 113 { 328 | throw FMError.errorCode113FunctionMissing 329 | } else if messageCode == 114 { 330 | throw FMError.errorCode114FileReferenceMissing 331 | } else if messageCode == 115 { 332 | throw FMError.errorCode115MenuSetMissing 333 | } else if messageCode == 116 { 334 | throw FMError.errorCode116LayoutObjectMissing 335 | } else if messageCode == 117 { 336 | throw FMError.errorCode117DataSourceMissing 337 | } else if messageCode == 118 { 338 | throw FMError.errorCode118ThemeMissing 339 | } else if messageCode == 130 { 340 | throw FMError.errorCode130FilesDamagedOrMissing 341 | } else if messageCode == 131 { 342 | throw FMError.errorCode131LanguagePackFilesMissing 343 | } else if messageCode == 200 { 344 | throw FMError.errorCode200RecordAccessDenied 345 | } else if messageCode == 201 { 346 | throw FMError.errorCode201FieldCannotBeModified 347 | } else if messageCode == 202 { 348 | throw FMError.errorCode202FieldAccesDenied 349 | } else if messageCode == 203 { 350 | throw FMError.errorCode203NoRecordsInFileToPrintOrPasswordDoesNotAllowPrintAccess 351 | } else if messageCode == 205 { 352 | throw FMError.errorCode205UserDoesNotHaveAccessPrivilegesToCreateNewRecords 353 | } else if messageCode == 206 { 354 | throw FMError.errorCode206UserDoesNotHavePasswordChangePrivileges 355 | } else if messageCode == 207 { 356 | throw FMError.errorCode207UserDoesNotHavePrivilegesToChangeDatabaseSchema 357 | } else if messageCode == 208 { 358 | throw FMError.errorCode208PasswordDoesNotContainEnoughCharacters 359 | } else if messageCode == 209 { 360 | throw FMError.errorCode209NewPasswordMustBeDifferentFromExistingOne 361 | } else if messageCode == 210 { 362 | throw FMError.errorCode210UserAccountIsInactive 363 | } else if messageCode == 211 { 364 | throw FMError.errorCode211PasswordHasExpired 365 | } else if messageCode == 212 { 366 | throw FMError.errorCode212InvalidUserAccountOrPassword 367 | } else if messageCode == 214 { 368 | throw FMError.errorCode214TooManyLoginAttempts 369 | } else if messageCode == 215 { 370 | throw FMError.errorCode215AdministratorPrivilegesCannotBeDuplicated 371 | } else if messageCode == 216 { 372 | throw FMError.errorCode216GuestAccountCannotBeDuplicated 373 | } else if messageCode == 217 { 374 | throw FMError.errorCode217UserDoesNotHaveSufficientPrivilegesToModifyAdministratorAccountù 375 | } else if messageCode == 218 { 376 | throw FMError.errorCode218PasswordAndVerifyPasswordDoNotMatch 377 | } else if messageCode == 219 { 378 | throw FMError.errorCode219CannotOpenFile 379 | } else if messageCode == 300 { 380 | throw FMError.errorCode300FileIsLockedOrInUse 381 | } else if messageCode == 301 { 382 | throw FMError.errorCode301RecordIsInUseByAnotherUser 383 | } else if messageCode == 302 { 384 | throw FMError.errorCode302TableIsInUseByAnotherUser 385 | } else if messageCode == 303 { 386 | throw FMError.errorCode303DatabaseSchemaIsInUseByAnotherUser 387 | } else if messageCode == 304 { 388 | throw FMError.errorCode304LayoutIsInUseByAnotherUser 389 | } else if messageCode == 306 { 390 | throw FMError.errorCode306RecordModificationIDDoesNotMatch 391 | } else if messageCode == 307 { 392 | throw FMError.errorCode307TransactionCouldNotBeLockedBecauseOfACommunicationErrorWithTheHost 393 | } else if messageCode == 308 { 394 | throw FMError.errorCode308ThemeIsLockedAndInUseByAnotherUser 395 | } else if messageCode == 400 { 396 | throw FMError.errorCode400FindCriteriaAreEmpty 397 | } else if messageCode == 401 { 398 | throw FMError.errorCode401NoRecordsMatchTheRequest 399 | } else if messageCode == 402 { 400 | throw FMError.errorCode402SelectedFieldIsNotAMatchFieldForALookup 401 | } else if messageCode == 404 { 402 | throw FMError.errorCode404SortOrderIsInvalid 403 | } else if messageCode == 405 { 404 | throw FMError.errorCode405NumberOfRecordsSpecifiedExceedsNumberOfRecordsThatCanBeOmitted 405 | } else if messageCode == 406 { 406 | throw FMError.errorCode406ReplaceOrReserializeCriteriaAreInvalid 407 | } else if messageCode == 407 { 408 | throw FMError.errorCode407OneOrBothMatchFieldsAreMissing 409 | } else if messageCode == 408 { 410 | throw FMError.errorCode408SpecifiedFieldHasInappropriateDataTypeForThisOperation 411 | } else if messageCode == 409 { 412 | throw FMError.errorCode409ImportOrderIsInvalid 413 | } else if messageCode == 410 { 414 | throw FMError.errorCode410ExportOrderIsInvalid 415 | } else if messageCode == 412 { 416 | throw FMError.errorCode412WrongVersionOfFileMakerProUsedToRecoverFile 417 | } else if messageCode == 413 { 418 | throw FMError.errorCode413SpecifiedFieldHasInappropiateFieldType 419 | } else if messageCode == 414 { 420 | throw FMError.errorCode414LayoutCannotDisplayTheResult 421 | } else if messageCode == 415 { 422 | throw FMError.errorCode415OneOrMoreRequiredRelatedRecordsAreNotAvailable 423 | } else if messageCode == 416 { 424 | throw FMError.errorCode416APrimaryKeyIsRequiredFromTheDataSourceTable 425 | } else if messageCode == 417 { 426 | throw FMError.errorCode417FileIsNotASupportedDataSource 427 | } else if messageCode == 418 { 428 | throw FMError.errorCode418InternalFailureInInsertOperationIntoAField 429 | } else if messageCode == 500 { 430 | throw FMError.errorCode500DateValueDoesNotMeetValidationEntryOptions 431 | } else if messageCode == 501 { 432 | throw FMError.errorCode501TimeValueDoesNotMeetValidationEntryOptions 433 | } else if messageCode == 502 { 434 | throw FMError.errorCode502NumberValueDoesNotMeetValidationEntryOptions 435 | } else if messageCode == 503 { 436 | throw FMError.errorCode503ValueInFieldIsNotWithinTheRangeSpecifiedInValidationEntryOption 437 | } else if messageCode == 504 { 438 | throw FMError.errorCode504ValueInFieldIsNotUnique 439 | } else if messageCode == 505 { 440 | throw FMError.errorCode505ValueInFieldIsNotAnExistingValueInTheFile 441 | } else if messageCode == 506 { 442 | throw FMError.errorCode506ValueInFieldIsNotListedInTheValueListSpecifiedInValidationEntry 443 | } else if messageCode == 507 { 444 | throw FMError.errorCode507ValueInFieldFailedCalculationTestOfValidationEntryOption 445 | } else if messageCode == 508 { 446 | throw FMError.errorCode508InvalidValueEnteredInFindMode 447 | } else if messageCode == 509 { 448 | throw FMError.errorCode509FieldRequiresAValidValue 449 | } else if messageCode == 510 { 450 | throw FMError.errorCode510RelatedValueIsEmptyOrUnavailable 451 | } else if messageCode == 511 { 452 | throw FMError.errorCode511ValueInFieldExceedsMaximumFieldSize 453 | } else if messageCode == 512 { 454 | throw FMError.errorCode512RecordWasAlreadyModifiedByAnotherUser 455 | } else if messageCode == 513 { 456 | throw FMError.errorCode513NoValidationWasSpecifiedButDataCannotFitIntoTheField 457 | } else if messageCode == 600 { 458 | throw FMError.errorCode600PrintErrorHasOccured 459 | } else if messageCode == 601 { 460 | throw FMError.errorCode601CombinedHeaderAndFooterExceedOnePage 461 | } else if messageCode == 602 { 462 | throw FMError.errorCode602BodyDoesNotFitOnAPageForCurrentColumnSetup 463 | } else if messageCode == 603 { 464 | throw FMError.errorCode603PrintConnectionList 465 | } else if messageCode == 700 { 466 | throw FMError.errorCode700FileIsOfTheWrongFileTypeForImport 467 | } else if messageCode == 706 { 468 | throw FMError.errorCode706EPSFileHasNoPreviewImage 469 | } else if messageCode == 707 { 470 | throw FMError.errorCode707GraphicTranslator 471 | } else if messageCode == 708 { 472 | throw FMError.errorCode708CannotImportTheFile 473 | } else if messageCode == 711 { 474 | throw FMError.errorCode711ImportTranslatorCannotBeFound 475 | } else if messageCode == 714 { 476 | throw FMError.errorCode714PasswordPrivilegesDoNotAllow 477 | } else if messageCode == 715 { 478 | throw FMError.errorCode715SpecifiedExcerlWorksheetOrNamedRangeIsMissing 479 | } else if messageCode == 716 { 480 | throw FMError.errorCode716SQLQueryIsNotAllowedForODBCImport 481 | } else if messageCode == 717 { 482 | throw FMError.errorCode717ThereIsNotEnoughXMLOrXSLInformationToProceedWithTheImportOrExport 483 | } else if messageCode == 718 { 484 | throw FMError.errorCode718ErrorInParsingXMLFileUsingXerces 485 | } else if messageCode == 720 { 486 | throw FMError.errorCode720ErrorWhenExportingFormatDoesNotSupportRepeatingFields 487 | } else if messageCode == 721 { 488 | throw FMError.errorCode721UknownErrorOccuredInTheParserOrTheTransformer 489 | } else if messageCode == 722 { 490 | throw FMError.errorCode722CannotImportDataIntoFileThatHasNoFields 491 | } else if messageCode == 723 { 492 | throw FMError.errorCode723YouDoNotHavePermissionToAddRecordsToOrModifyRecordsInTheTargetTable 493 | } else if messageCode == 724 { 494 | throw FMError.errorCode724YouDoNotHavePermissionToAddRecordsToTheTargetTable 495 | } else if messageCode == 506 { 496 | throw FMError.errorCode506ValueInFieldIsNotListedInTheValueListSpecifiedInValidationEntry 497 | } else if messageCode == 725 { 498 | throw FMError.errorCode725YouDoNotHavePermissionToModifyRecordsToTheTargetTable 499 | } else if messageCode == 726 { 500 | throw FMError.errorCode726SourceFileHasMoreRecordsThanTheTargetTable 501 | } else if messageCode == 727 { 502 | throw FMError.errorCode727TargetTableHasMoreRecordsThanTheSourceFile 503 | } else if messageCode == 729 { 504 | throw FMError.errorCode729ErrorsOccurredDuringImport 505 | } else if messageCode == 730 { 506 | throw FMError.errorCode730UnsupportedExcelVersion 507 | } else if messageCode == 731 { 508 | throw FMError.errorCode731FileYouAreImportingFromContainsNoData 509 | } else if messageCode == 732 { 510 | throw FMError.errorCode732ThisFileCannotBeInsertedBecauseItContainsOtherFiles 511 | } else if messageCode == 733 { 512 | throw FMError.errorCode733ATableCannotBeImportedIntoItself 513 | } else if messageCode == 734 { 514 | throw FMError.errorCode734ThisFileTypeCannotBeDisplayedAsAPicture 515 | } else if messageCode == 735 { 516 | throw FMError.errorCode735ThisFileTypeCannotBeDisplayedAsAPicture 517 | } else if messageCode == 736 { 518 | throw FMError.errorCode736TooMuchDataToExportToThisFormat 519 | } else if messageCode == 738 { 520 | throw FMError.errorCode738TheThemeYouAreImportingAlreadyExists 521 | } else if messageCode == 800 { 522 | throw FMError.errorCode800UnableToCreateFileOnDisk 523 | } else if messageCode == 801 { 524 | throw FMError.errorCode801UnableToCreateTemporaryFileOnSystemDisk 525 | } else if messageCode == 802 { 526 | throw FMError.errorCode802UnableToOpenFile 527 | } else if messageCode == 803 { 528 | throw FMError.errorCode803FileIsSingleUserOrHostCannotBeFound 529 | } else if messageCode == 804 { 530 | throw FMError.errorCode804FileCannotBeOpenedAsReadOnlyInItsCurrentState 531 | } else if messageCode == 805 { 532 | throw FMError.errorCode805FileIsDamaged 533 | } else if messageCode == 806 { 534 | throw FMError.errorCode806FileCannotBeOpenedWithThisVersionOfAFileMakerClient 535 | } else if messageCode == 807 { 536 | throw FMError.errorCode807FileIsNotAFileMakerProFile 537 | } else if messageCode == 808 { 538 | throw FMError.errorCode808CannotOpenFileBecauseAccessPrivilegesAreDamaged 539 | } else if messageCode == 809 { 540 | throw FMError.errorCode809DiskOrVolumeIsFull 541 | } else if messageCode == 810 { 542 | throw FMError.errorCode810DiskOrVolumeIsLocked 543 | } else if messageCode == 811 { 544 | throw FMError.errorCode811TemporaryFileCannotBeOpenedAsFileMakerProfile 545 | } else if messageCode == 812 { 546 | throw FMError.errorCode812ExceededHostCapacity 547 | } else if messageCode == 813 { 548 | throw FMError.errorCode813RecordSynchronizationErrorOnNetwork 549 | } else if messageCode == 814 { 550 | throw FMError.errorCode814FileCannotBeOpenedBecauseMaximumNumberIsOpen 551 | } else if messageCode == 815 { 552 | throw FMError.errorCode815CouldNotOpenLookupFile 553 | } else if messageCode == 816 { 554 | throw FMError.errorCode816UnableToConvertFile 555 | } else if messageCode == 817 { 556 | throw FMError.errorCode817UnableToOpenFileBecauseItDoesNotBelongToThisSolution 557 | } else if messageCode == 819 { 558 | throw FMError.errorCode819CannotSaveALocalCopyOfARemoteFile 559 | } else if messageCode == 820 { 560 | throw FMError.errorCode820FileIsBeingClosed 561 | } else if messageCode == 821 { 562 | throw FMError.errorCode821HostForcedADisconnect 563 | } else if messageCode == 822 { 564 | throw FMError.errorCode822FileMakerProFilesNotFound 565 | } else if messageCode == 823 { 566 | throw FMError.errorCode823CannotSetFileToSingleUser 567 | } else if messageCode == 824 { 568 | throw FMError.errorCode824FileIsDamagedOrNotAFileMakerProFile 569 | } else if messageCode == 825 { 570 | throw FMError.errorCode825FileIsNotAuthorizedToReferenceTheProtectedFile 571 | } else if messageCode == 826 { 572 | throw FMError.errorCode826FilePathSpecifiedIsNotAValidPath 573 | } else if messageCode == 827 { 574 | throw FMError.errorCode827FileWasNotCreatedBecauseTheSourceContainedNoDataOrIsAReference 575 | } else if messageCode == 850 { 576 | throw FMError.errorCode850PathIsNotValidForTheOperatingSystem 577 | } else if messageCode == 851 { 578 | throw FMError.errorCode851CannotDeleteAnExternalFileFromDisk 579 | } else if messageCode == 852 { 580 | throw FMError.errorCode852CannotWriteAFileToTheExternalStorage 581 | } else if messageCode == 853 { 582 | throw FMError.errorCode853OneOrMoreContainersFailedToTransfer 583 | } else if messageCode == 870 { 584 | throw FMError.errorCode870CannotModifyFileBecauseAnotherUserIsModifyingIt 585 | } else if messageCode == 871 { 586 | throw FMError.errorCode871ErrorOccurredLoadingCoreMLModel 587 | } else if messageCode == 872 { 588 | throw FMError.errorCode872CoreMLModelWasNotLoadedBecauseItContainedAnUnsupportedInputOrOutputParameter 589 | } else if messageCode == 900 { 590 | throw FMError.errorCode900GeneralSpellingEngineError 591 | } else if messageCode == 901 { 592 | throw FMError.errorCode901MainSpellingDictionaryNotInstalled 593 | } else if messageCode == 903 { 594 | throw FMError.errorCode903CommandCannotBeUsedInASharedFile 595 | } else if messageCode == 905 { 596 | throw FMError.errorCode905CommandRequiresAFieldToBeActive 597 | } else if messageCode == 906 { 598 | throw FMError.errorCode906CurrentFileIsNotShared 599 | } else if messageCode == 920 { 600 | throw FMError.errorCode920CannotInitializeSpellingEngine 601 | } else if messageCode == 921 { 602 | throw FMError.errorCode921UserDictionaryCannotBeLoadedForEditing 603 | } else if messageCode == 922 { 604 | throw FMError.errorCode922UserDictionaryCannotBeFound 605 | } else if messageCode == 923 { 606 | throw FMError.errorCode923UserDictionaryIsReadOnly 607 | } else if messageCode == 951 { 608 | throw FMError.errorCode951AnUnexpectedErrorOccured 609 | } else if messageCode == 952 { 610 | throw FMError.errorCode952InvalidFileMakerDataAPIToken 611 | } else if messageCode == 953 { 612 | throw FMError.errorCode953ExceededLimitOnDataTheFileMakerDataAPIAndODataCanTranmit 613 | } else if messageCode == 954 { 614 | throw FMError.errorCode954UnsupportedXMLGrammar 615 | } else if messageCode == 955 { 616 | throw FMError.errorCode955NoDatabaseName 617 | } else if messageCode == 956 { 618 | throw FMError.errorCode956MaximumNumberOfDatabaseOrAdminAPISessionsExceeded 619 | } else if messageCode == 957 { 620 | throw FMError.errorCode957ConflictingCommands 621 | } else if messageCode == 958 { 622 | throw FMError.errorCode958ParameterMissing 623 | } else if messageCode == 959 { 624 | throw FMError.errorCode959CustomWebPublishingTechnologyIsDisabled 625 | } else if messageCode == 960 { 626 | throw FMError.errorCode960ParameterIsInvalid 627 | } else if messageCode == 1200 { 628 | throw FMError.errorCode1200GenericCalculationError 629 | } else if messageCode == 1201 { 630 | throw FMError.errorCode1201TooFewParametersInTheFunction 631 | } else if messageCode == 1202 { 632 | throw FMError.errorCode1202TooManyParametersInTheFunction 633 | } else if messageCode == 1203 { 634 | throw FMError.errorCode1203UnexpectedEndOfCalculation 635 | } else if messageCode == 1204 { 636 | throw FMError.errorCode1204NumberOrTextConstantOrFieldNameOrBracketExpected 637 | } else if messageCode == 1205 { 638 | throw FMError.errorCode1205CommenIsNotTerminatedWithSlash 639 | } else if messageCode == 1206 { 640 | throw FMError.errorCode1206TextConstantMustEndWithAQuotationMark 641 | } else if messageCode == 1207 { 642 | throw FMError.errorCode1207UnbalancedParenthesis 643 | } else if messageCode == 1208 { 644 | throw FMError.errorCode1208OperatorMissingOrFunctionNotFoundOrBracketNotExpected 645 | } else if messageCode == 1209 { 646 | throw FMError.errorCode1209NameIsMissing 647 | } else if messageCode == 1210 { 648 | throw FMError.errorCode1210PlugInFunctionOrScriptStepHasAlreadyBeenRegistered 649 | } else if messageCode == 1211 { 650 | throw FMError.errorCode1211ListUsageIsNotAllowedInThisFun 651 | } else if messageCode == 1212 { 652 | throw FMError.errorCode1212AnOperatorIsExpectedHere 653 | } else if messageCode == 1213 { 654 | throw FMError.errorCode1213ThisVariableHasAlreadyBeenDefinedInTheLetFunction 655 | } else if messageCode == 1214 { 656 | throw FMError.errorCode1214AFunctionParameterIsAnInvalidGetFunctionParameter 657 | } else if messageCode == 1215 { 658 | throw FMError.errorCode1215ThisParameterIsAnInvalidGetFunctionParameter 659 | } else if messageCode == 1216 { 660 | throw FMError.errorCode1216OnlySummaryFieldsAreAllowedAsFirstArgumentInGetSummary 661 | } else if messageCode == 1217 { 662 | throw FMError.errorCode1217BreakFieldIsInvalid 663 | } else if messageCode == 1218 { 664 | throw FMError.errorCode1218CannotEvaluateTheNumber 665 | } else if messageCode == 1219 { 666 | throw FMError.errorCode1219AFieldCannotBeUsedInItsOwnFormula 667 | } else if messageCode == 1220 { 668 | throw FMError.errorCode1220FieldTypeMustBeNormalOrCalculated 669 | } else if messageCode == 1221 { 670 | throw FMError.errorCode1221DataTypeMustBeNumberOrDateOrTimeOrTimestamp 671 | } else if messageCode == 1222 { 672 | throw FMError.errorCode1222CalculationCannotBeStored 673 | } else if messageCode == 1223 { 674 | throw FMError.errorCode1223FunctionReferredToIsNotYetImplemented 675 | } else if messageCode == 1224 { 676 | throw FMError.errorCode1224FunctionReferredToDoesNotExist 677 | } else if messageCode == 1225 { 678 | throw FMError.errorCode1225FunctionReferredToIsNotSupportedInThisContext 679 | } else if messageCode == 1300 { 680 | throw FMError.errorCode1300TheSpecifiedNameCannotBeUsed 681 | } else if messageCode == 1301 { 682 | throw FMError.errorCode1301AParameterOfTheImportedOrPastedFunctionHasTheSameNameAsAFunctionInTheSameFile 683 | } else if messageCode ?? 0 >= 1400 && messageCode ?? 0 <= 1499 { 684 | throw FMError.errorCode1400ODBCConnectionErrors 685 | } else if messageCode ?? 0 >= 1500 && messageCode ?? 0 <= 1599 { 686 | throw FMError.errorCode1500SMTPConnectionErrors 687 | } else if messageCode == 1626 { 688 | throw FMError.errorCode1626ProtocolIsNotSupported 689 | } else if messageCode == 1627 { 690 | throw FMError.errorCode1627AuthenticationFailed 691 | } else if messageCode == 1628 { 692 | throw FMError.errorCode1628ThereWasAnErrorWithSSL 693 | } else if messageCode == 1629 { 694 | throw FMError.errorCode1629ConnectionTimedOut 695 | } else if messageCode == 1630 { 696 | throw FMError.errorCode1630URLFormatIsIncorrect 697 | } else if messageCode == 1631 { 698 | throw FMError.errorCode1631ConnectionFailed 699 | } else if messageCode == 1632 { 700 | throw FMError.errorCode1632TheCertificateHasExpired 701 | } else if messageCode == 1633 { 702 | throw FMError.errorCode1633TheCertificateIsSelfSigned 703 | } else if messageCode == 1634 { 704 | throw FMError.errorCode1634ACertificateVerificationErrorOccured 705 | } else if messageCode == 1635 { 706 | throw FMError.errorCode1635ConnectionIsUnencrypted 707 | } else if messageCode == 1700 { 708 | throw FMError.errorCode1700ResourceDoesNotExist 709 | } else if messageCode == 1701 { 710 | throw FMError.errorCode1701HostIsCurrentlyEnableToReceiveRequests 711 | } else if messageCode == 1702 { 712 | throw FMError.errorCode1702AuthenticationInformationWasNotProvidedInTheCorrectFormat 713 | } else if messageCode == 1703 { 714 | throw FMError.errorCode1703InvalidUsernameOrPasswordOrJSONWebToken 715 | } else if messageCode == 1704 { 716 | throw FMError.errorCode1704ResourceDoesNotSupportTheSpecifiedHTTPVerb 717 | } else if messageCode == 1705 { 718 | throw FMError.errorCode1705RequiredHTTPHeaderWasNotSpecified 719 | } else if messageCode == 1706 { 720 | throw FMError.errorCode1706ParameterIsNotSupported 721 | } else if messageCode == 1707 { 722 | throw FMError.errorCode1707RequiredParameterWasNotSpecifiedInTheRequest 723 | } else if messageCode == 1708 { 724 | throw FMError.errorCode1708ParameterValueIsInvalid 725 | } else if messageCode == 1709 { 726 | throw FMError.errorCode1709OperationIsInvalidForTheResourceCurrentStatus 727 | } else if messageCode == 1710 { 728 | throw FMError.errorCode1710JSONInputIsNotSyntacticallyValid 729 | } else if messageCode == 1710 { 730 | throw FMError.errorCode1710JSONInputIsNotSyntacticallyValid 731 | } else if messageCode == 1711 { 732 | throw FMError.errorCode1711HostLicenseHasExpired 733 | } else if messageCode == 1712 { 734 | throw FMError.errorCode1712PrivateKeyFileAlreadyExists 735 | } else if messageCode == 1713 { 736 | throw FMError.errorCode1713TheAPIRequestIsNotSupportedForThisOperatingSystem 737 | } else if messageCode == 1714 { 738 | throw FMError.errorCode1714ExternalGroupNameIsInvalid 739 | } else if messageCode == 1715 { 740 | throw FMError.errorCode1715ExternalServerAccountSignInIsNotEnabled 741 | }else{ 742 | throw FMError.undefinedError 743 | } 744 | } 745 | -------------------------------------------------------------------------------- /Sources/FMProKit/supportFunctionRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Coderly Studio (Francesco De Marco, Gianluca Annina, Giuseppe Carannante) on 23/12/22. 6 | // 7 | 8 | import SwiftUI 9 | extension APIProtocol { 10 | /// Build and execute an HTTPRequest fetching all the data 11 | /// - Parameters: 12 | /// - urlTmp: The final url 13 | /// - method: The type of the HTTPMethod 14 | /// - Returns: returns the data fetched from the HTTP request 15 | func executeRequest(urlTmp: String, method: HTTPMethod, isData:Bool = false) async throws -> Data { 16 | guard let url = urlTmp.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), 17 | let requestURL = URL(string: url) else { 18 | throw URLError(.badURL) 19 | } 20 | 21 | var request: URLRequest 22 | request = URLRequest(url: requestURL) 23 | request.addValue(authData, forHTTPHeaderField: "Authorization") 24 | request.httpMethod = method.rawValue 25 | 26 | self.request = request 27 | 28 | let (data, response) = try await URLSession.shared.data(for: request) 29 | 30 | responseJSON = data 31 | do { 32 | try (response as? HTTPURLResponse)?.checkResponseCode() 33 | } catch HTTPError.errorCode500InternalServerError { 34 | let messages = try! JSONDecoder().decode(MessagesModel.self, from: data) 35 | try checkResponseCode(messages: messages.messages) 36 | } 37 | 38 | return data 39 | } 40 | 41 | /// Build and execute an HTTPRequest fetching all the data 42 | /// - Parameters: 43 | /// - urlTmp: The final url 44 | /// - method: The type of the HTTPMethod 45 | /// - data: the data converted to JSON file 46 | /// - Returns: returns the data fetched from the HTTP request 47 | func executeRequest(urlTmp: String, method: HTTPMethod, data: T) async throws -> Data { 48 | guard let url = urlTmp.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), 49 | let requestURL = URL(string: url) else { 50 | throw URLError(.badURL) 51 | } 52 | 53 | var request: URLRequest 54 | request = URLRequest(url: requestURL) 55 | request.addValue(authData, forHTTPHeaderField: "Authorization") 56 | request.httpMethod = method.rawValue 57 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 58 | 59 | self.request = request 60 | 61 | let encoded = try JSONEncoder().encode(data) 62 | let (data, response) = try await URLSession.shared.upload(for: request, from: encoded) 63 | 64 | responseJSON = data 65 | 66 | do { 67 | try (response as? HTTPURLResponse)?.checkResponseCode() 68 | } catch HTTPError.errorCode500InternalServerError { 69 | let messages = try! JSONDecoder().decode(MessagesModel.self, from: data) 70 | try checkResponseCode(messages: messages.messages) 71 | } 72 | 73 | return data 74 | } 75 | } 76 | --------------------------------------------------------------------------------