├── .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 | 
2 |
3 |
4 |   
5 |
6 | ---
7 |
8 |
9 | # FMProKit - an easier way to communicate with FileMaker
10 |
11 | [](#)
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://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://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://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://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://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 | [](#)
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://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 |
--------------------------------------------------------------------------------