From 9469a0aff2024a56efd08f83ff3a2a5ee727995b Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Sat, 13 Sep 2025 11:25:35 +0200 Subject: [PATCH] Better handling of Nutriments --- .swift-format | 69 ++ Package.resolved | 15 + Package.swift | 56 +- Sources/OpenFoodFacts/Extension+String.swift | 34 +- Sources/OpenFoodFacts/OpenFoodFacts.swift | 151 +-- Sources/OpenFoodFacts/Schemas/Images.swift | 15 + .../OpenFoodFacts/Schemas/Ingredient.swift | 21 + .../Schemas/LanguagesCodes.swift | 5 + .../Schemas/NutrientLevels.swift | 13 + .../OpenFoodFacts/Schemas/Nutriments.swift | 239 +++++ .../Schemas/ObjectDebugger.swift | 58 ++ .../Schemas/PerlSearchQuery.swift | 63 ++ Sources/OpenFoodFacts/Schemas/Product.swift | 932 ++++++++++++++++++ .../Schemas/ProductResponse.swift | 13 + .../Schemas/SearchNutriment.swift | 98 ++ .../Schemas/SearchResponse.swift | 14 + Sources/OpenFoodFacts/Schemas/SearchTag.swift | 17 + .../OpenFoodFacts/Schemas/SelectedImage.swift | 14 + .../Schemas/SelectedImageItem.swift | 15 + .../Schemas/SelectedImages.swift | 14 + Sources/OpenFoodFacts/Schemas/Source.swift | 19 + Sources/OpenFoodFacts/types/Images.swift | 15 - Sources/OpenFoodFacts/types/Ingredient.swift | 21 - .../OpenFoodFacts/types/LanguagesCodes.swift | 6 - .../OpenFoodFacts/types/NutrientLevels.swift | 13 - Sources/OpenFoodFacts/types/Nutriments.swift | 237 ----- .../OpenFoodFacts/types/ObjectDebugger.swift | 57 -- .../OpenFoodFacts/types/PerlSearchQuery.swift | 56 -- Sources/OpenFoodFacts/types/Product.swift | 726 -------------- .../OpenFoodFacts/types/ProductResponse.swift | 14 - .../OpenFoodFacts/types/SearchNutriment.swift | 98 -- .../OpenFoodFacts/types/SearchResponse.swift | 14 - Sources/OpenFoodFacts/types/SearchTag.swift | 17 - .../OpenFoodFacts/types/SelectedImage.swift | 11 - .../types/SelectedImageItem.swift | 15 - .../OpenFoodFacts/types/SelectedImages.swift | 11 - Sources/OpenFoodFacts/types/Source.swift | 19 - 37 files changed, 1765 insertions(+), 1440 deletions(-) create mode 100644 .swift-format create mode 100644 Package.resolved create mode 100644 Sources/OpenFoodFacts/Schemas/Images.swift create mode 100644 Sources/OpenFoodFacts/Schemas/Ingredient.swift create mode 100644 Sources/OpenFoodFacts/Schemas/LanguagesCodes.swift create mode 100644 Sources/OpenFoodFacts/Schemas/NutrientLevels.swift create mode 100644 Sources/OpenFoodFacts/Schemas/Nutriments.swift create mode 100644 Sources/OpenFoodFacts/Schemas/ObjectDebugger.swift create mode 100644 Sources/OpenFoodFacts/Schemas/PerlSearchQuery.swift create mode 100644 Sources/OpenFoodFacts/Schemas/Product.swift create mode 100644 Sources/OpenFoodFacts/Schemas/ProductResponse.swift create mode 100644 Sources/OpenFoodFacts/Schemas/SearchNutriment.swift create mode 100644 Sources/OpenFoodFacts/Schemas/SearchResponse.swift create mode 100644 Sources/OpenFoodFacts/Schemas/SearchTag.swift create mode 100644 Sources/OpenFoodFacts/Schemas/SelectedImage.swift create mode 100644 Sources/OpenFoodFacts/Schemas/SelectedImageItem.swift create mode 100644 Sources/OpenFoodFacts/Schemas/SelectedImages.swift create mode 100644 Sources/OpenFoodFacts/Schemas/Source.swift delete mode 100644 Sources/OpenFoodFacts/types/Images.swift delete mode 100644 Sources/OpenFoodFacts/types/Ingredient.swift delete mode 100644 Sources/OpenFoodFacts/types/LanguagesCodes.swift delete mode 100644 Sources/OpenFoodFacts/types/NutrientLevels.swift delete mode 100644 Sources/OpenFoodFacts/types/Nutriments.swift delete mode 100644 Sources/OpenFoodFacts/types/ObjectDebugger.swift delete mode 100644 Sources/OpenFoodFacts/types/PerlSearchQuery.swift delete mode 100644 Sources/OpenFoodFacts/types/Product.swift delete mode 100644 Sources/OpenFoodFacts/types/ProductResponse.swift delete mode 100644 Sources/OpenFoodFacts/types/SearchNutriment.swift delete mode 100644 Sources/OpenFoodFacts/types/SearchResponse.swift delete mode 100644 Sources/OpenFoodFacts/types/SearchTag.swift delete mode 100644 Sources/OpenFoodFacts/types/SelectedImage.swift delete mode 100644 Sources/OpenFoodFacts/types/SelectedImageItem.swift delete mode 100644 Sources/OpenFoodFacts/types/SelectedImages.swift delete mode 100644 Sources/OpenFoodFacts/types/Source.swift diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..d226c08 --- /dev/null +++ b/.swift-format @@ -0,0 +1,69 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "tabs" : 1 + }, + "indentConditionalCompilationBlocks" : true, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : false, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 80, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : false, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 4, + "version" : 1 +} diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..97ea9d0 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "45ae84ae5c5095f4d85c870c36fff7e4577f267c4a02fa550762071d2f9eca53", + "pins" : [ + { + "identity" : "swift-units", + "kind" : "remoteSourceControl", + "location" : "git@git.cems.dev:cdricms/swift-units.git", + "state" : { + "branch" : "master", + "revision" : "0962ae290044d24722610234cb215b748722f196" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift index 49c0516..f0010db 100644 --- a/Package.swift +++ b/Package.swift @@ -1,30 +1,38 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( - name: "swift-openfoodfacts-sdk", - platforms: [ - .macOS(.v14), - .iOS(.v15), - ], - products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. - .library( - name: "OpenFoodFacts", - targets: ["OpenFoodFacts"] - ), - ], - targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. - .target( - name: "OpenFoodFacts", path: "Sources/OpenFoodFacts" - ), - .testTarget( - name: "OpenFoodFactsTests", - dependencies: ["OpenFoodFacts"] - ), - ] + name: "swift-openfoodfacts-sdk", + platforms: [ + .macOS(.v14), + .iOS(.v15), + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "OpenFoodFacts", + targets: ["OpenFoodFacts"] + ) + ], + dependencies: [ + .package( + url: "git@git.cems.dev:cdricms/swift-units.git", branch: "master") + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "OpenFoodFacts", + dependencies: [ + .product(name: "Units", package: "swift-units") + ], + path: "Sources/OpenFoodFacts", + ), + .testTarget( + name: "OpenFoodFactsTests", + dependencies: ["OpenFoodFacts"] + ), + ] ) diff --git a/Sources/OpenFoodFacts/Extension+String.swift b/Sources/OpenFoodFacts/Extension+String.swift index d12fb91..b5de910 100644 --- a/Sources/OpenFoodFacts/Extension+String.swift +++ b/Sources/OpenFoodFacts/Extension+String.swift @@ -1,21 +1,21 @@ extension String { - func camelCaseToSnakeCase() -> String { - var result = "" - var lastCharacterWasUppercase = false + func camelCaseToSnakeCase() -> String { + var result = "" + var lastCharacterWasUppercase = false - for character in self { - if character.isUppercase { - if !result.isEmpty && !lastCharacterWasUppercase { - result.append("_") - } - result.append(character.lowercased()) - lastCharacterWasUppercase = true - } else { - result.append(character) - lastCharacterWasUppercase = false - } - } + for character in self { + if character.isUppercase { + if !result.isEmpty && !lastCharacterWasUppercase { + result.append("_") + } + result.append(character.lowercased()) + lastCharacterWasUppercase = true + } else { + result.append(character) + lastCharacterWasUppercase = false + } + } - return result - } + return result + } } diff --git a/Sources/OpenFoodFacts/OpenFoodFacts.swift b/Sources/OpenFoodFacts/OpenFoodFacts.swift index 53a034d..e6922a0 100644 --- a/Sources/OpenFoodFacts/OpenFoodFacts.swift +++ b/Sources/OpenFoodFacts/OpenFoodFacts.swift @@ -4,84 +4,97 @@ import Foundation public class OpenFoodFactsClient { - let version: Int = 0 - public var prod: Bool = false - var baseURL: URL? { - if prod { - return URL(string: "https://world.openfoodfacts.org/api/v\(version)") - } else { - return URL(string: "https://world.openfoodfacts.net/api/v\(version)") - } - } + let version: Int = 0 + public var prod: Bool = false + var baseURL: URL? { + if prod { + return URL( + string: "https://world.openfoodfacts.org/api/v\(version)") + } else { + return URL( + string: "https://world.openfoodfacts.net/api/v\(version)") + } + } - public init() {} + public init() {} - public func getProductByBarcode(_ barcode: String) async throws -> ProductResponse { - guard let endpoint = baseURL?.appendingPathComponent("product/\(barcode)") else { throw OFFError.invalidURL } - var request = URLRequest(url: endpoint) - request.setValue("application/json", forHTTPHeaderField: "accept") - let (data, response) = try await URLSession.shared.data(for: request) + public func getProductByBarcode(_ barcode: String) async throws + -> ProductResponse + { + guard + let endpoint = baseURL?.appendingPathComponent("product/\(barcode)") + else { throw OFFError.invalidURL } + var request = URLRequest(url: endpoint) + request.setValue("application/json", forHTTPHeaderField: "accept") + let (data, response) = try await URLSession.shared.data(for: request) - guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { - throw OFFError.invalidResponse - } - do { - return try JSONDecoder().decode(ProductResponse.self, from: data) - } - } + guard let response = response as? HTTPURLResponse, + response.statusCode == 200 + else { + throw OFFError.invalidResponse + } + do { + return try JSONDecoder().decode(ProductResponse.self, from: data) + } + } - public struct SearchQuery { - public let additivesTags: String? - public let allergensTags: String? - public let brandsTags: String? - public let categoriesTags: String? - public let countriesTagsEn: String? - public let embCodesTags: String? - public let labelsTags: String? - public let manufacturingPlacesTags: String? - public let nutritionGradesTags: String? - public let originsTags: String? - public let packagingTagsDe: String? - public let purchasePlacesTags: String? - } + public struct SearchQuery { + public let additivesTags: String? + public let allergensTags: String? + public let brandsTags: String? + public let categoriesTags: String? + public let countriesTagsEn: String? + public let embCodesTags: String? + public let labelsTags: String? + public let manufacturingPlacesTags: String? + public let nutritionGradesTags: String? + public let originsTags: String? + public let packagingTagsDe: String? + public let purchasePlacesTags: String? + } - // public func search(_ productName: String, queryParams: SearchQuery? = nil) async throws -> SearchResponse { - // let qp = Mirror(reflecting: queryParams ?? {}) - // var s: String = "?product_name=\(productName)&" - // for case let (label?, value) in qp.children { - // s += label.camelCaseToSnakeCase() + "=" + (value as! String) + "&" - // } - // guard let endpoint = baseURL?.appendingPathComponent("search\(s)") else { throw OFFError.invalidURL } - // var request = URLRequest(url: endpoint) - // request.setValue("application/json", forHTTPHeaderField: "accept") + // public func search(_ productName: String, queryParams: SearchQuery? = nil) async throws -> SearchResponse { + // let qp = Mirror(reflecting: queryParams ?? {}) + // var s: String = "?product_name=\(productName)&" + // for case let (label?, value) in qp.children { + // s += label.camelCaseToSnakeCase() + "=" + (value as! String) + "&" + // } + // guard let endpoint = baseURL?.appendingPathComponent("search\(s)") else { throw OFFError.invalidURL } + // var request = URLRequest(url: endpoint) + // request.setValue("application/json", forHTTPHeaderField: "accept") - // let (data, response) = try await URLSession.shared.data(for: request) - // guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { - // throw OFFError.invalidResponse - // } - // do { - // return try JSONDecoder().decode(SearchResponse.self, from: data) - // } - // } + // let (data, response) = try await URLSession.shared.data(for: request) + // guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { + // throw OFFError.invalidResponse + // } + // do { + // return try JSONDecoder().decode(SearchResponse.self, from: data) + // } + // } - //https://wiki.openfoodfacts.org/API/Read/Search#Parameters - public func search(query: PerlSearchQuery) async throws -> SearchResponse { - let endpoint = URL(string: "https://world.openfoodfacts.org/cgi/search.pl?\(query.makeToRequest())")! - print(endpoint) - let request = URLRequest(url: endpoint) - // request.setValue("application/json", forHTTPHeaderField: "accept") - let (data, response) = try await URLSession.shared.data(for: request) + //https://wiki.openfoodfacts.org/API/Read/Search#Parameters + public func search(query: PerlSearchQuery) async throws -> SearchResponse { + let endpoint = URL( + string: + "https://world.openfoodfacts.org/cgi/search.pl?\(query.makeToRequest())" + )! + print(endpoint) + let request = URLRequest(url: endpoint) + // request.setValue("application/json", forHTTPHeaderField: "accept") + let (data, response) = try await URLSession.shared.data(for: request) - guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { - throw OFFError.invalidResponse - } - do { - return try JSONDecoder().decode(SearchResponse.self, from: data) - } - - } + guard let response = response as? HTTPURLResponse, + response.statusCode == 200 + else { + throw OFFError.invalidResponse + } + do { + return try JSONDecoder().decode(SearchResponse.self, from: data) + } + + } } enum OFFError: Error { - case invalidURL, invalidResponse + case invalidURL, invalidResponse } diff --git a/Sources/OpenFoodFacts/Schemas/Images.swift b/Sources/OpenFoodFacts/Schemas/Images.swift new file mode 100644 index 0000000..d0285b0 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/Images.swift @@ -0,0 +1,15 @@ +import Foundation + +public struct Images: Codable { + public var otherData: [String: Data] = [:] + + mutating func setDetail(key: String, value: T) throws { + let encodedValue = try JSONEncoder().encode(value) + otherData[key] = encodedValue + } + + func getDetail(key: String, type: T.Type) throws -> T? { + guard let data = otherData[key] else { return nil } + return try JSONDecoder().decode(type, from: data) + } +} diff --git a/Sources/OpenFoodFacts/Schemas/Ingredient.swift b/Sources/OpenFoodFacts/Schemas/Ingredient.swift new file mode 100644 index 0000000..aa64ffa --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/Ingredient.swift @@ -0,0 +1,21 @@ +public struct Ingredient: Codable, ObjectDebugger { + public var fromPalmOil: String? = nil + public var id: String? = nil + public var origin: String? = nil + public var percent: Float? = nil + public var rank: Float? = 0 + public var text: String? = nil + public var vegan: String? = nil + public var vegetarian: String? = nil + + private enum CodingKeys: String, CodingKey { + case fromPalmOil = "from_palm_oil" + case id + case origin + case percent + case rank + case text + case vegan + case vegetarian + } +} diff --git a/Sources/OpenFoodFacts/Schemas/LanguagesCodes.swift b/Sources/OpenFoodFacts/Schemas/LanguagesCodes.swift new file mode 100644 index 0000000..5f155a2 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/LanguagesCodes.swift @@ -0,0 +1,5 @@ +public struct LanguagesCodes: Codable, ObjectDebugger { + public var en: Float? = nil + public var fr: Float? = nil + public var pl: Float? = nil +} diff --git a/Sources/OpenFoodFacts/Schemas/NutrientLevels.swift b/Sources/OpenFoodFacts/Schemas/NutrientLevels.swift new file mode 100644 index 0000000..da03cfc --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/NutrientLevels.swift @@ -0,0 +1,13 @@ +public struct NutrientLevels: Codable, ObjectDebugger { + public var fat: String? = nil + public var salt: String? = nil + public var saturatedFat: String? = nil + public var sugars: String? = nil + + private enum CodingKeys: String, CodingKey { + case fat + case salt + case saturatedFat = "saturated-fat" + case sugars + } +} diff --git a/Sources/OpenFoodFacts/Schemas/Nutriments.swift b/Sources/OpenFoodFacts/Schemas/Nutriments.swift new file mode 100644 index 0000000..2ef06b5 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/Nutriments.swift @@ -0,0 +1,239 @@ +import Foundation +import Units + +@dynamicMemberLookup +public struct Nutriments: Codable, ObjectDebugger { + private var nutrients: [String: Nutrient] = [:] // Key is accessName ( - replaced with _ ) + + public init() {} + + public subscript(dynamicMember member: String) -> Nutrient? { + nutrients[member] + } + + // MARK: - Decoding + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: AnyCodingKey.self) + for key in container.allKeys { + let keyStr = key.stringValue + let parts = keyStr.components(separatedBy: "_") + + var nutrientName = "" // Original with - + var field = "" + var isPrepared = false + + if let preparedIdx = parts.firstIndex(of: "prepared") { + isPrepared = true + nutrientName = parts[0..? { + guard let rawValue = per100g ?? value ?? valueComputed, + let unitString = unit, + let unitEnum = Unit(rawValue: unitString) + else { + return nil + } + return UnitValue(value: rawValue, unit: unitEnum) + } + + // Computed UnitValue for perServing + public var perServingUnitValue: UnitValue? { + guard let rawValue = perServing, + let unitString = unit, + let unitEnum = Unit(rawValue: unitString) + else { + return nil + } + return UnitValue(value: rawValue, unit: unitEnum) + } + + // Computed UnitValue for preparedPer100g (fallback similar) + public var preparedPer100gUnitValue: UnitValue? { + guard + let rawValue = preparedPer100g ?? preparedValue + ?? preparedValueComputed, + let unitString = preparedUnit ?? unit, // Fallback to main unit if preparedUnit nil + let unitEnum = Unit(rawValue: unitString) + else { + return nil + } + return UnitValue(value: rawValue, unit: unitEnum) + } + + // Computed UnitValue for preparedPerServing + public var preparedPerServingUnitValue: UnitValue? { + guard let rawValue = preparedPerServing, + let unitString = preparedUnit ?? unit, + let unitEnum = Unit(rawValue: unitString) + else { + return nil + } + return UnitValue(value: rawValue, unit: unitEnum) + } +} + +// MARK: - Helper for Dynamic Coding Keys +struct AnyCodingKey: CodingKey, Hashable { + var stringValue: String + var intValue: Int? + + init(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + init(intValue: Int) { + self.intValue = intValue + self.stringValue = String(intValue) + } +} diff --git a/Sources/OpenFoodFacts/Schemas/ObjectDebugger.swift b/Sources/OpenFoodFacts/Schemas/ObjectDebugger.swift new file mode 100644 index 0000000..6bc9e75 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/ObjectDebugger.swift @@ -0,0 +1,58 @@ +public protocol ObjectDebugger: CustomStringConvertible { + var description: String { get } +} + +extension ObjectDebugger { + public var description: String { + var description = "\(type(of: self))(" + let mirror = Mirror(reflecting: self) + + for (label, value) in mirror.children { + if let label = label { + description += "\(label): \(value), " + } + } + + // Remove the trailing comma and space + description = String(description.dropLast(2)) + description += ")" + + return description + } + + private func prettyPrint(object: Any, indentation: String = "") -> String { + var description = "\(type(of: object)) {" + + let mirror = Mirror(reflecting: object) + + for (label, value) in mirror.children { + if let label = label { + let childDescription: String + switch value { + case let nestedObject as CustomStringConvertible: + childDescription = prettyPrint( + object: nestedObject, indentation: "\(indentation) ") + case let stringValue as String: + childDescription = "\"\(stringValue)\"" + case let floatValue as Float: + childDescription = "\(floatValue)" + case let intValue as Int: + childDescription = "\(intValue)" + case let optionalValue as CustomStringConvertible?: + if let unwrapped = optionalValue { + childDescription = "\(unwrapped)" + } else { + childDescription = "nil" + } + default: + childDescription = "\(value)" + } + + description += "\n\(indentation) \(label): \(childDescription)" + } + } + + description += "\n\(indentation)}" + return description + } +} diff --git a/Sources/OpenFoodFacts/Schemas/PerlSearchQuery.swift b/Sources/OpenFoodFacts/Schemas/PerlSearchQuery.swift new file mode 100644 index 0000000..e6464cb --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/PerlSearchQuery.swift @@ -0,0 +1,63 @@ +public enum PerlOperator: String { + case lt, lte, gt, gte, eq +} + +public enum PerlFormat: String { + case json, xml, jqm +} + +public struct SearchNutrimentEntry { + public let nutriment: SearchNutriment + public let op: PerlOperator + public let value: Int +} + +public struct SearchTagsEntry { + public let tag: SearchTag + public let value: String + public let contains: Bool = true +} + +//https://wiki.openfoodfacts.org/API/Read/Search#Parameters +public struct PerlSearchQuery { + public var searchTerms: String + public var searchTags: [SearchTagsEntry]? // tagtype_i=SearchTag — i as #element (starting from 0); (tag_contains_i|tag_does_not_contain_i)=String + public var searchNutriment: [SearchNutrimentEntry]? // nutriment_i=SearchNutriment — i as #element (starting from 0); nutriment_compare_i=PerlOperator; nutriment_value_i=String + public var page: Int = 1 // Pagination + public var format: PerlFormat // json=1 | xml=1 | jqm=1 + + public init( + searchTerms: String, searchTags: [SearchTagsEntry]? = nil, + searchNutriment: [SearchNutrimentEntry]? = nil, page: Int = 1, + format: PerlFormat = .json + ) { + self.searchTerms = searchTerms + self.searchTags = searchTags + self.searchNutriment = searchNutriment + self.page = page + self.format = format + } + + public func makeToRequest() -> String { + var _searchTags: String? + var _searchNutriment: String? + let _format: String = "\(format.rawValue)=1" + let _page: String = "page=\(page)" + if let tags = searchTags { + _searchTags = tags.enumerated().map { i, v in + return + "tagtype_\(i)=\(v.tag.rawValue)&tag_contains_\(i)=\(v.contains ? "contains" : "does_not_contain")&tag_\(i)=\(v.value)" + }.joined(separator: "&") + } + if let nutriments = searchNutriment { + _searchNutriment = nutriments.enumerated().map { i, v in + return + "nutriment_\(i)=\(v.nutriment.rawValue)&nutriment_compare_\(i)=\(v.op.rawValue)&nutriment_value_\(i)=\(v.nutriment.rawValue)" + }.joined(separator: "&") + } + + return + "search_terms=\(searchTerms)\(_searchTags != nil ? "&\(_searchTags!)" : "")\(_searchNutriment != nil ? "&\(_searchNutriment!)" : "")&\(_page)&\(_format)" + + } +} diff --git a/Sources/OpenFoodFacts/Schemas/Product.swift b/Sources/OpenFoodFacts/Schemas/Product.swift new file mode 100644 index 0000000..dcbe5fe --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/Product.swift @@ -0,0 +1,932 @@ +public class Product: Codable, ObjectDebugger { + // public var images: Images? = Images() + public var ingredients: [Ingredient]? = [] + public var languagesCodes: LanguagesCodes? + public var nutrientLevels: NutrientLevels? + public var nutriments: Nutriments? = Nutriments() + public var selectedImages: SelectedImages? + public var sources: [Source]? = [] + public var additivesN: Float? + public var additivesOldN: Float? + public var additivesOriginalTags: [String]? + public var additivesOldTags: [String]? + public var additivesPrevOriginalTags: [String]? + public var additivesDebugTags: [String]? + public var additivesTags: [String]? + public var allergens: String? + public var allergensFromIngredients: String? + public var allergensFromUser: String? + public var allergensHierarchy: [String]? + public var allergensLc: String? + public var allergensTags: [String]? + public var aminoAcidsPrevTags: [String]? + public var aminoAcidsTags: [String]? + public var brands: String? + public var brandsDebugTags: [String]? + public var brandsTags: [String]? + public var carbonFootprintPercentOfKnownIngredients: Float? + public var carbonFootprintFromKnownIngredientsDebug: String? + public var categories: String? + public var categoriesHierarchy: [String]? + public var categoriesLc: String? + public var categoriesPropertiesTags: [String]? + public var categoriesTags: [String]? + public var checkersTags: [String]? + public var citiesTags: [String]? + public var code: String? + public var codesTags: [String]? + public var comparedToCategory: String? + public var complete: Float? + public var completedT: Float? + public var completeness: Double? + public var conservationConditions: String? + public var countries: String? + public var countriesHierarchy: [String]? + public var countriesLc: String? + public var countriesDebugTags: [String]? + public var countriesTags: [String]? + public var correctorsTags: [String]? + public var createdT: Float? + public var creator: String? + public var dataQualityBugsTags: [String]? + public var dataQualityErrorsTags: [String]? + public var dataQualityInfoTags: [String]? + public var dataQualityTags: [String]? + public var dataQualityWarningsTags: [String]? + public var dataSources: String? + public var dataSourcesTags: [String]? + public var debugParamSortedLangs: [String]? + public var editorsTags: [String]? + public var embCodes: String? + public var embCodesDebugTags: [String]? + public var embCodesOrig: String? + public var embCodesTags: [String]? + public var entryDatesTags: [String]? + public var expirationDate: String? + public var expirationDateDebugTags: [String]? + public var fruitsVegetablesNuts100GEstimate: Float? + public var genericName: String? + public var id: String? + public var imageFrontSmallUrl: String? + public var imageFrontThumbUrl: String? + public var imageFrontUrl: String? + public var imageIngredientsUrl: String? + public var imageIngredientsSmallUrl: String? + public var imageIngredientsThumbUrl: String? + public var imageNutritionSmallUrl: String? + public var imageNutritionThumbUrl: String? + public var imageNutritionUrl: String? + public var imageSmallUrl: String? + public var imageThumbUrl: String? + public var imageUrl: String? + public var informersTags: [String]? + public var ingredientsAnalysisTags: [String]? + public var ingredientsDebug: [String?]? + public var ingredientsFromOrThatMayBeFromPalmOilN: Float? + public var ingredientsFromPalmOilTags: [String]? + public var ingredientsFromPalmOilN: Float? + public var ingredientsHierarchy: [String]? + public var ingredientsIdsDebug: [String]? + public var ingredientsN: Float? + public var ingredientsNTags: [String]? + public var ingredientsOriginalTags: [String]? + public var ingredientsTags: [String]? + public var ingredientsText: String? + public var ingredientsTextDebug: String? + public var ingredientsTextWithAllergens: String? + public var ingredientsThatMayBeFromPalmOilN: Float? + public var ingredientsThatMayBeFromPalmOilTags: [String]? + public var interfaceVersionCreated: String? + public var interfaceVersionModified: String? + public var keywords: [String]? + public var knownIngredientsN: Float? + public var labels: String? + public var labelsHierarchy: [String]? + public var labelsLc: String? + public var labelsPrevHierarchy: [String]? + public var labelsPrevTags: [String]? + public var labelsTags: [String]? + public var labelsDebugTags: [String]? + public var lang: String? + public var langDebugTags: [String]? + public var languagesHierarchy: [String]? + public var languagesTags: [String]? + public var lastEditDatesTags: [String]? + public var lastEditor: String? + public var lastImageDatesTags: [String]? + public var lastImageT: Float? + public var lastModifiedBy: String? + public var lastModifiedT: Float? + public var lc: String? + public var link: String? + public var linkDebugTags: [String]? + public var manufacturingPlaces: String? + public var manufacturingPlacesDebugTags: [String]? + public var manufacturingPlacesTags: [String]? + public var maxImgid: String? + public var mineralsPrevTags: [String]? + public var mineralsTags: [String]? + public var miscTags: [String]? + public var netWeightUnit: String? + public var netWeightValue: String? + public var nutritionDataPer: String? + public var nutritionScoreWarningNoFruitsVegetablesNuts: Float? + public var nutriscoreGrade: String? + public var noNutritionData: String? + public var novaGroup: Float? + public var novaGroups: String? + public var novaGroupDebug: String? + public var novaGroupTags: [String]? + public var novaGroupsTags: [String]? + public var nucleotidesPrevTags: [String]? + public var nucleotidesTags: [String]? + public var nutrientLevelsTags: [String]? + public var nutritionData: String? + public var nutritionDataPerDebugTags: [String]? + public var nutritionDataPrepared: String? + public var nutritionDataPreparedPer: String? + public var nutritionGrades: String? + public var nutritionScoreBeverage: Float? + public var nutritionScoreDebug: String? + public var nutritionScoreWarningNoFiber: Float? + public var nutritionGradesTags: [String]? + public var origins: String? + public var originsDebugTags: [String]? + public var originsTags: [String]? + public var otherInformation: String? + public var otherNutritionalSubstancesTags: [String]? + public var packaging: String? + public var packagingDebugTags: [String]? + public var packagingTags: [String]? + public var photographersTags: [String]? + public var pnnsGroups1: String? + public var pnnsGroups2: String? + public var pnnsGroups1Tags: [String]? + public var pnnsGroups2Tags: [String]? + public var popularityKey: Float? + public var producerVersionId: String? + public var productName: String? + public var productQuantity: Float? + public var purchasePlaces: String? + public var purchasePlacesDebugTags: [String]? + public var purchasePlacesTags: [String]? + public var qualityTags: [String]? + public var quantity: String? + public var quantityDebugTags: [String]? + public var recyclingInstructionsToDiscard: String? + public var rev: Float? + public var servingQuantity: String? + public var servingSize: String? + public var servingSizeDebugTags: [String]? + public var sortkey: Float? + public var states: String? + public var statesHierarchy: [String]? + public var statesTags: [String]? + public var stores: String? + public var storesDebugTags: [String]? + public var storesTags: [String]? + public var traces: String? + public var tracesFromIngredients: String? + public var tracesHierarchy: [String]? + public var tracesDebugTags: [String]? + public var tracesFromUser: String? + public var tracesLc: String? + public var tracesTags: [String]? + public var unknownIngredientsN: Float? + public var unknownNutrientsTags: [String]? + public var updateKey: String? + public var vitaminsPrevTags: [String]? + public var vitaminsTags: [String]? + + private enum CodingKeys: String, CodingKey { + // case images + case ingredients + case languagesCodes = "languages_codes" + case nutrientLevels = "nutrient_levels" + case nutriments + case selectedImages = "selected_images" + case sources + case additivesN = "additives_n" + case additivesOldN = "additives_old_n" + case additivesOriginalTags = "additives_original_tags" + case additivesOldTags = "additives_old_tags" + case additivesPrevOriginalTags = "additives_prev_original_tags" + case additivesDebugTags = "additives_debug_tags" + case additivesTags = "additives_tags" + case allergens + case allergensFromIngredients = "allergens_from_ingredients" + case allergensFromUser = "allergens_from_user" + case allergensHierarchy = "allergens_hierarchy" + case allergensLc = "allergens_lc" + case allergensTags = "allergens_tags" + case aminoAcidsPrevTags = "amino_acids_prev_tags" + case aminoAcidsTags = "amino_acids_tags" + case brands + case brandsDebugTags = "brands_debug_tags" + case brandsTags = "brands_tags" + case carbonFootprintPercentOfKnownIngredients = + "carbon_footprint_percent_of_known_ingredients" + case carbonFootprintFromKnownIngredientsDebug = + "carbon_footprint_from_known_ingredients_debug" + case categories + case categoriesHierarchy = "categories_hierarchy" + case categoriesLc = "categories_lc" + case categoriesPropertiesTags = "categories_properties_tags" + case categoriesTags = "categories_tags" + case checkersTags = "checkers_tags" + case citiesTags = "cities_tags" + case code + case codesTags = "codes_tags" + case comparedToCategory = "compared_to_category" + case complete + case completedT = "completed_t" + case completeness + case conservationConditions = "conservation_conditions" + case countries + case countriesHierarchy = "countries_hierarchy" + case countriesLc = "countries_lc" + case countriesDebugTags = "countries_debug_tags" + case countriesTags = "countries_tags" + case correctorsTags = "correctors_tags" + case createdT = "created_t" + case creator + case dataQualityBugsTags = "data_quality_bugs_tags" + case dataQualityErrorsTags = "data_quality_errors_tags" + case dataQualityInfoTags = "data_quality_info_tags" + case dataQualityTags = "data_quality_tags" + case dataQualityWarningsTags = "data_quality_warnings_tags" + case dataSources = "data_sources" + case dataSourcesTags = "data_sources_tags" + case debugParamSortedLangs = "debug_param_sorted_langs" + case editorsTags = "editors_tags" + case embCodes = "emb_codes" + case embCodesDebugTags = "emb_codes_debug_tags" + case embCodesOrig = "emb_codes_orig" + case embCodesTags = "emb_codes_tags" + case entryDatesTags = "entry_dates_tags" + case expirationDate = "expiration_date" + case expirationDateDebugTags = "expiration_date_debug_tags" + case fruitsVegetablesNuts100GEstimate = + "fruits-vegetables-nuts_100g_estimate" + case genericName + case id + case imageFrontSmallUrl = "image_front_small_url" + case imageFrontThumbUrl = "image_front_thumb_url" + case imageFrontUrl = "image_front_url" + case imageIngredientsUrl = "image_ingredients_url" + case imageIngredientsSmallUrl = "image_ingredients_small_url" + case imageIngredientsThumbUrl = "image_ingredients_thumb_url" + case imageNutritionSmallUrl = "image_nutrition_small_url" + case imageNutritionThumbUrl = "image_nutrition_thumb_url" + case imageNutritionUrl = "image_nutrition_url" + case imageSmallUrl = "image_small_url" + case imageThumbUrl = "image_thumb_url" + case imageUrl = "image_url" + case informersTags = "informers_tags" + case ingredientsAnalysisTags = "ingredients_analysis_tags" + case ingredientsDebug = "ingredients_debug" + case ingredientsFromOrThatMayBeFromPalmOilN = + "ingredients_from_or_that_may_be_from_palm_oil_n" + case ingredientsFromPalmOilTags = "ingredients_from_palm_oil_tags" + case ingredientsFromPalmOilN = "ingredients_from_palm_oil_n" + case ingredientsHierarchy = "ingredients_hierarchy" + case ingredientsIdsDebug = "ingredients_ids_debug" + case ingredientsN = "ingredients_n" + case ingredientsNTags = "ingredients_n_tags" + case ingredientsOriginalTags = "ingredients_original_tags" + case ingredientsTags = "ingredients_tags" + case ingredientsText = "ingredients_text" + case ingredientsTextDebug = "ingredients_text_debug" + case ingredientsTextWithAllergens = "ingredients_text_with_allergens" + case ingredientsThatMayBeFromPalmOilN = + "ingredients_that_may_be_from_palm_oil_n" + case ingredientsThatMayBeFromPalmOilTags = + "ingredients_that_may_be_from_palm_oil_tags" + case interfaceVersionCreated = "interface_version_created" + case interfaceVersionModified = "interface_version_modified" + case keywords + case knownIngredientsN = "known_ingredients_n" + case labels + case labelsHierarchy = "labels_hierarchy" + case labelsLc = "labels_lc" + case labelsPrevHierarchy = "labels_prev_hierarchy" + case labelsPrevTags = "labels_prev_tags" + case labelsTags = "labels_tags" + case labelsDebugTags = "labels_debug_tags" + case lang + case langDebugTags = "lang_debug_tags" + case languagesHierarchy = "languages_hierarchy" + case languagesTags = "languages_tags" + case lastEditDatesTags = "last_edit_dates_tags" + case lastEditor = "last_editor" + case lastImageDatesTags = "last_image_dates_tags" + case lastImageT = "last_image_t" + case lastModifiedBy = "last_modified_by" + case lastModifiedT = "last_modified_t" + case lc + case link + case linkDebugTags = "link_debug_tags" + case manufacturingPlaces = "manufacturing_places" + case manufacturingPlacesDebugTags = "manufacturing_places_debug_tags" + case manufacturingPlacesTags = "manufacturing_places_tags" + case maxImgid = "max_imgid" + case mineralsPrevTags = "minerals_prev_tags" + case mineralsTags = "minerals_tags" + case miscTags = "misc_tags" + case netWeightUnit = "net_weight_unit" + case netWeightValue = "net_weight_value" + case nutritionDataPer = "nutrition_data_per" + case nutritionScoreWarningNoFruitsVegetablesNuts = + "nutrition_score_warning_no_fruits_vegetables_nuts" + case nutriscoreGrade = "nutriscore_grade" + case noNutritionData = "no_nutrition_data" + case novaGroup = "nova_group" + case novaGroups = "nova_groups" + case novaGroupDebug = "nova_group_debug" + case novaGroupTags = "nova_group_tags" + case novaGroupsTags = "nova_groups_tags" + case nucleotidesPrevTags = "nucleotides_prev_tags" + case nucleotidesTags = "nucleotides_tags" + case nutrientLevelsTags = "nutrient_levels_tags" + case nutritionData = "nutrition_data" + case nutritionDataPerDebugTags = "nutrition_data_per_debug_tags" + case nutritionDataPrepared = "nutrition_data_prepared" + case nutritionDataPreparedPer = "nutrition_data_prepared_per" + case nutritionGrades = "nutrition_grades" + case nutritionScoreBeverage = "nutrition_score_beverage" + case nutritionScoreDebug = "nutrition_score_debug" + case nutritionScoreWarningNoFiber = "nutrition_score_warning_no_fiber" + case nutritionGradesTags = "nutrition_grades_tags" + case origins + case originsDebugTags = "origins_debug_tags" + case originsTags = "origins_tags" + case otherInformation = "other_information" + case otherNutritionalSubstancesTags = + "other_nutritional_substances_tags" + case packaging + case packagingDebugTags = "packaging_debug_tags" + case packagingTags = "packaging_tags" + case photographersTags = "photographers_tags" + case pnnsGroups1 = "pnns_groups_1" + case pnnsGroups2 = "pnns_groups_2" + case pnnsGroups1Tags = "pnns_groups_1_tags" + case pnnsGroups2Tags = "pnns_groups_2_tags" + case popularityKey = "popularity_key" + case producerVersionId = "producer_version_id" + case productName = "product_name" + case productQuantity = "product_quantity" + case purchasePlaces = "purchase_places" + case purchasePlacesDebugTags = "purchase_places_debug_tags" + case purchasePlacesTags = "purchase_places_tags" + case qualityTags = "quality_tags" + case quantity + case quantityDebugTags = "quantity_debug_tags" + case recyclingInstructionsToDiscard = + "recycling_instructions_to_discard" + case rev + case servingQuantity = "serving_quantity" + case servingSize = "serving_size" + case servingSizeDebugTags = "serving_size_debug_tags" + case sortkey + case states + case statesHierarchy = "states_hierarchy" + case statesTags = "states_tags" + case stores + case storesDebugTags = "stores_debug_tags" + case storesTags = "stores_tags" + case traces + case tracesFromIngredients = "traces_from_ingredients" + case tracesHierarchy = "traces_hierarchy" + case tracesDebugTags = "traces_debug_tags" + case tracesFromUser = "traces_from_user" + case tracesLc = "traces_lc" + case tracesTags = "traces_tags" + case unknownIngredientsN = "unknown_ingredients_n" + case unknownNutrientsTags = "unknown_nutrients_tags" + case updateKey = "update_key" + case vitaminsPrevTags = "vitamins_prev_tags" + case vitaminsTags = "vitamins_tags" + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + ingredients = try container.decodeIfPresent( + [Ingredient].self, forKey: .ingredients) + languagesCodes = try container.decodeIfPresent( + LanguagesCodes.self, forKey: .languagesCodes) + nutrientLevels = try container.decodeIfPresent( + NutrientLevels.self, forKey: .nutrientLevels) + nutriments = + try container.decodeIfPresent(Nutriments.self, forKey: .nutriments) + ?? Nutriments() + selectedImages = try container.decodeIfPresent( + SelectedImages.self, forKey: .selectedImages) + sources = try container.decodeIfPresent([Source].self, forKey: .sources) + additivesN = try container.decodeIfPresent( + Float.self, forKey: .additivesN) + additivesOldN = try container.decodeIfPresent( + Float.self, forKey: .additivesOldN) + additivesOriginalTags = try container.decodeIfPresent( + [String].self, forKey: .additivesOriginalTags) + additivesOldTags = try container.decodeIfPresent( + [String].self, forKey: .additivesOldTags) + additivesPrevOriginalTags = try container.decodeIfPresent( + [String].self, forKey: .additivesPrevOriginalTags) + additivesDebugTags = try container.decodeIfPresent( + [String].self, forKey: .additivesDebugTags) + additivesTags = try container.decodeIfPresent( + [String].self, forKey: .additivesTags) + allergens = try container.decodeIfPresent( + String.self, forKey: .allergens) + allergensFromIngredients = try container.decodeIfPresent( + String.self, forKey: .allergensFromIngredients) + allergensFromUser = try container.decodeIfPresent( + String.self, forKey: .allergensFromUser) + allergensHierarchy = try container.decodeIfPresent( + [String].self, forKey: .allergensHierarchy) + allergensLc = try container.decodeIfPresent( + String.self, forKey: .allergensLc) + allergensTags = try container.decodeIfPresent( + [String].self, forKey: .allergensTags) + aminoAcidsPrevTags = try container.decodeIfPresent( + [String].self, forKey: .aminoAcidsPrevTags) + aminoAcidsTags = try container.decodeIfPresent( + [String].self, forKey: .aminoAcidsTags) + brands = try container.decodeIfPresent(String.self, forKey: .brands) + brandsDebugTags = try container.decodeIfPresent( + [String].self, forKey: .brandsDebugTags) + brandsTags = try container.decodeIfPresent( + [String].self, forKey: .brandsTags) + carbonFootprintPercentOfKnownIngredients = + try container.decodeIfPresent( + Float.self, forKey: .carbonFootprintPercentOfKnownIngredients) + carbonFootprintFromKnownIngredientsDebug = + try container.decodeIfPresent( + String.self, forKey: .carbonFootprintFromKnownIngredientsDebug) + categories = try container.decodeIfPresent( + String.self, forKey: .categories) + categoriesHierarchy = try container.decodeIfPresent( + [String].self, forKey: .categoriesHierarchy) + categoriesLc = try container.decodeIfPresent( + String.self, forKey: .categoriesLc) + categoriesPropertiesTags = try container.decodeIfPresent( + [String].self, forKey: .categoriesPropertiesTags) + categoriesTags = try container.decodeIfPresent( + [String].self, forKey: .categoriesTags) + checkersTags = try container.decodeIfPresent( + [String].self, forKey: .checkersTags) + citiesTags = try container.decodeIfPresent( + [String].self, forKey: .citiesTags) + code = try container.decodeIfPresent(String.self, forKey: .code) + codesTags = try container.decodeIfPresent( + [String].self, forKey: .codesTags) + comparedToCategory = try container.decodeIfPresent( + String.self, forKey: .comparedToCategory) + complete = try container.decodeIfPresent(Float.self, forKey: .complete) + completedT = try container.decodeIfPresent( + Float.self, forKey: .completedT) + completeness = try container.decodeIfPresent( + Double.self, forKey: .completeness) + conservationConditions = try container.decodeIfPresent( + String.self, forKey: .conservationConditions) + countries = try container.decodeIfPresent( + String.self, forKey: .countries) + countriesHierarchy = try container.decodeIfPresent( + [String].self, forKey: .countriesHierarchy) + countriesLc = try container.decodeIfPresent( + String.self, forKey: .countriesLc) + countriesDebugTags = try container.decodeIfPresent( + [String].self, forKey: .countriesDebugTags) + countriesTags = try container.decodeIfPresent( + [String].self, forKey: .countriesTags) + correctorsTags = try container.decodeIfPresent( + [String].self, forKey: .correctorsTags) + createdT = try container.decodeIfPresent(Float.self, forKey: .createdT) + creator = try container.decodeIfPresent(String.self, forKey: .creator) + dataQualityBugsTags = try container.decodeIfPresent( + [String].self, forKey: .dataQualityBugsTags) + dataQualityErrorsTags = try container.decodeIfPresent( + [String].self, forKey: .dataQualityErrorsTags) + dataQualityInfoTags = try container.decodeIfPresent( + [String].self, forKey: .dataQualityInfoTags) + dataQualityTags = try container.decodeIfPresent( + [String].self, forKey: .dataQualityTags) + dataQualityWarningsTags = try container.decodeIfPresent( + [String].self, forKey: .dataQualityWarningsTags) + dataSources = try container.decodeIfPresent( + String.self, forKey: .dataSources) + dataSourcesTags = try container.decodeIfPresent( + [String].self, forKey: .dataSourcesTags) + debugParamSortedLangs = try container.decodeIfPresent( + [String].self, forKey: .debugParamSortedLangs) + editorsTags = try container.decodeIfPresent( + [String].self, forKey: .editorsTags) + embCodes = try container.decodeIfPresent(String.self, forKey: .embCodes) + embCodesDebugTags = try container.decodeIfPresent( + [String].self, forKey: .embCodesDebugTags) + embCodesOrig = try container.decodeIfPresent( + String.self, forKey: .embCodesOrig) + embCodesTags = try container.decodeIfPresent( + [String].self, forKey: .embCodesTags) + entryDatesTags = try container.decodeIfPresent( + [String].self, forKey: .entryDatesTags) + expirationDate = try container.decodeIfPresent( + String.self, forKey: .expirationDate) + expirationDateDebugTags = try container.decodeIfPresent( + [String].self, forKey: .expirationDateDebugTags) + fruitsVegetablesNuts100GEstimate = try container.decodeIfPresent( + Float.self, forKey: .fruitsVegetablesNuts100GEstimate) + genericName = try container.decodeIfPresent( + String.self, forKey: .genericName) + id = try container.decodeIfPresent(String.self, forKey: .id) + imageFrontSmallUrl = try container.decodeIfPresent( + String.self, forKey: .imageFrontSmallUrl) + imageFrontThumbUrl = try container.decodeIfPresent( + String.self, forKey: .imageFrontThumbUrl) + imageFrontUrl = try container.decodeIfPresent( + String.self, forKey: .imageFrontUrl) + imageIngredientsUrl = try container.decodeIfPresent( + String.self, forKey: .imageIngredientsUrl) + imageIngredientsSmallUrl = try container.decodeIfPresent( + String.self, forKey: .imageIngredientsSmallUrl) + imageIngredientsThumbUrl = try container.decodeIfPresent( + String.self, forKey: .imageIngredientsThumbUrl) + imageNutritionSmallUrl = try container.decodeIfPresent( + String.self, forKey: .imageNutritionSmallUrl) + imageNutritionThumbUrl = try container.decodeIfPresent( + String.self, forKey: .imageNutritionThumbUrl) + imageNutritionUrl = try container.decodeIfPresent( + String.self, forKey: .imageNutritionUrl) + imageSmallUrl = try container.decodeIfPresent( + String.self, forKey: .imageSmallUrl) + imageThumbUrl = try container.decodeIfPresent( + String.self, forKey: .imageThumbUrl) + imageUrl = try container.decodeIfPresent(String.self, forKey: .imageUrl) + informersTags = try container.decodeIfPresent( + [String].self, forKey: .informersTags) + ingredientsAnalysisTags = try container.decodeIfPresent( + [String].self, forKey: .ingredientsAnalysisTags) + ingredientsDebug = try container.decodeIfPresent( + [String?].self, forKey: .ingredientsDebug) + ingredientsFromOrThatMayBeFromPalmOilN = try container.decodeIfPresent( + Float.self, forKey: .ingredientsFromOrThatMayBeFromPalmOilN) + ingredientsFromPalmOilTags = try container.decodeIfPresent( + [String].self, forKey: .ingredientsFromPalmOilTags) + ingredientsFromPalmOilN = try container.decodeIfPresent( + Float.self, forKey: .ingredientsFromPalmOilN) + ingredientsHierarchy = try container.decodeIfPresent( + [String].self, forKey: .ingredientsHierarchy) + ingredientsIdsDebug = try container.decodeIfPresent( + [String].self, forKey: .ingredientsIdsDebug) + if container.contains(.ingredientsN) { + if try container.decodeNil(forKey: .ingredientsN) { + ingredientsN = nil + } else { + if let intValue = try? container.decode( + Float.self, forKey: .ingredientsN) + { + ingredientsN = intValue + } else if let stringValue = try? container.decode( + String.self, forKey: .ingredientsN) + { + ingredientsN = Float(stringValue) + } else { + // If decoding as both Float and String fails, handle the error accordingly + throw DecodingError.dataCorruptedError( + forKey: .ingredientsN, + in: container, + debugDescription: "Unable to decode ingredientsN" + ) + } + + } + } else { + ingredientsN = nil + } + ingredientsNTags = try container.decodeIfPresent( + [String].self, forKey: .ingredientsNTags) + ingredientsOriginalTags = try container.decodeIfPresent( + [String].self, forKey: .ingredientsOriginalTags) + ingredientsTags = try container.decodeIfPresent( + [String].self, forKey: .ingredientsTags) + ingredientsText = try container.decodeIfPresent( + String.self, forKey: .ingredientsText) + ingredientsTextDebug = try container.decodeIfPresent( + String.self, forKey: .ingredientsTextDebug) + ingredientsTextWithAllergens = try container.decodeIfPresent( + String.self, forKey: .ingredientsTextWithAllergens) + ingredientsThatMayBeFromPalmOilN = try container.decodeIfPresent( + Float.self, forKey: .ingredientsThatMayBeFromPalmOilN) + ingredientsThatMayBeFromPalmOilTags = try container.decodeIfPresent( + [String].self, forKey: .ingredientsThatMayBeFromPalmOilTags) + interfaceVersionCreated = try container.decodeIfPresent( + String.self, forKey: .interfaceVersionCreated) + interfaceVersionModified = try container.decodeIfPresent( + String.self, forKey: .interfaceVersionModified) + keywords = try container.decodeIfPresent( + [String].self, forKey: .keywords) + knownIngredientsN = try container.decodeIfPresent( + Float.self, forKey: .knownIngredientsN) + labels = try container.decodeIfPresent(String.self, forKey: .labels) + labelsHierarchy = try container.decodeIfPresent( + [String].self, forKey: .labelsHierarchy) + labelsLc = try container.decodeIfPresent(String.self, forKey: .labelsLc) + labelsPrevHierarchy = try container.decodeIfPresent( + [String].self, forKey: .labelsPrevHierarchy) + labelsPrevTags = try container.decodeIfPresent( + [String].self, forKey: .labelsPrevTags) + labelsTags = try container.decodeIfPresent( + [String].self, forKey: .labelsTags) + labelsDebugTags = try container.decodeIfPresent( + [String].self, forKey: .labelsDebugTags) + lang = try container.decodeIfPresent(String.self, forKey: .lang) + langDebugTags = try container.decodeIfPresent( + [String].self, forKey: .langDebugTags) + languagesHierarchy = try container.decodeIfPresent( + [String].self, forKey: .languagesHierarchy) + languagesTags = try container.decodeIfPresent( + [String].self, forKey: .languagesTags) + lastEditDatesTags = try container.decodeIfPresent( + [String].self, forKey: .lastEditDatesTags) + lastEditor = try container.decodeIfPresent( + String.self, forKey: .lastEditor) + lastImageDatesTags = try container.decodeIfPresent( + [String].self, forKey: .lastImageDatesTags) + lastImageT = try container.decodeIfPresent( + Float.self, forKey: .lastImageT) + lastModifiedBy = try container.decodeIfPresent( + String.self, forKey: .lastModifiedBy) + if container.contains(.lastModifiedT) { + if try container.decodeNil(forKey: .maxImgid) { + lastModifiedT = nil + } else { + if let floatValue = try? container.decode( + Float.self, forKey: .lastModifiedT) + { + lastModifiedT = floatValue + } else if let stringValue = try? container.decode( + String.self, forKey: .lastModifiedT) + { + lastModifiedT = Float(stringValue) + } else { + // If decoding as both Float and String fails, handle the error accordingly + throw DecodingError.dataCorruptedError( + forKey: .lastModifiedT, + in: container, + debugDescription: "Unable to decode lastModifiedT" + ) + } + + } + } else { + lastModifiedT = nil + } + lc = try container.decodeIfPresent(String.self, forKey: .lc) + link = try container.decodeIfPresent(String.self, forKey: .link) + linkDebugTags = try container.decodeIfPresent( + [String].self, forKey: .linkDebugTags) + manufacturingPlaces = try container.decodeIfPresent( + String.self, forKey: .manufacturingPlaces) + manufacturingPlacesDebugTags = try container.decodeIfPresent( + [String].self, forKey: .manufacturingPlacesDebugTags) + manufacturingPlacesTags = try container.decodeIfPresent( + [String].self, forKey: .manufacturingPlacesTags) + if container.contains(.maxImgid) { + if try container.decodeNil(forKey: .maxImgid) { + maxImgid = nil + } else { + if let floatValue = try? container.decode( + Float.self, forKey: .maxImgid) + { + maxImgid = "\(floatValue)" + } else if let stringValue = try? container.decode( + String.self, forKey: .maxImgid) + { + maxImgid = stringValue + } else { + // If decoding as both Float and String fails, handle the error accordingly + throw DecodingError.dataCorruptedError( + forKey: .maxImgid, + in: container, + debugDescription: "Unable to decode maxImgid" + ) + } + + } + } else { + maxImgid = nil + } + mineralsPrevTags = try container.decodeIfPresent( + [String].self, forKey: .mineralsPrevTags) + mineralsTags = try container.decodeIfPresent( + [String].self, forKey: .mineralsTags) + miscTags = try container.decodeIfPresent( + [String].self, forKey: .miscTags) + netWeightUnit = try container.decodeIfPresent( + String.self, forKey: .netWeightUnit) + netWeightValue = try container.decodeIfPresent( + String.self, forKey: .netWeightValue) + nutritionDataPer = try container.decodeIfPresent( + String.self, forKey: .nutritionDataPer) + nutritionScoreWarningNoFruitsVegetablesNuts = + try container.decodeIfPresent( + Float.self, forKey: .nutritionScoreWarningNoFruitsVegetablesNuts + ) + noNutritionData = try container.decodeIfPresent( + String.self, forKey: .noNutritionData) + novaGroup = try container.decodeIfPresent( + Float.self, forKey: .novaGroup) + novaGroups = try container.decodeIfPresent( + String.self, forKey: .novaGroups) + novaGroupDebug = try container.decodeIfPresent( + String.self, forKey: .novaGroupDebug) + novaGroupTags = try container.decodeIfPresent( + [String].self, forKey: .novaGroupTags) + novaGroupsTags = try container.decodeIfPresent( + [String].self, forKey: .novaGroupsTags) + nucleotidesTags = try container.decodeIfPresent( + [String].self, forKey: .nucleotidesTags) + nutrientLevelsTags = try container.decodeIfPresent( + [String].self, forKey: .nutrientLevelsTags) + nutritionData = try container.decodeIfPresent( + String.self, forKey: .nutritionData) + nutriscoreGrade = try container.decodeIfPresent( + String.self, forKey: .nutriscoreGrade) + nutritionDataPerDebugTags = try container.decodeIfPresent( + [String].self, forKey: .nutritionDataPerDebugTags) + nutritionDataPrepared = try container.decodeIfPresent( + String.self, forKey: .nutritionDataPrepared) + nutritionDataPreparedPer = try container.decodeIfPresent( + String.self, forKey: .nutritionDataPreparedPer) + nutritionGrades = try container.decodeIfPresent( + String.self, forKey: .nutritionGrades) + nutritionScoreBeverage = try container.decodeIfPresent( + Float.self, forKey: .nutritionScoreBeverage) + nutritionScoreDebug = try container.decodeIfPresent( + String.self, forKey: .nutritionScoreDebug) + nutritionScoreWarningNoFiber = try container.decodeIfPresent( + Float.self, forKey: .nutritionScoreWarningNoFiber) + nutritionGradesTags = try container.decodeIfPresent( + [String].self, forKey: .nutritionGradesTags) + origins = try container.decodeIfPresent(String.self, forKey: .origins) + originsDebugTags = try container.decodeIfPresent( + [String].self, forKey: .originsDebugTags) + originsTags = try container.decodeIfPresent( + [String].self, forKey: .originsTags) + otherInformation = try container.decodeIfPresent( + String.self, forKey: .otherInformation) + otherNutritionalSubstancesTags = try container.decodeIfPresent( + [String].self, forKey: .otherNutritionalSubstancesTags) + packaging = try container.decodeIfPresent( + String.self, forKey: .packaging) + packagingDebugTags = try container.decodeIfPresent( + [String].self, forKey: .packagingDebugTags) + packagingTags = try container.decodeIfPresent( + [String].self, forKey: .packagingTags) + photographersTags = try container.decodeIfPresent( + [String].self, forKey: .photographersTags) + pnnsGroups1 = try container.decodeIfPresent( + String.self, forKey: .pnnsGroups1) + pnnsGroups2 = try container.decodeIfPresent( + String.self, forKey: .pnnsGroups2) + pnnsGroups1Tags = try container.decodeIfPresent( + [String].self, forKey: .pnnsGroups1Tags) + pnnsGroups2Tags = try container.decodeIfPresent( + [String].self, forKey: .pnnsGroups2Tags) + popularityKey = try container.decodeIfPresent( + Float.self, forKey: .popularityKey) + producerVersionId = try container.decodeIfPresent( + String.self, forKey: .producerVersionId) + productName = try container.decodeIfPresent( + String.self, forKey: .productName) + purchasePlaces = try container.decodeIfPresent( + String.self, forKey: .purchasePlaces) + purchasePlacesDebugTags = try container.decodeIfPresent( + [String].self, forKey: .purchasePlacesDebugTags) + purchasePlacesTags = try container.decodeIfPresent( + [String].self, forKey: .purchasePlacesTags) + qualityTags = try container.decodeIfPresent( + [String].self, forKey: .qualityTags) + quantity = try container.decodeIfPresent(String.self, forKey: .quantity) + quantityDebugTags = try container.decodeIfPresent( + [String].self, forKey: .quantityDebugTags) + recyclingInstructionsToDiscard = try container.decodeIfPresent( + String.self, forKey: .recyclingInstructionsToDiscard) + rev = try container.decodeIfPresent(Float.self, forKey: .rev) + if container.contains(.servingQuantity) { + if try container.decodeNil(forKey: .servingQuantity) { + servingQuantity = nil + } else { + if let floatValue = try? container.decode( + Float.self, forKey: .servingQuantity) + { + servingQuantity = "\(floatValue)" + } else if let stringValue = try? container.decode( + String.self, forKey: .servingQuantity) + { + servingQuantity = stringValue + } else { + // If decoding as both Float and String fails, handle the error accordingly + throw DecodingError.dataCorruptedError( + forKey: .servingQuantity, + in: container, + debugDescription: "Unable to decode servingQuantity" + ) + } + + } + } else { + servingQuantity = nil + } + servingSize = try container.decodeIfPresent( + String.self, forKey: .servingSize) + servingSizeDebugTags = try container.decodeIfPresent( + [String].self, forKey: .servingSizeDebugTags) + sortkey = try container.decodeIfPresent(Float.self, forKey: .sortkey) + states = try container.decodeIfPresent(String.self, forKey: .states) + statesHierarchy = try container.decodeIfPresent( + [String].self, forKey: .statesHierarchy) + statesTags = try container.decodeIfPresent( + [String].self, forKey: .statesTags) + stores = try container.decodeIfPresent(String.self, forKey: .stores) + storesDebugTags = try container.decodeIfPresent( + [String].self, forKey: .storesDebugTags) + storesTags = try container.decodeIfPresent( + [String].self, forKey: .storesTags) + traces = try container.decodeIfPresent(String.self, forKey: .traces) + tracesFromIngredients = try container.decodeIfPresent( + String.self, forKey: .tracesFromIngredients) + tracesHierarchy = try container.decodeIfPresent( + [String].self, forKey: .tracesHierarchy) + tracesDebugTags = try container.decodeIfPresent( + [String].self, forKey: .tracesDebugTags) + tracesFromUser = try container.decodeIfPresent( + String.self, forKey: .tracesFromUser) + tracesLc = try container.decodeIfPresent(String.self, forKey: .tracesLc) + tracesTags = try container.decodeIfPresent( + [String].self, forKey: .tracesTags) + if container.contains(.unknownIngredientsN) { + if try container.decodeNil(forKey: .unknownIngredientsN) { + unknownIngredientsN = nil + } else { + if let intValue = try? container.decode( + Float.self, forKey: .unknownIngredientsN) + { + unknownIngredientsN = intValue + } else if let stringValue = try? container.decode( + String.self, forKey: .unknownIngredientsN) + { + unknownIngredientsN = Float(stringValue) + } else { + // If decoding as both Float and String fails, handle the error accordingly + throw DecodingError.dataCorruptedError( + forKey: .unknownIngredientsN, + in: container, + debugDescription: "Unable to decode unknownIngredientsN" + ) + } + + } + } else { + unknownIngredientsN = nil + } + unknownNutrientsTags = try container.decodeIfPresent( + [String].self, forKey: .unknownNutrientsTags) + updateKey = try container.decodeIfPresent( + String.self, forKey: .updateKey) + vitaminsPrevTags = try container.decodeIfPresent( + [String].self, forKey: .vitaminsPrevTags) + vitaminsTags = try container.decodeIfPresent( + [String].self, forKey: .vitaminsTags) + + // Check for null value + if container.contains(.productQuantity) { + if try container.decodeNil(forKey: .productQuantity) { + productQuantity = nil + } else { + // Try to decode as Float + if let floatValue = try? container.decode( + Float.self, forKey: .productQuantity) + { + productQuantity = floatValue + } else if let stringValue = try? container.decode( + String.self, forKey: .productQuantity) + { + // If decoding as Float fails, try to decode as String + productQuantity = Float(stringValue) + } else { + // If decoding as both Float and String fails, handle the error accordingly + throw DecodingError.dataCorruptedError( + forKey: .productQuantity, + in: container, + debugDescription: "Unable to decode productQuantity" + ) + } + } + } else { + productQuantity = nil + } + + // ... (initialize other properties) + } +} diff --git a/Sources/OpenFoodFacts/Schemas/ProductResponse.swift b/Sources/OpenFoodFacts/Schemas/ProductResponse.swift new file mode 100644 index 0000000..5c97bd0 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/ProductResponse.swift @@ -0,0 +1,13 @@ +public struct ProductResponse: Codable, ObjectDebugger { + public var product: Product? + public var code: String? + public var status: Int? // or Bool, depending on your needs + public var statusVerbose: String? + + private enum CodingKeys: String, CodingKey { + case product + case code + case status + case statusVerbose = "status_verbose" + } +} diff --git a/Sources/OpenFoodFacts/Schemas/SearchNutriment.swift b/Sources/OpenFoodFacts/Schemas/SearchNutriment.swift new file mode 100644 index 0000000..60e5c67 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/SearchNutriment.swift @@ -0,0 +1,98 @@ +public enum SearchNutriment: String, Codable { + case energy // Energy + case energyFromFat = "energy-from-fat" // Energy from fat + case fat // Fat + case saturatedFat = "saturated-fat" // Saturated fat + case butyricAcid = "butyric-acid" // Butyric acid (4:0) + case caproicAcid = "caproic-acid" // Caproic acid (6:0) + case caprylicAcid = "caprylic-acid" // Caprylic acid (8:0) + case capricAcid = "capric-acid" // Capric acid (10:0) + case lauricAcid = "lauric-acid" // Lauric acid (12:0) + case myristicAcid = "myristic-acid" // Myristic acid (14:0) + case palmiticAcid = "palmitic-acid" // Palmitic acid (16:0) + case stearicAcid = "stearic-acid" // Stearic acid (18:0) + case arachidicAcid = "arachidic-acid" // Arachidic acid (20:0) + case behenicAcid = "behenic-acid" // Behenic acid (22:0) + case lignocericAcid = "lignoceric-acid" // Lignoceric acid (24:0) + case ceroticAcid = "cerotic-acid" // Cerotic acid (26:0) + case montanicAcid = "montanic-acid" // Montanic acid (28:0) + case melissicAcid = "melissic-acid" // Melissic acid (30:0) + case monounsaturatedFat = "monounsaturated-fat" // Monounsaturated fat + case polyunsaturatedFat = "polyunsaturated-fat" // Polyunsaturated fat + case omega3Fat = "omega-3-fat" // Omega 3 fatty acids + case alphaLinolenicAcid = "alpha-linolenic-acid" // Alpha-linolenic acid / ALA (18:3 n-3) + case eicosapentaenoicAcid = "eicosapentaenoic-acid" // Eicosapentaenoic acid / EPA (20:5 n-3) + case docosahexaenoicAcid = "docosahexaenoic-acid" // Docosahexaenoic acid / DHA (22:6 n-3) + case omega6Fat = "omega-6-fat" // Omega 6 fatty acids + case linoleicAcid = "linoleic-acid" // Linoleic acid / LA (18:2 n-6) + case arachidonicAcid = "arachidonic-acid" // Arachidonic acid / AA / ARA (20:4 n-6) + case gammaLinolenicAcid = "gamma-linolenic-acid" // Gamma-linolenic acid / GLA (18:3 n-6) + case dihomoGammaLinolenicAcid = "dihomo-gamma-linolenic-acid" // Dihomo-gamma-linolenic acid / DGLA (20:3 n-6) + case omega9Fat = "omega-9-fat" // Omega 9 fatty acids + case oleicAcid = "oleic-acid" // Oleic acid (18:1 n-9) + case elaidicAcid = "elaidic-acid" // Elaidic acid (18:1 n-9) + case gondoicAcid = "gondoic-acid" // Gondoic acid (20:1 n-9) + case meadAcid = "mead-acid" // Mead acid (20:3 n-9) + case erucicAcid = "erucic-acid" // Erucic acid (22:1 n-9) + case nervonicAcid = "nervonic-acid" // Nervonic acid (24:1 n-9) + case transFat = "trans-fat" // Trans fat + case cholesterol // Cholesterol + case carbohydrates // Carbohydrate + case sugars // Sugars + case sucrose // Sucrose + case glucose // Glucose + case fructose // Fructose + case lactose // Lactose + case maltose // Maltose + case maltodextrins // Maltodextrins + case starch // Starch + case polyols // Sugar alcohols (Polyols) + case fiber // Dietary fiber + case proteins // Proteins + case casein // Casein + case serumProteins = "serum-proteins" // Serum proteins + case nucleotides // Nucleotides + case salt // Salt + case sodium // Sodium + case alcohol // Alcohol + case vitaminA = "vitamin-a" // Vitamin A + case betaCarotene = "beta-carotene" // Beta carotene + case vitaminD = "vitamin-d" // Vitamin D + case vitaminE = "vitamin-e" // Vitamin E + case vitaminK = "vitamin-k" // Vitamin K + case vitaminC = "vitamin-c" // Vitamin C (ascorbic acid) + case vitaminB1 = "vitamin-b1" // Vitamin B1 (Thiamin) + case vitaminB2 = "vitamin-b2" // Vitamin B2 (Riboflavin) + case vitaminPP = "vitamin-pp" // Vitamin B3 / Vitamin PP (Niacin) + case vitaminB6 = "vitamin-b6" // Vitamin B6 (Pyridoxin) + case vitaminB9 = "vitamin-b9" // Vitamin B9 (Folic acid / Folates) + case vitaminB12 = "vitamin-b12" // Vitamin B12 (Cobalamin) + case biotin // Biotin + case pantothenicAcid = "pantothenic-acid" // Pantothenic acid / Pantothenate (Vitamin B5) + case silica // Silica + case bicarbonate // Bicarbonate + case potassium // Potassium + case chloride // Chloride + case calcium // Calcium + case phosphorus // Phosphorus + case iron // Iron + case magnesium // Magnesium + case zinc // Zinc + case copper // Copper + case manganese // Manganese + case fluoride // Fluoride + case selenium // Selenium + case chromium // Chromium + case molybdenum // Molybdenum + case iodine // Iodine + case caffeine // Caffeine + case taurine // Taurine + case pH // pH + case fruitsVegetablesNuts = "fruits-vegetables-nuts" // Fruits, vegetables, and nuts (minimum) + case collagenMeatProteinRatio = "collagen-meat-protein-ratio" // Collagen/Meat protein ratio (maximum) + case cocoa // Cocoa (minimum) + case chlorophyll = "chlorophyll" // Chlorophyll + case carbonFootprint = "carbon-footprint" // Carbon footprint / CO2 emissions + case nutritionScoreFR = "nutrition-score-fr" // Experimental nutrition score + case nutritionScoreUK = "nutrition-score-uk" // Nutrition score - UK +} diff --git a/Sources/OpenFoodFacts/Schemas/SearchResponse.swift b/Sources/OpenFoodFacts/Schemas/SearchResponse.swift new file mode 100644 index 0000000..0933fc2 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/SearchResponse.swift @@ -0,0 +1,14 @@ +public struct SearchResponse: Codable, ObjectDebugger { + public var count: Int + public var page: Int + public var pageCount: Int + public var pageSize: Int + public var products: [Product]? + public var skip: Int + + private enum CodingKeys: String, CodingKey { + case count, page, products, skip + case pageCount = "page_count" + case pageSize = "page_size" + } +} diff --git a/Sources/OpenFoodFacts/Schemas/SearchTag.swift b/Sources/OpenFoodFacts/Schemas/SearchTag.swift new file mode 100644 index 0000000..28a0e21 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/SearchTag.swift @@ -0,0 +1,17 @@ +public enum SearchTag: String, Codable { + case brands // Brands + case categories // Categories + case packaging // Packaging + case labels // Labels + case origins // Origins of ingredients + case manufacturingPlaces = "manufacturing_places" // Manufacturing or processing places + case embCodes = "emb_codes" // Packager codes + case purchasePlaces = "purchase_places" // Purchase places + case stores // Stores + case countries // Countries + case additives // Additives + case allergens // Allergens + case traces // Traces + case nutritionGrades = "nutrition_grades" // Nutrition grades + case states // States +} diff --git a/Sources/OpenFoodFacts/Schemas/SelectedImage.swift b/Sources/OpenFoodFacts/Schemas/SelectedImage.swift new file mode 100644 index 0000000..d2f11e4 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/SelectedImage.swift @@ -0,0 +1,14 @@ +public class SelectedImage: Codable, ObjectDebugger { + public var display: SelectedImageItem? + public var small: SelectedImageItem? + public var thumb: SelectedImageItem? + + public init( + display: SelectedImageItem?, small: SelectedImageItem?, + thumb: SelectedImageItem? + ) { + self.display = display + self.small = small + self.thumb = thumb + } +} diff --git a/Sources/OpenFoodFacts/Schemas/SelectedImageItem.swift b/Sources/OpenFoodFacts/Schemas/SelectedImageItem.swift new file mode 100644 index 0000000..a6b5d72 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/SelectedImageItem.swift @@ -0,0 +1,15 @@ +public struct SelectedImageItem: Codable, ObjectDebugger { + public var en: String? + public var fr: String? + public var pl: String? + + public var url: String { + [en, fr, pl].compactMap { $0 }.first ?? "" + } + + public init(en: String?, fr: String?, pl: String?) { + self.en = en + self.fr = fr + self.pl = pl + } +} diff --git a/Sources/OpenFoodFacts/Schemas/SelectedImages.swift b/Sources/OpenFoodFacts/Schemas/SelectedImages.swift new file mode 100644 index 0000000..f93f5e7 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/SelectedImages.swift @@ -0,0 +1,14 @@ +public struct SelectedImages: Codable, ObjectDebugger { + public var front: SelectedImage? + public var ingredients: SelectedImage? + public var nutrition: SelectedImage? + + public init( + front: SelectedImage?, ingredients: SelectedImage?, + nutrition: SelectedImage? + ) { + self.front = front + self.ingredients = ingredients + self.nutrition = nutrition + } +} diff --git a/Sources/OpenFoodFacts/Schemas/Source.swift b/Sources/OpenFoodFacts/Schemas/Source.swift new file mode 100644 index 0000000..a605989 --- /dev/null +++ b/Sources/OpenFoodFacts/Schemas/Source.swift @@ -0,0 +1,19 @@ +public struct Source: Codable, ObjectDebugger { + public let fields: [String] = [] + public let id: String? = nil + public let images: [String] = [] + public let importT: Int = 0 + public let manufacturer: String? = nil + public let name: String? = nil + public let url: String? = nil + + private enum CodingKeys: String, CodingKey { + case fields + case id + case images + case importT = "import_t" + case manufacturer + case name + case url + } +} diff --git a/Sources/OpenFoodFacts/types/Images.swift b/Sources/OpenFoodFacts/types/Images.swift deleted file mode 100644 index 66ea40b..0000000 --- a/Sources/OpenFoodFacts/types/Images.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -public struct Images: Codable { - public var otherData: [String: Data] = [:] - - mutating func setDetail(key: String, value: T) throws { - let encodedValue = try JSONEncoder().encode(value) - otherData[key] = encodedValue - } - - func getDetail(key: String, type: T.Type) throws -> T? { - guard let data = otherData[key] else { return nil } - return try JSONDecoder().decode(type, from: data) - } -} diff --git a/Sources/OpenFoodFacts/types/Ingredient.swift b/Sources/OpenFoodFacts/types/Ingredient.swift deleted file mode 100644 index db2a9fb..0000000 --- a/Sources/OpenFoodFacts/types/Ingredient.swift +++ /dev/null @@ -1,21 +0,0 @@ -public struct Ingredient: Codable, ObjectDebugger { - public var fromPalmOil: String? = nil - public var id: String? = nil - public var origin: String? = nil - public var percent: Float? = nil - public var rank: Float? = 0 - public var text: String? = nil - public var vegan: String? = nil - public var vegetarian: String? = nil - - private enum CodingKeys: String, CodingKey { - case fromPalmOil = "from_palm_oil" - case id - case origin - case percent - case rank - case text - case vegan - case vegetarian - } -} diff --git a/Sources/OpenFoodFacts/types/LanguagesCodes.swift b/Sources/OpenFoodFacts/types/LanguagesCodes.swift deleted file mode 100644 index 1658a84..0000000 --- a/Sources/OpenFoodFacts/types/LanguagesCodes.swift +++ /dev/null @@ -1,6 +0,0 @@ - -public struct LanguagesCodes: Codable, ObjectDebugger { - public var en: Float? = nil - public var fr: Float? = nil - public var pl: Float? = nil -} diff --git a/Sources/OpenFoodFacts/types/NutrientLevels.swift b/Sources/OpenFoodFacts/types/NutrientLevels.swift deleted file mode 100644 index c8aeec7..0000000 --- a/Sources/OpenFoodFacts/types/NutrientLevels.swift +++ /dev/null @@ -1,13 +0,0 @@ -public struct NutrientLevels: Codable, ObjectDebugger { - public var fat: String? = nil - public var salt: String? = nil - public var saturatedFat: String? = nil - public var sugars: String? = nil - - private enum CodingKeys: String, CodingKey { - case fat - case salt - case saturatedFat = "saturated-fat" - case sugars - } -} diff --git a/Sources/OpenFoodFacts/types/Nutriments.swift b/Sources/OpenFoodFacts/types/Nutriments.swift deleted file mode 100644 index 2519c16..0000000 --- a/Sources/OpenFoodFacts/types/Nutriments.swift +++ /dev/null @@ -1,237 +0,0 @@ -public struct Nutriments: Codable, ObjectDebugger { - public var calcium: Float? - public var calciumValue: Float? - public var calcium100G: Float? - public var calciumServing: Float? - public var calciumUnit: String? - - public var carbohydrates: Float? - public var carbohydratesValue: Float? - public var carbohydrates100G: Float? - public var carbohydratesServing: Float? - public var carbohydratesUnit: String? - - public var carbonFootprintFromKnownIngredientsProduct: Float? - public var carbonFootprintFromKnownIngredients100G: Float? - public var carbonFootprintFromKnownIngredientsServing: Float? - - public var cholesterol: Float? - public var cholesterolValue: Float? - public var cholesterol100G: Float? - public var cholesterolServing: Float? - public var cholesterolUnit: String? - - public var energy: Float? - public var energyKcal: Float? - public var energyKj: Float? - public var energyValue: Float? - public var energyKcalValue: Float? - public var energyKjValue: Float? - public var energy100G: Float? - public var energyKcal100G: Float? - public var energyKj100G: Float? - public var energyServing: Float? - public var energyKcalServing: Double? - public var energyKjServing: Float? - public var energyUnit: String? - public var energyKcalUnit: String? - public var energyKjUnit: String? - - public var fat: Float? - public var fatValue: Float? - public var fat100G: Float? - public var fatServing: Float? - public var fatUnit: String? - - public var fiber: Float? - public var fiberValue: Float? - public var fiber100G: Float? - public var fiberServing: Float? - public var fiberUnit: String? - - public var fruitsVegetablesNutsEstimateFromIngredients100G: Float? - - public var iron: Float? - public var ironValue: Float? - public var iron100G: Float? - public var ironServing: Float? - public var ironUnit: String? - - public var novaGroup: Float? - public var novaGroup100G: Float? - public var novaGroupServing: Float? - - public var proteins: Float? - public var proteinsValue: Float? - public var proteins100G: Float? - public var proteinsServing: Float? - public var proteinsUnit: String? - - public var salt: Float? - public var saltValue: Float? - public var salt100G: Float? - public var saltServing: Float? - public var saltUnit: String? - - public var saturatedFat: Float? - public var saturatedFatValue: Float? - public var saturatedFat100G: Float? - public var saturatedFatServing: Float? - public var saturatedFatUnit: String? - - public var sodium: Float? - public var sodiumValue: Float? - public var sodium100G: Float? - public var sodiumServing: Float? - public var sodiumUnit: String? - - public var sugars: Float? - public var sugarsValue: Float? - public var sugars100G: Float? - public var sugarsServing: Float? - public var sugarsUnit: String? - - public var transFat: Float? - public var transFatValue: Float? - public var transFat100G: Float? - public var transFatServing: Float? - public var transFatUnit: String? - - public var vitaminA: Float? - public var vitaminAValue: Float? - public var vitaminA100G: Float? - public var vitaminAServing: Float? - public var vitaminAUnit: String? - - public var vitaminC: Float? - public var vitaminCValue: Float? - public var vitaminC100G: Float? - public var vitaminCServing: Float? - public var vitaminCUnit: String? - - public var vitaminD: Float? - public var vitaminDValue: Float? - public var vitaminD100G: Float? - public var vitaminDServing: Float? - public var vitaminDUnit: String? - - private enum CodingKeys: String, CodingKey { - case calcium - case calciumValue = "calcium_value" - case calcium100G = "calcium_100g" - case calciumServing = "calcium_serving" - case calciumUnit = "calcium_unit" - - case carbohydrates - case carbohydratesValue = "carbohydrates_value" - case carbohydrates100G = "carbohydrates_100g" - case carbohydratesServing = "carbohydrates_serving" - case carbohydratesUnit = "carbohydrates_unit" - - case carbonFootprintFromKnownIngredientsProduct = "carbon-footprint-from-known-ingredients_product" - case carbonFootprintFromKnownIngredients100G = "carbon-footprint-from-known-ingredients_100g" - case carbonFootprintFromKnownIngredientsServing = "carbon-footprint-from-known-ingredients_serving" - - case cholesterol - case cholesterolValue = "cholesterol_value" - case cholesterol100G = "cholesterol_100g" - case cholesterolServing = "cholesterol_serving" - case cholesterolUnit = "cholesterol_unit" - - case energy - case energyKcal = "energy-kcal" - case energyKj = "energy-kj" - case energyValue = "energy_value" - case energyKcalValue = "energy-kcal_value" - case energyKjValue = "energy-kj_value" - case energy100G = "energy_100g" - case energyKcal100G = "energy-kcal_100g" - case energyKj100G = "energy-kj_100g" - case energyServing = "energy_serving" - case energyKcalServing = "energy-kcal_serving" - case energyKjServing = "energy-kj_serving" - case energyUnit = "energy_unit" - case energyKcalUnit = "energy-kcal_unit" - case energyKjUnit = "energy-kj_unit" - - case fat - case fatValue = "fat_value" - case fat100G = "fat_100g" - case fatServing = "fat_serving" - case fatUnit = "fat_unit" - - case fiber - case fiberValue = "fiber_value" - case fiber100G = "fiber_100g" - case fiberServing = "fiber_serving" - case fiberUnit = "fiber_unit" - - case fruitsVegetablesNutsEstimateFromIngredients100G = "fruits-vegetables-nuts-estimate-from-ingredients_100g" - - case iron - case ironValue = "iron_value" - case iron100G = "iron_100g" - case ironServing = "iron_serving" - case ironUnit = "iron_unit" - - case novaGroup - case novaGroup100G = "nova-group_100g" - case novaGroupServing = "nova-group_serving" - - case proteins - case proteinsValue = "proteins_value" - case proteins100G = "proteins_100g" - case proteinsServing = "proteins_serving" - case proteinsUnit = "proteins_unit" - - case salt - case saltValue = "salt_value" - case salt100G = "salt_100g" - case saltServing = "salt_serving" - case saltUnit = "salt_unit" - - case saturatedFat = "saturated-fat" - case saturatedFatValue = "saturated-fat_value" - case saturatedFat100G = "saturated-fat_100g" - case saturatedFatServing = "saturated-fat_serving" - case saturatedFatUnit = "saturated-fat_unit" - - case sodium - case sodiumValue = "sodium_value" - case sodium100G = "sodium_100g" - case sodiumServing = "sodium_serving" - case sodiumUnit = "sodium_unit" - - case sugars - case sugarsValue = "sugars_value" - case sugars100G = "sugars_100g" - case sugarsServing = "sugars_serving" - case sugarsUnit = "sugars_unit" - - case transFat = "trans-fat" - case transFatValue = "trans-fat_value" - case transFat100G = "trans-fat_100g" - case transFatServing = "trans-fat_serving" - case transFatUnit = "trans-fat_unit" - - case vitaminA = "vitamin-a" - case vitaminAValue = "vitamin-a_value" - case vitaminA100G = "vitamin-a_100g" - case vitaminAServing = "vitamin-a_serving" - case vitaminAUnit = "vitamin-a_unit" - - case vitaminC = "vitamin-c" - case vitaminCValue = "vitamin-c_value" - case vitaminC100G = "vitamin-c_100g" - case vitaminCServing = "vitamin-c_serving" - case vitaminCUnit = "vitamin-c_unit" - - case vitaminD = "vitamin-d" - case vitaminDValue = "vitamin-d_value" - case vitaminD100G = "vitamin-d_100g" - case vitaminDServing = "vitamin-d_serving" - case vitaminDUnit = "vitamin-d_unit" - -// case other - } -} diff --git a/Sources/OpenFoodFacts/types/ObjectDebugger.swift b/Sources/OpenFoodFacts/types/ObjectDebugger.swift deleted file mode 100644 index 6b314d2..0000000 --- a/Sources/OpenFoodFacts/types/ObjectDebugger.swift +++ /dev/null @@ -1,57 +0,0 @@ -public protocol ObjectDebugger: CustomStringConvertible { - var description: String { get } -} - -public extension ObjectDebugger { - var description: String { - var description = "\(type(of: self))(" - let mirror = Mirror(reflecting: self) - - for (label, value) in mirror.children { - if let label = label { - description += "\(label): \(value), " - } - } - - // Remove the trailing comma and space - description = String(description.dropLast(2)) - description += ")" - - return description - } - - private func prettyPrint(object: Any, indentation: String = "") -> String { - var description = "\(type(of: object)) {" - - let mirror = Mirror(reflecting: object) - - for (label, value) in mirror.children { - if let label = label { - let childDescription: String - switch value { - case let nestedObject as CustomStringConvertible: - childDescription = prettyPrint(object: nestedObject, indentation: "\(indentation) ") - case let stringValue as String: - childDescription = "\"\(stringValue)\"" - case let floatValue as Float: - childDescription = "\(floatValue)" - case let intValue as Int: - childDescription = "\(intValue)" - case let optionalValue as CustomStringConvertible?: - if let unwrapped = optionalValue { - childDescription = "\(unwrapped)" - } else { - childDescription = "nil" - } - default: - childDescription = "\(value)" - } - - description += "\n\(indentation) \(label): \(childDescription)" - } - } - - description += "\n\(indentation)}" - return description - } -} diff --git a/Sources/OpenFoodFacts/types/PerlSearchQuery.swift b/Sources/OpenFoodFacts/types/PerlSearchQuery.swift deleted file mode 100644 index 4dbb37f..0000000 --- a/Sources/OpenFoodFacts/types/PerlSearchQuery.swift +++ /dev/null @@ -1,56 +0,0 @@ -public enum PerlOperator: String { - case lt, lte, gt, gte, eq -} - -public enum PerlFormat: String { - case json, xml, jqm -} - -public struct SearchNutrimentEntry { - public let nutriment: SearchNutriment - public let op: PerlOperator - public let value: Int -} - -public struct SearchTagsEntry { - public let tag: SearchTag - public let value: String - public let contains: Bool = true -} - -//https://wiki.openfoodfacts.org/API/Read/Search#Parameters -public struct PerlSearchQuery { - public var searchTerms: String - public var searchTags: [SearchTagsEntry]? // tagtype_i=SearchTag — i as #element (starting from 0); (tag_contains_i|tag_does_not_contain_i)=String - public var searchNutriment: [SearchNutrimentEntry]? // nutriment_i=SearchNutriment — i as #element (starting from 0); nutriment_compare_i=PerlOperator; nutriment_value_i=String - public var page: Int = 1 // Pagination - public var format: PerlFormat // json=1 | xml=1 | jqm=1 - - public init(searchTerms: String, searchTags: [SearchTagsEntry]? = nil, searchNutriment: [SearchNutrimentEntry]? = nil, page: Int = 1, format: PerlFormat = .json) { - self.searchTerms = searchTerms - self.searchTags = searchTags - self.searchNutriment = searchNutriment - self.page = page - self.format = format - } - - public func makeToRequest() -> String { - var _searchTags: String? - var _searchNutriment: String? - let _format: String = "\(format.rawValue)=1" - let _page: String = "page=\(page)" - if let tags = searchTags { - _searchTags = tags.enumerated().map { i, v in - return "tagtype_\(i)=\(v.tag.rawValue)&tag_contains_\(i)=\(v.contains ? "contains" : "does_not_contain")&tag_\(i)=\(v.value)" - }.joined(separator: "&") - } - if let nutriments = searchNutriment { - _searchNutriment = nutriments.enumerated().map { i, v in - return "nutriment_\(i)=\(v.nutriment.rawValue)&nutriment_compare_\(i)=\(v.op.rawValue)&nutriment_value_\(i)=\(v.nutriment.rawValue)" - }.joined(separator: "&") - } - - return "search_terms=\(searchTerms)\(_searchTags != nil ? "&\(_searchTags!)" : "")\(_searchNutriment != nil ? "&\(_searchNutriment!)" : "")&\(_page)&\(_format)" - - } -} diff --git a/Sources/OpenFoodFacts/types/Product.swift b/Sources/OpenFoodFacts/types/Product.swift deleted file mode 100644 index 4b6af52..0000000 --- a/Sources/OpenFoodFacts/types/Product.swift +++ /dev/null @@ -1,726 +0,0 @@ -public class Product: Codable, ObjectDebugger { - // public var images: Images? = Images() - public var ingredients: [Ingredient]? = [] - public var languagesCodes: LanguagesCodes? - public var nutrientLevels: NutrientLevels? - public var nutriments: Nutriments? = Nutriments() - public var selectedImages: SelectedImages? - public var sources: [Source]? = [] - public var additivesN: Float? - public var additivesOldN: Float? - public var additivesOriginalTags: [String]? - public var additivesOldTags: [String]? - public var additivesPrevOriginalTags: [String]? - public var additivesDebugTags: [String]? - public var additivesTags: [String]? - public var allergens: String? - public var allergensFromIngredients: String? - public var allergensFromUser: String? - public var allergensHierarchy: [String]? - public var allergensLc: String? - public var allergensTags: [String]? - public var aminoAcidsPrevTags: [String]? - public var aminoAcidsTags: [String]? - public var brands: String? - public var brandsDebugTags: [String]? - public var brandsTags: [String]? - public var carbonFootprintPercentOfKnownIngredients: Float? - public var carbonFootprintFromKnownIngredientsDebug: String? - public var categories: String? - public var categoriesHierarchy: [String]? - public var categoriesLc: String? - public var categoriesPropertiesTags: [String]? - public var categoriesTags: [String]? - public var checkersTags: [String]? - public var citiesTags: [String]? - public var code: String? - public var codesTags: [String]? - public var comparedToCategory: String? - public var complete: Float? - public var completedT: Float? - public var completeness: Double? - public var conservationConditions: String? - public var countries: String? - public var countriesHierarchy: [String]? - public var countriesLc: String? - public var countriesDebugTags: [String]? - public var countriesTags: [String]? - public var correctorsTags: [String]? - public var createdT: Float? - public var creator: String? - public var dataQualityBugsTags: [String]? - public var dataQualityErrorsTags: [String]? - public var dataQualityInfoTags: [String]? - public var dataQualityTags: [String]? - public var dataQualityWarningsTags: [String]? - public var dataSources: String? - public var dataSourcesTags: [String]? - public var debugParamSortedLangs: [String]? - public var editorsTags: [String]? - public var embCodes: String? - public var embCodesDebugTags: [String]? - public var embCodesOrig: String? - public var embCodesTags: [String]? - public var entryDatesTags: [String]? - public var expirationDate: String? - public var expirationDateDebugTags: [String]? - public var fruitsVegetablesNuts100GEstimate: Float? - public var genericName: String? - public var id: String? - public var imageFrontSmallUrl: String? - public var imageFrontThumbUrl: String? - public var imageFrontUrl: String? - public var imageIngredientsUrl: String? - public var imageIngredientsSmallUrl: String? - public var imageIngredientsThumbUrl: String? - public var imageNutritionSmallUrl: String? - public var imageNutritionThumbUrl: String? - public var imageNutritionUrl: String? - public var imageSmallUrl: String? - public var imageThumbUrl: String? - public var imageUrl: String? - public var informersTags: [String]? - public var ingredientsAnalysisTags: [String]? - public var ingredientsDebug: [String?]? - public var ingredientsFromOrThatMayBeFromPalmOilN: Float? - public var ingredientsFromPalmOilTags: [String]? - public var ingredientsFromPalmOilN: Float? - public var ingredientsHierarchy: [String]? - public var ingredientsIdsDebug: [String]? - public var ingredientsN: Float? - public var ingredientsNTags: [String]? - public var ingredientsOriginalTags: [String]? - public var ingredientsTags: [String]? - public var ingredientsText: String? - public var ingredientsTextDebug: String? - public var ingredientsTextWithAllergens: String? - public var ingredientsThatMayBeFromPalmOilN: Float? - public var ingredientsThatMayBeFromPalmOilTags: [String]? - public var interfaceVersionCreated: String? - public var interfaceVersionModified: String? - public var keywords: [String]? - public var knownIngredientsN: Float? - public var labels: String? - public var labelsHierarchy: [String]? - public var labelsLc: String? - public var labelsPrevHierarchy: [String]? - public var labelsPrevTags: [String]? - public var labelsTags: [String]? - public var labelsDebugTags: [String]? - public var lang: String? - public var langDebugTags: [String]? - public var languagesHierarchy: [String]? - public var languagesTags: [String]? - public var lastEditDatesTags: [String]? - public var lastEditor: String? - public var lastImageDatesTags: [String]? - public var lastImageT: Float? - public var lastModifiedBy: String? - public var lastModifiedT: Float? - public var lc: String? - public var link: String? - public var linkDebugTags: [String]? - public var manufacturingPlaces: String? - public var manufacturingPlacesDebugTags: [String]? - public var manufacturingPlacesTags: [String]? - public var maxImgid: String? - public var mineralsPrevTags: [String]? - public var mineralsTags: [String]? - public var miscTags: [String]? - public var netWeightUnit: String? - public var netWeightValue: String? - public var nutritionDataPer: String? - public var nutritionScoreWarningNoFruitsVegetablesNuts: Float? - public var nutriscoreGrade: String? - public var noNutritionData: String? - public var novaGroup: Float? - public var novaGroups: String? - public var novaGroupDebug: String? - public var novaGroupTags: [String]? - public var novaGroupsTags: [String]? - public var nucleotidesPrevTags: [String]? - public var nucleotidesTags: [String]? - public var nutrientLevelsTags: [String]? - public var nutritionData: String? - public var nutritionDataPerDebugTags: [String]? - public var nutritionDataPrepared: String? - public var nutritionDataPreparedPer: String? - public var nutritionGrades: String? - public var nutritionScoreBeverage: Float? - public var nutritionScoreDebug: String? - public var nutritionScoreWarningNoFiber: Float? - public var nutritionGradesTags: [String]? - public var origins: String? - public var originsDebugTags: [String]? - public var originsTags: [String]? - public var otherInformation: String? - public var otherNutritionalSubstancesTags: [String]? - public var packaging: String? - public var packagingDebugTags: [String]? - public var packagingTags: [String]? - public var photographersTags: [String]? - public var pnnsGroups1: String? - public var pnnsGroups2: String? - public var pnnsGroups1Tags: [String]? - public var pnnsGroups2Tags: [String]? - public var popularityKey: Float? - public var producerVersionId: String? - public var productName: String? - public var productQuantity: Float? - public var purchasePlaces: String? - public var purchasePlacesDebugTags: [String]? - public var purchasePlacesTags: [String]? - public var qualityTags: [String]? - public var quantity: String? - public var quantityDebugTags: [String]? - public var recyclingInstructionsToDiscard: String? - public var rev: Float? - public var servingQuantity: String? - public var servingSize: String? - public var servingSizeDebugTags: [String]? - public var sortkey: Float? - public var states: String? - public var statesHierarchy: [String]? - public var statesTags: [String]? - public var stores: String? - public var storesDebugTags: [String]? - public var storesTags: [String]? - public var traces: String? - public var tracesFromIngredients: String? - public var tracesHierarchy: [String]? - public var tracesDebugTags: [String]? - public var tracesFromUser: String? - public var tracesLc: String? - public var tracesTags: [String]? - public var unknownIngredientsN: Float? - public var unknownNutrientsTags: [String]? - public var updateKey: String? - public var vitaminsPrevTags: [String]? - public var vitaminsTags: [String]? - - private enum CodingKeys: String, CodingKey { - // case images - case ingredients - case languagesCodes = "languages_codes" - case nutrientLevels = "nutrient_levels" - case nutriments - case selectedImages = "selected_images" - case sources - case additivesN = "additives_n" - case additivesOldN = "additives_old_n" - case additivesOriginalTags = "additives_original_tags" - case additivesOldTags = "additives_old_tags" - case additivesPrevOriginalTags = "additives_prev_original_tags" - case additivesDebugTags = "additives_debug_tags" - case additivesTags = "additives_tags" - case allergens - case allergensFromIngredients = "allergens_from_ingredients" - case allergensFromUser = "allergens_from_user" - case allergensHierarchy = "allergens_hierarchy" - case allergensLc = "allergens_lc" - case allergensTags = "allergens_tags" - case aminoAcidsPrevTags = "amino_acids_prev_tags" - case aminoAcidsTags = "amino_acids_tags" - case brands - case brandsDebugTags = "brands_debug_tags" - case brandsTags = "brands_tags" - case carbonFootprintPercentOfKnownIngredients = "carbon_footprint_percent_of_known_ingredients" - case carbonFootprintFromKnownIngredientsDebug = "carbon_footprint_from_known_ingredients_debug" - case categories - case categoriesHierarchy = "categories_hierarchy" - case categoriesLc = "categories_lc" - case categoriesPropertiesTags = "categories_properties_tags" - case categoriesTags = "categories_tags" - case checkersTags = "checkers_tags" - case citiesTags = "cities_tags" - case code - case codesTags = "codes_tags" - case comparedToCategory = "compared_to_category" - case complete - case completedT = "completed_t" - case completeness - case conservationConditions = "conservation_conditions" - case countries - case countriesHierarchy = "countries_hierarchy" - case countriesLc = "countries_lc" - case countriesDebugTags = "countries_debug_tags" - case countriesTags = "countries_tags" - case correctorsTags = "correctors_tags" - case createdT = "created_t" - case creator - case dataQualityBugsTags = "data_quality_bugs_tags" - case dataQualityErrorsTags = "data_quality_errors_tags" - case dataQualityInfoTags = "data_quality_info_tags" - case dataQualityTags = "data_quality_tags" - case dataQualityWarningsTags = "data_quality_warnings_tags" - case dataSources = "data_sources" - case dataSourcesTags = "data_sources_tags" - case debugParamSortedLangs = "debug_param_sorted_langs" - case editorsTags = "editors_tags" - case embCodes = "emb_codes" - case embCodesDebugTags = "emb_codes_debug_tags" - case embCodesOrig = "emb_codes_orig" - case embCodesTags = "emb_codes_tags" - case entryDatesTags = "entry_dates_tags" - case expirationDate = "expiration_date" - case expirationDateDebugTags = "expiration_date_debug_tags" - case fruitsVegetablesNuts100GEstimate = "fruits-vegetables-nuts_100g_estimate" - case genericName - case id - case imageFrontSmallUrl = "image_front_small_url" - case imageFrontThumbUrl = "image_front_thumb_url" - case imageFrontUrl = "image_front_url" - case imageIngredientsUrl = "image_ingredients_url" - case imageIngredientsSmallUrl = "image_ingredients_small_url" - case imageIngredientsThumbUrl = "image_ingredients_thumb_url" - case imageNutritionSmallUrl = "image_nutrition_small_url" - case imageNutritionThumbUrl = "image_nutrition_thumb_url" - case imageNutritionUrl = "image_nutrition_url" - case imageSmallUrl = "image_small_url" - case imageThumbUrl = "image_thumb_url" - case imageUrl = "image_url" - case informersTags = "informers_tags" - case ingredientsAnalysisTags = "ingredients_analysis_tags" - case ingredientsDebug = "ingredients_debug" - case ingredientsFromOrThatMayBeFromPalmOilN = "ingredients_from_or_that_may_be_from_palm_oil_n" - case ingredientsFromPalmOilTags = "ingredients_from_palm_oil_tags" - case ingredientsFromPalmOilN = "ingredients_from_palm_oil_n" - case ingredientsHierarchy = "ingredients_hierarchy" - case ingredientsIdsDebug = "ingredients_ids_debug" - case ingredientsN = "ingredients_n" - case ingredientsNTags = "ingredients_n_tags" - case ingredientsOriginalTags = "ingredients_original_tags" - case ingredientsTags = "ingredients_tags" - case ingredientsText = "ingredients_text" - case ingredientsTextDebug = "ingredients_text_debug" - case ingredientsTextWithAllergens = "ingredients_text_with_allergens" - case ingredientsThatMayBeFromPalmOilN = "ingredients_that_may_be_from_palm_oil_n" - case ingredientsThatMayBeFromPalmOilTags = "ingredients_that_may_be_from_palm_oil_tags" - case interfaceVersionCreated = "interface_version_created" - case interfaceVersionModified = "interface_version_modified" - case keywords - case knownIngredientsN = "known_ingredients_n" - case labels - case labelsHierarchy = "labels_hierarchy" - case labelsLc = "labels_lc" - case labelsPrevHierarchy = "labels_prev_hierarchy" - case labelsPrevTags = "labels_prev_tags" - case labelsTags = "labels_tags" - case labelsDebugTags = "labels_debug_tags" - case lang - case langDebugTags = "lang_debug_tags" - case languagesHierarchy = "languages_hierarchy" - case languagesTags = "languages_tags" - case lastEditDatesTags = "last_edit_dates_tags" - case lastEditor = "last_editor" - case lastImageDatesTags = "last_image_dates_tags" - case lastImageT = "last_image_t" - case lastModifiedBy = "last_modified_by" - case lastModifiedT = "last_modified_t" - case lc - case link - case linkDebugTags = "link_debug_tags" - case manufacturingPlaces = "manufacturing_places" - case manufacturingPlacesDebugTags = "manufacturing_places_debug_tags" - case manufacturingPlacesTags = "manufacturing_places_tags" - case maxImgid = "max_imgid" - case mineralsPrevTags = "minerals_prev_tags" - case mineralsTags = "minerals_tags" - case miscTags = "misc_tags" - case netWeightUnit = "net_weight_unit" - case netWeightValue = "net_weight_value" - case nutritionDataPer = "nutrition_data_per" - case nutritionScoreWarningNoFruitsVegetablesNuts = "nutrition_score_warning_no_fruits_vegetables_nuts" - case nutriscoreGrade = "nutriscore_grade" - case noNutritionData = "no_nutrition_data" - case novaGroup = "nova_group" - case novaGroups = "nova_groups" - case novaGroupDebug = "nova_group_debug" - case novaGroupTags = "nova_group_tags" - case novaGroupsTags = "nova_groups_tags" - case nucleotidesPrevTags = "nucleotides_prev_tags" - case nucleotidesTags = "nucleotides_tags" - case nutrientLevelsTags = "nutrient_levels_tags" - case nutritionData = "nutrition_data" - case nutritionDataPerDebugTags = "nutrition_data_per_debug_tags" - case nutritionDataPrepared = "nutrition_data_prepared" - case nutritionDataPreparedPer = "nutrition_data_prepared_per" - case nutritionGrades = "nutrition_grades" - case nutritionScoreBeverage = "nutrition_score_beverage" - case nutritionScoreDebug = "nutrition_score_debug" - case nutritionScoreWarningNoFiber = "nutrition_score_warning_no_fiber" - case nutritionGradesTags = "nutrition_grades_tags" - case origins - case originsDebugTags = "origins_debug_tags" - case originsTags = "origins_tags" - case otherInformation = "other_information" - case otherNutritionalSubstancesTags = "other_nutritional_substances_tags" - case packaging - case packagingDebugTags = "packaging_debug_tags" - case packagingTags = "packaging_tags" - case photographersTags = "photographers_tags" - case pnnsGroups1 = "pnns_groups_1" - case pnnsGroups2 = "pnns_groups_2" - case pnnsGroups1Tags = "pnns_groups_1_tags" - case pnnsGroups2Tags = "pnns_groups_2_tags" - case popularityKey = "popularity_key" - case producerVersionId = "producer_version_id" - case productName = "product_name" - case productQuantity = "product_quantity" - case purchasePlaces = "purchase_places" - case purchasePlacesDebugTags = "purchase_places_debug_tags" - case purchasePlacesTags = "purchase_places_tags" - case qualityTags = "quality_tags" - case quantity - case quantityDebugTags = "quantity_debug_tags" - case recyclingInstructionsToDiscard = "recycling_instructions_to_discard" - case rev - case servingQuantity = "serving_quantity" - case servingSize = "serving_size" - case servingSizeDebugTags = "serving_size_debug_tags" - case sortkey - case states - case statesHierarchy = "states_hierarchy" - case statesTags = "states_tags" - case stores - case storesDebugTags = "stores_debug_tags" - case storesTags = "stores_tags" - case traces - case tracesFromIngredients = "traces_from_ingredients" - case tracesHierarchy = "traces_hierarchy" - case tracesDebugTags = "traces_debug_tags" - case tracesFromUser = "traces_from_user" - case tracesLc = "traces_lc" - case tracesTags = "traces_tags" - case unknownIngredientsN = "unknown_ingredients_n" - case unknownNutrientsTags = "unknown_nutrients_tags" - case updateKey = "update_key" - case vitaminsPrevTags = "vitamins_prev_tags" - case vitaminsTags = "vitamins_tags" - } - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - ingredients = try container.decodeIfPresent([Ingredient].self, forKey: .ingredients) - languagesCodes = try container.decodeIfPresent(LanguagesCodes.self, forKey: .languagesCodes) - nutrientLevels = try container.decodeIfPresent(NutrientLevels.self, forKey: .nutrientLevels) - nutriments = try container.decodeIfPresent(Nutriments.self, forKey: .nutriments) ?? Nutriments() - selectedImages = try container.decodeIfPresent(SelectedImages.self, forKey: .selectedImages) - sources = try container.decodeIfPresent([Source].self, forKey: .sources) - additivesN = try container.decodeIfPresent(Float.self, forKey: .additivesN) - additivesOldN = try container.decodeIfPresent(Float.self, forKey: .additivesOldN) - additivesOriginalTags = try container.decodeIfPresent([String].self, forKey: .additivesOriginalTags) - additivesOldTags = try container.decodeIfPresent([String].self, forKey: .additivesOldTags) - additivesPrevOriginalTags = try container.decodeIfPresent([String].self, forKey: .additivesPrevOriginalTags) - additivesDebugTags = try container.decodeIfPresent([String].self, forKey: .additivesDebugTags) - additivesTags = try container.decodeIfPresent([String].self, forKey: .additivesTags) - allergens = try container.decodeIfPresent(String.self, forKey: .allergens) - allergensFromIngredients = try container.decodeIfPresent(String.self, forKey: .allergensFromIngredients) - allergensFromUser = try container.decodeIfPresent(String.self, forKey: .allergensFromUser) - allergensHierarchy = try container.decodeIfPresent([String].self, forKey: .allergensHierarchy) - allergensLc = try container.decodeIfPresent(String.self, forKey: .allergensLc) - allergensTags = try container.decodeIfPresent([String].self, forKey: .allergensTags) - aminoAcidsPrevTags = try container.decodeIfPresent([String].self, forKey: .aminoAcidsPrevTags) - aminoAcidsTags = try container.decodeIfPresent([String].self, forKey: .aminoAcidsTags) - brands = try container.decodeIfPresent(String.self, forKey: .brands) - brandsDebugTags = try container.decodeIfPresent([String].self, forKey: .brandsDebugTags) - brandsTags = try container.decodeIfPresent([String].self, forKey: .brandsTags) - carbonFootprintPercentOfKnownIngredients = try container.decodeIfPresent(Float.self, forKey: .carbonFootprintPercentOfKnownIngredients) - carbonFootprintFromKnownIngredientsDebug = try container.decodeIfPresent(String.self, forKey: .carbonFootprintFromKnownIngredientsDebug) - categories = try container.decodeIfPresent(String.self, forKey: .categories) - categoriesHierarchy = try container.decodeIfPresent([String].self, forKey: .categoriesHierarchy) - categoriesLc = try container.decodeIfPresent(String.self, forKey: .categoriesLc) - categoriesPropertiesTags = try container.decodeIfPresent([String].self, forKey: .categoriesPropertiesTags) - categoriesTags = try container.decodeIfPresent([String].self, forKey: .categoriesTags) - checkersTags = try container.decodeIfPresent([String].self, forKey: .checkersTags) - citiesTags = try container.decodeIfPresent([String].self, forKey: .citiesTags) - code = try container.decodeIfPresent(String.self, forKey: .code) - codesTags = try container.decodeIfPresent([String].self, forKey: .codesTags) - comparedToCategory = try container.decodeIfPresent(String.self, forKey: .comparedToCategory) - complete = try container.decodeIfPresent(Float.self, forKey: .complete) - completedT = try container.decodeIfPresent(Float.self, forKey: .completedT) - completeness = try container.decodeIfPresent(Double.self, forKey: .completeness) - conservationConditions = try container.decodeIfPresent(String.self, forKey: .conservationConditions) - countries = try container.decodeIfPresent(String.self, forKey: .countries) - countriesHierarchy = try container.decodeIfPresent([String].self, forKey: .countriesHierarchy) - countriesLc = try container.decodeIfPresent(String.self, forKey: .countriesLc) - countriesDebugTags = try container.decodeIfPresent([String].self, forKey: .countriesDebugTags) - countriesTags = try container.decodeIfPresent([String].self, forKey: .countriesTags) - correctorsTags = try container.decodeIfPresent([String].self, forKey: .correctorsTags) - createdT = try container.decodeIfPresent(Float.self, forKey: .createdT) - creator = try container.decodeIfPresent(String.self, forKey: .creator) - dataQualityBugsTags = try container.decodeIfPresent([String].self, forKey: .dataQualityBugsTags) - dataQualityErrorsTags = try container.decodeIfPresent([String].self, forKey: .dataQualityErrorsTags) - dataQualityInfoTags = try container.decodeIfPresent([String].self, forKey: .dataQualityInfoTags) - dataQualityTags = try container.decodeIfPresent([String].self, forKey: .dataQualityTags) - dataQualityWarningsTags = try container.decodeIfPresent([String].self, forKey: .dataQualityWarningsTags) - dataSources = try container.decodeIfPresent(String.self, forKey: .dataSources) - dataSourcesTags = try container.decodeIfPresent([String].self, forKey: .dataSourcesTags) - debugParamSortedLangs = try container.decodeIfPresent([String].self, forKey: .debugParamSortedLangs) - editorsTags = try container.decodeIfPresent([String].self, forKey: .editorsTags) - embCodes = try container.decodeIfPresent(String.self, forKey: .embCodes) - embCodesDebugTags = try container.decodeIfPresent([String].self, forKey: .embCodesDebugTags) - embCodesOrig = try container.decodeIfPresent(String.self, forKey: .embCodesOrig) - embCodesTags = try container.decodeIfPresent([String].self, forKey: .embCodesTags) - entryDatesTags = try container.decodeIfPresent([String].self, forKey: .entryDatesTags) - expirationDate = try container.decodeIfPresent(String.self, forKey: .expirationDate) - expirationDateDebugTags = try container.decodeIfPresent([String].self, forKey: .expirationDateDebugTags) - fruitsVegetablesNuts100GEstimate = try container.decodeIfPresent(Float.self, forKey: .fruitsVegetablesNuts100GEstimate) - genericName = try container.decodeIfPresent(String.self, forKey: .genericName) - id = try container.decodeIfPresent(String.self, forKey: .id) - imageFrontSmallUrl = try container.decodeIfPresent(String.self, forKey: .imageFrontSmallUrl) - imageFrontThumbUrl = try container.decodeIfPresent(String.self, forKey: .imageFrontThumbUrl) - imageFrontUrl = try container.decodeIfPresent(String.self, forKey: .imageFrontUrl) - imageIngredientsUrl = try container.decodeIfPresent(String.self, forKey: .imageIngredientsUrl) - imageIngredientsSmallUrl = try container.decodeIfPresent(String.self, forKey: .imageIngredientsSmallUrl) - imageIngredientsThumbUrl = try container.decodeIfPresent(String.self, forKey: .imageIngredientsThumbUrl) - imageNutritionSmallUrl = try container.decodeIfPresent(String.self, forKey: .imageNutritionSmallUrl) - imageNutritionThumbUrl = try container.decodeIfPresent(String.self, forKey: .imageNutritionThumbUrl) - imageNutritionUrl = try container.decodeIfPresent(String.self, forKey: .imageNutritionUrl) - imageSmallUrl = try container.decodeIfPresent(String.self, forKey: .imageSmallUrl) - imageThumbUrl = try container.decodeIfPresent(String.self, forKey: .imageThumbUrl) - imageUrl = try container.decodeIfPresent(String.self, forKey: .imageUrl) - informersTags = try container.decodeIfPresent([String].self, forKey: .informersTags) - ingredientsAnalysisTags = try container.decodeIfPresent([String].self, forKey: .ingredientsAnalysisTags) - ingredientsDebug = try container.decodeIfPresent([String?].self, forKey: .ingredientsDebug) - ingredientsFromOrThatMayBeFromPalmOilN = try container.decodeIfPresent(Float.self, forKey: .ingredientsFromOrThatMayBeFromPalmOilN) - ingredientsFromPalmOilTags = try container.decodeIfPresent([String].self, forKey: .ingredientsFromPalmOilTags) - ingredientsFromPalmOilN = try container.decodeIfPresent(Float.self, forKey: .ingredientsFromPalmOilN) - ingredientsHierarchy = try container.decodeIfPresent([String].self, forKey: .ingredientsHierarchy) - ingredientsIdsDebug = try container.decodeIfPresent([String].self, forKey: .ingredientsIdsDebug) - if container.contains(.ingredientsN) { - if try container.decodeNil(forKey: .ingredientsN) { - ingredientsN = nil - } else { - if let intValue = try? container.decode(Float.self, forKey: .ingredientsN) { - ingredientsN = intValue - } else if let stringValue = try? container.decode(String.self, forKey: .ingredientsN) { - ingredientsN = Float(stringValue) - }else { - // If decoding as both Float and String fails, handle the error accordingly - throw DecodingError.dataCorruptedError( - forKey: .ingredientsN, - in: container, - debugDescription: "Unable to decode ingredientsN" - ) - } - - } - } else { - ingredientsN = nil - } - ingredientsNTags = try container.decodeIfPresent([String].self, forKey: .ingredientsNTags) - ingredientsOriginalTags = try container.decodeIfPresent([String].self, forKey: .ingredientsOriginalTags) - ingredientsTags = try container.decodeIfPresent([String].self, forKey: .ingredientsTags) - ingredientsText = try container.decodeIfPresent(String.self, forKey: .ingredientsText) - ingredientsTextDebug = try container.decodeIfPresent(String.self, forKey: .ingredientsTextDebug) - ingredientsTextWithAllergens = try container.decodeIfPresent(String.self, forKey: .ingredientsTextWithAllergens) - ingredientsThatMayBeFromPalmOilN = try container.decodeIfPresent(Float.self, forKey: .ingredientsThatMayBeFromPalmOilN) - ingredientsThatMayBeFromPalmOilTags = try container.decodeIfPresent([String].self, forKey: .ingredientsThatMayBeFromPalmOilTags) - interfaceVersionCreated = try container.decodeIfPresent(String.self, forKey: .interfaceVersionCreated) - interfaceVersionModified = try container.decodeIfPresent(String.self, forKey: .interfaceVersionModified) - keywords = try container.decodeIfPresent([String].self, forKey: .keywords) - knownIngredientsN = try container.decodeIfPresent(Float.self, forKey: .knownIngredientsN) - labels = try container.decodeIfPresent(String.self, forKey: .labels) - labelsHierarchy = try container.decodeIfPresent([String].self, forKey: .labelsHierarchy) - labelsLc = try container.decodeIfPresent(String.self, forKey: .labelsLc) - labelsPrevHierarchy = try container.decodeIfPresent([String].self, forKey: .labelsPrevHierarchy) - labelsPrevTags = try container.decodeIfPresent([String].self, forKey: .labelsPrevTags) - labelsTags = try container.decodeIfPresent([String].self, forKey: .labelsTags) - labelsDebugTags = try container.decodeIfPresent([String].self, forKey: .labelsDebugTags) - lang = try container.decodeIfPresent(String.self, forKey: .lang) - langDebugTags = try container.decodeIfPresent([String].self, forKey: .langDebugTags) - languagesHierarchy = try container.decodeIfPresent([String].self, forKey: .languagesHierarchy) - languagesTags = try container.decodeIfPresent([String].self, forKey: .languagesTags) - lastEditDatesTags = try container.decodeIfPresent([String].self, forKey: .lastEditDatesTags) - lastEditor = try container.decodeIfPresent(String.self, forKey: .lastEditor) - lastImageDatesTags = try container.decodeIfPresent([String].self, forKey: .lastImageDatesTags) - lastImageT = try container.decodeIfPresent(Float.self, forKey: .lastImageT) - lastModifiedBy = try container.decodeIfPresent(String.self, forKey: .lastModifiedBy) - if container.contains(.lastModifiedT) { - if try container.decodeNil(forKey: .maxImgid) { - lastModifiedT = nil - } else { - if let floatValue = try? container.decode(Float.self, forKey: .lastModifiedT) { - lastModifiedT = floatValue - } else if let stringValue = try? container.decode(String.self, forKey: .lastModifiedT) { - lastModifiedT = Float(stringValue) - }else { - // If decoding as both Float and String fails, handle the error accordingly - throw DecodingError.dataCorruptedError( - forKey: .lastModifiedT, - in: container, - debugDescription: "Unable to decode lastModifiedT" - ) - } - - } - } else { - lastModifiedT = nil - } - lc = try container.decodeIfPresent(String.self, forKey: .lc) - link = try container.decodeIfPresent(String.self, forKey: .link) - linkDebugTags = try container.decodeIfPresent([String].self, forKey: .linkDebugTags) - manufacturingPlaces = try container.decodeIfPresent(String.self, forKey: .manufacturingPlaces) - manufacturingPlacesDebugTags = try container.decodeIfPresent([String].self, forKey: .manufacturingPlacesDebugTags) - manufacturingPlacesTags = try container.decodeIfPresent([String].self, forKey: .manufacturingPlacesTags) - if container.contains(.maxImgid) { - if try container.decodeNil(forKey: .maxImgid) { - maxImgid = nil - } else { - if let floatValue = try? container.decode(Float.self, forKey: .maxImgid) { - maxImgid = "\(floatValue)" - } else if let stringValue = try? container.decode(String.self, forKey: .maxImgid) { - maxImgid = stringValue - }else { - // If decoding as both Float and String fails, handle the error accordingly - throw DecodingError.dataCorruptedError( - forKey: .maxImgid, - in: container, - debugDescription: "Unable to decode maxImgid" - ) - } - - } - } else { - maxImgid = nil - } - mineralsPrevTags = try container.decodeIfPresent([String].self, forKey: .mineralsPrevTags) - mineralsTags = try container.decodeIfPresent([String].self, forKey: .mineralsTags) - miscTags = try container.decodeIfPresent([String].self, forKey: .miscTags) - netWeightUnit = try container.decodeIfPresent(String.self, forKey: .netWeightUnit) - netWeightValue = try container.decodeIfPresent(String.self, forKey: .netWeightValue) - nutritionDataPer = try container.decodeIfPresent(String.self, forKey: .nutritionDataPer) - nutritionScoreWarningNoFruitsVegetablesNuts = try container.decodeIfPresent(Float.self, forKey: .nutritionScoreWarningNoFruitsVegetablesNuts) - noNutritionData = try container.decodeIfPresent(String.self, forKey: .noNutritionData) - novaGroup = try container.decodeIfPresent(Float.self, forKey: .novaGroup) - novaGroups = try container.decodeIfPresent(String.self, forKey: .novaGroups) - novaGroupDebug = try container.decodeIfPresent(String.self, forKey: .novaGroupDebug) - novaGroupTags = try container.decodeIfPresent([String].self, forKey: .novaGroupTags) - novaGroupsTags = try container.decodeIfPresent([String].self, forKey: .novaGroupsTags) - nucleotidesTags = try container.decodeIfPresent([String].self, forKey: .nucleotidesTags) - nutrientLevelsTags = try container.decodeIfPresent([String].self, forKey: .nutrientLevelsTags) - nutritionData = try container.decodeIfPresent(String.self, forKey: .nutritionData) - nutriscoreGrade = try container.decodeIfPresent(String.self, forKey: .nutriscoreGrade) - nutritionDataPerDebugTags = try container.decodeIfPresent([String].self, forKey: .nutritionDataPerDebugTags) - nutritionDataPrepared = try container.decodeIfPresent(String.self, forKey: .nutritionDataPrepared) - nutritionDataPreparedPer = try container.decodeIfPresent(String.self, forKey: .nutritionDataPreparedPer) - nutritionGrades = try container.decodeIfPresent(String.self, forKey: .nutritionGrades) - nutritionScoreBeverage = try container.decodeIfPresent(Float.self, forKey: .nutritionScoreBeverage) - nutritionScoreDebug = try container.decodeIfPresent(String.self, forKey: .nutritionScoreDebug) - nutritionScoreWarningNoFiber = try container.decodeIfPresent(Float.self, forKey: .nutritionScoreWarningNoFiber) - nutritionGradesTags = try container.decodeIfPresent([String].self, forKey: .nutritionGradesTags) - origins = try container.decodeIfPresent(String.self, forKey: .origins) - originsDebugTags = try container.decodeIfPresent([String].self, forKey: .originsDebugTags) - originsTags = try container.decodeIfPresent([String].self, forKey: .originsTags) - otherInformation = try container.decodeIfPresent(String.self, forKey: .otherInformation) - otherNutritionalSubstancesTags = try container.decodeIfPresent([String].self, forKey: .otherNutritionalSubstancesTags) - packaging = try container.decodeIfPresent(String.self, forKey: .packaging) - packagingDebugTags = try container.decodeIfPresent([String].self, forKey: .packagingDebugTags) - packagingTags = try container.decodeIfPresent([String].self, forKey: .packagingTags) - photographersTags = try container.decodeIfPresent([String].self, forKey: .photographersTags) - pnnsGroups1 = try container.decodeIfPresent(String.self, forKey: .pnnsGroups1) - pnnsGroups2 = try container.decodeIfPresent(String.self, forKey: .pnnsGroups2) - pnnsGroups1Tags = try container.decodeIfPresent([String].self, forKey: .pnnsGroups1Tags) - pnnsGroups2Tags = try container.decodeIfPresent([String].self, forKey: .pnnsGroups2Tags) - popularityKey = try container.decodeIfPresent(Float.self, forKey: .popularityKey) - producerVersionId = try container.decodeIfPresent(String.self, forKey: .producerVersionId) - productName = try container.decodeIfPresent(String.self, forKey: .productName) - purchasePlaces = try container.decodeIfPresent(String.self, forKey: .purchasePlaces) - purchasePlacesDebugTags = try container.decodeIfPresent([String].self, forKey: .purchasePlacesDebugTags) - purchasePlacesTags = try container.decodeIfPresent([String].self, forKey: .purchasePlacesTags) - qualityTags = try container.decodeIfPresent([String].self, forKey: .qualityTags) - quantity = try container.decodeIfPresent(String.self, forKey: .quantity) - quantityDebugTags = try container.decodeIfPresent([String].self, forKey: .quantityDebugTags) - recyclingInstructionsToDiscard = try container.decodeIfPresent(String.self, forKey: .recyclingInstructionsToDiscard) - rev = try container.decodeIfPresent(Float.self, forKey: .rev) - if container.contains(.servingQuantity) { - if try container.decodeNil(forKey: .servingQuantity) { - servingQuantity = nil - } else { - if let floatValue = try? container.decode(Float.self, forKey: .servingQuantity) { - servingQuantity = "\(floatValue)" - } else if let stringValue = try? container.decode(String.self, forKey: .servingQuantity) { - servingQuantity = stringValue - }else { - // If decoding as both Float and String fails, handle the error accordingly - throw DecodingError.dataCorruptedError( - forKey: .servingQuantity, - in: container, - debugDescription: "Unable to decode servingQuantity" - ) - } - - } - } else { - servingQuantity = nil - } - servingSize = try container.decodeIfPresent(String.self, forKey: .servingSize) - servingSizeDebugTags = try container.decodeIfPresent([String].self, forKey: .servingSizeDebugTags) - sortkey = try container.decodeIfPresent(Float.self, forKey: .sortkey) - states = try container.decodeIfPresent(String.self, forKey: .states) - statesHierarchy = try container.decodeIfPresent([String].self, forKey: .statesHierarchy) - statesTags = try container.decodeIfPresent([String].self, forKey: .statesTags) - stores = try container.decodeIfPresent(String.self, forKey: .stores) - storesDebugTags = try container.decodeIfPresent([String].self, forKey: .storesDebugTags) - storesTags = try container.decodeIfPresent([String].self, forKey: .storesTags) - traces = try container.decodeIfPresent(String.self, forKey: .traces) - tracesFromIngredients = try container.decodeIfPresent(String.self, forKey: .tracesFromIngredients) - tracesHierarchy = try container.decodeIfPresent([String].self, forKey: .tracesHierarchy) - tracesDebugTags = try container.decodeIfPresent([String].self, forKey: .tracesDebugTags) - tracesFromUser = try container.decodeIfPresent(String.self, forKey: .tracesFromUser) - tracesLc = try container.decodeIfPresent(String.self, forKey: .tracesLc) - tracesTags = try container.decodeIfPresent([String].self, forKey: .tracesTags) - if container.contains(.unknownIngredientsN) { - if try container.decodeNil(forKey: .unknownIngredientsN) { - unknownIngredientsN = nil - } else { - if let intValue = try? container.decode(Float.self, forKey: .unknownIngredientsN) { - unknownIngredientsN = intValue - } else if let stringValue = try? container.decode(String.self, forKey: .unknownIngredientsN) { - unknownIngredientsN = Float(stringValue) - }else { - // If decoding as both Float and String fails, handle the error accordingly - throw DecodingError.dataCorruptedError( - forKey: .unknownIngredientsN, - in: container, - debugDescription: "Unable to decode unknownIngredientsN" - ) - } - - } - } else { - unknownIngredientsN = nil - } - unknownNutrientsTags = try container.decodeIfPresent([String].self, forKey: .unknownNutrientsTags) - updateKey = try container.decodeIfPresent(String.self, forKey: .updateKey) - vitaminsPrevTags = try container.decodeIfPresent([String].self, forKey: .vitaminsPrevTags) - vitaminsTags = try container.decodeIfPresent([String].self, forKey: .vitaminsTags) - - // Check for null value - if container.contains(.productQuantity) { - if try container.decodeNil(forKey: .productQuantity) { - productQuantity = nil - } else { - // Try to decode as Float - if let floatValue = try? container.decode(Float.self, forKey: .productQuantity) { - productQuantity = floatValue - } else if let stringValue = try? container.decode(String.self, forKey: .productQuantity) { - // If decoding as Float fails, try to decode as String - productQuantity = Float(stringValue) - } else { - // If decoding as both Float and String fails, handle the error accordingly - throw DecodingError.dataCorruptedError( - forKey: .productQuantity, - in: container, - debugDescription: "Unable to decode productQuantity" - ) - } - } - } else { - productQuantity = nil - } - - // ... (initialize other properties) - } -} diff --git a/Sources/OpenFoodFacts/types/ProductResponse.swift b/Sources/OpenFoodFacts/types/ProductResponse.swift deleted file mode 100644 index e168b18..0000000 --- a/Sources/OpenFoodFacts/types/ProductResponse.swift +++ /dev/null @@ -1,14 +0,0 @@ - -public struct ProductResponse: Codable, ObjectDebugger { - public var product: Product? - public var code: String? - public var status: Int? // or Bool, depending on your needs - public var statusVerbose: String? - - private enum CodingKeys: String, CodingKey { - case product - case code - case status - case statusVerbose = "status_verbose" - } -} diff --git a/Sources/OpenFoodFacts/types/SearchNutriment.swift b/Sources/OpenFoodFacts/types/SearchNutriment.swift deleted file mode 100644 index e733270..0000000 --- a/Sources/OpenFoodFacts/types/SearchNutriment.swift +++ /dev/null @@ -1,98 +0,0 @@ -public enum SearchNutriment: String, Codable { - case energy // Energy - case energyFromFat = "energy-from-fat" // Energy from fat - case fat // Fat - case saturatedFat = "saturated-fat" // Saturated fat - case butyricAcid = "butyric-acid" // Butyric acid (4:0) - case caproicAcid = "caproic-acid" // Caproic acid (6:0) - case caprylicAcid = "caprylic-acid" // Caprylic acid (8:0) - case capricAcid = "capric-acid" // Capric acid (10:0) - case lauricAcid = "lauric-acid" // Lauric acid (12:0) - case myristicAcid = "myristic-acid" // Myristic acid (14:0) - case palmiticAcid = "palmitic-acid" // Palmitic acid (16:0) - case stearicAcid = "stearic-acid" // Stearic acid (18:0) - case arachidicAcid = "arachidic-acid" // Arachidic acid (20:0) - case behenicAcid = "behenic-acid" // Behenic acid (22:0) - case lignocericAcid = "lignoceric-acid" // Lignoceric acid (24:0) - case ceroticAcid = "cerotic-acid" // Cerotic acid (26:0) - case montanicAcid = "montanic-acid" // Montanic acid (28:0) - case melissicAcid = "melissic-acid" // Melissic acid (30:0) - case monounsaturatedFat = "monounsaturated-fat" // Monounsaturated fat - case polyunsaturatedFat = "polyunsaturated-fat" // Polyunsaturated fat - case omega3Fat = "omega-3-fat" // Omega 3 fatty acids - case alphaLinolenicAcid = "alpha-linolenic-acid" // Alpha-linolenic acid / ALA (18:3 n-3) - case eicosapentaenoicAcid = "eicosapentaenoic-acid" // Eicosapentaenoic acid / EPA (20:5 n-3) - case docosahexaenoicAcid = "docosahexaenoic-acid" // Docosahexaenoic acid / DHA (22:6 n-3) - case omega6Fat = "omega-6-fat" // Omega 6 fatty acids - case linoleicAcid = "linoleic-acid" // Linoleic acid / LA (18:2 n-6) - case arachidonicAcid = "arachidonic-acid" // Arachidonic acid / AA / ARA (20:4 n-6) - case gammaLinolenicAcid = "gamma-linolenic-acid" // Gamma-linolenic acid / GLA (18:3 n-6) - case dihomoGammaLinolenicAcid = "dihomo-gamma-linolenic-acid" // Dihomo-gamma-linolenic acid / DGLA (20:3 n-6) - case omega9Fat = "omega-9-fat" // Omega 9 fatty acids - case oleicAcid = "oleic-acid" // Oleic acid (18:1 n-9) - case elaidicAcid = "elaidic-acid" // Elaidic acid (18:1 n-9) - case gondoicAcid = "gondoic-acid" // Gondoic acid (20:1 n-9) - case meadAcid = "mead-acid" // Mead acid (20:3 n-9) - case erucicAcid = "erucic-acid" // Erucic acid (22:1 n-9) - case nervonicAcid = "nervonic-acid" // Nervonic acid (24:1 n-9) - case transFat = "trans-fat" // Trans fat - case cholesterol // Cholesterol - case carbohydrates // Carbohydrate - case sugars // Sugars - case sucrose // Sucrose - case glucose // Glucose - case fructose // Fructose - case lactose // Lactose - case maltose // Maltose - case maltodextrins // Maltodextrins - case starch // Starch - case polyols // Sugar alcohols (Polyols) - case fiber // Dietary fiber - case proteins // Proteins - case casein // Casein - case serumProteins = "serum-proteins" // Serum proteins - case nucleotides // Nucleotides - case salt // Salt - case sodium // Sodium - case alcohol // Alcohol - case vitaminA = "vitamin-a" // Vitamin A - case betaCarotene = "beta-carotene" // Beta carotene - case vitaminD = "vitamin-d" // Vitamin D - case vitaminE = "vitamin-e" // Vitamin E - case vitaminK = "vitamin-k" // Vitamin K - case vitaminC = "vitamin-c" // Vitamin C (ascorbic acid) - case vitaminB1 = "vitamin-b1" // Vitamin B1 (Thiamin) - case vitaminB2 = "vitamin-b2" // Vitamin B2 (Riboflavin) - case vitaminPP = "vitamin-pp" // Vitamin B3 / Vitamin PP (Niacin) - case vitaminB6 = "vitamin-b6" // Vitamin B6 (Pyridoxin) - case vitaminB9 = "vitamin-b9" // Vitamin B9 (Folic acid / Folates) - case vitaminB12 = "vitamin-b12" // Vitamin B12 (Cobalamin) - case biotin // Biotin - case pantothenicAcid = "pantothenic-acid" // Pantothenic acid / Pantothenate (Vitamin B5) - case silica // Silica - case bicarbonate // Bicarbonate - case potassium // Potassium - case chloride // Chloride - case calcium // Calcium - case phosphorus // Phosphorus - case iron // Iron - case magnesium // Magnesium - case zinc // Zinc - case copper // Copper - case manganese // Manganese - case fluoride // Fluoride - case selenium // Selenium - case chromium // Chromium - case molybdenum // Molybdenum - case iodine // Iodine - case caffeine // Caffeine - case taurine // Taurine - case pH // pH - case fruitsVegetablesNuts = "fruits-vegetables-nuts" // Fruits, vegetables, and nuts (minimum) - case collagenMeatProteinRatio = "collagen-meat-protein-ratio" // Collagen/Meat protein ratio (maximum) - case cocoa // Cocoa (minimum) - case chlorophyll = "chlorophyll" // Chlorophyll - case carbonFootprint = "carbon-footprint" // Carbon footprint / CO2 emissions - case nutritionScoreFR = "nutrition-score-fr" // Experimental nutrition score - case nutritionScoreUK = "nutrition-score-uk" // Nutrition score - UK -} diff --git a/Sources/OpenFoodFacts/types/SearchResponse.swift b/Sources/OpenFoodFacts/types/SearchResponse.swift deleted file mode 100644 index 27fc10f..0000000 --- a/Sources/OpenFoodFacts/types/SearchResponse.swift +++ /dev/null @@ -1,14 +0,0 @@ -public struct SearchResponse: Codable, ObjectDebugger { - public var count: Int - public var page: Int - public var pageCount: Int - public var pageSize: Int - public var products: [Product]? - public var skip: Int - - private enum CodingKeys: String, CodingKey { - case count, page, products, skip - case pageCount = "page_count" - case pageSize = "page_size" - } -} diff --git a/Sources/OpenFoodFacts/types/SearchTag.swift b/Sources/OpenFoodFacts/types/SearchTag.swift deleted file mode 100644 index 4cc9f0b..0000000 --- a/Sources/OpenFoodFacts/types/SearchTag.swift +++ /dev/null @@ -1,17 +0,0 @@ -public enum SearchTag: String, Codable { - case brands // Brands - case categories // Categories - case packaging // Packaging - case labels // Labels - case origins // Origins of ingredients - case manufacturingPlaces = "manufacturing_places" // Manufacturing or processing places - case embCodes = "emb_codes" // Packager codes - case purchasePlaces = "purchase_places" // Purchase places - case stores // Stores - case countries // Countries - case additives // Additives - case allergens // Allergens - case traces // Traces - case nutritionGrades = "nutrition_grades" // Nutrition grades - case states // States -} diff --git a/Sources/OpenFoodFacts/types/SelectedImage.swift b/Sources/OpenFoodFacts/types/SelectedImage.swift deleted file mode 100644 index 1b46a2d..0000000 --- a/Sources/OpenFoodFacts/types/SelectedImage.swift +++ /dev/null @@ -1,11 +0,0 @@ -public class SelectedImage: Codable, ObjectDebugger { - public var display: SelectedImageItem? - public var small: SelectedImageItem? - public var thumb: SelectedImageItem? - - public init(display: SelectedImageItem?, small: SelectedImageItem?, thumb: SelectedImageItem?) { - self.display = display - self.small = small - self.thumb = thumb - } -} diff --git a/Sources/OpenFoodFacts/types/SelectedImageItem.swift b/Sources/OpenFoodFacts/types/SelectedImageItem.swift deleted file mode 100644 index bde221b..0000000 --- a/Sources/OpenFoodFacts/types/SelectedImageItem.swift +++ /dev/null @@ -1,15 +0,0 @@ -public struct SelectedImageItem: Codable, ObjectDebugger { - public var en: String? - public var fr: String? - public var pl: String? - - public var url: String { - [en, fr, pl].compactMap { $0 }.first ?? "" - } - - public init(en: String?, fr: String?, pl: String?) { - self.en = en - self.fr = fr - self.pl = pl - } -} diff --git a/Sources/OpenFoodFacts/types/SelectedImages.swift b/Sources/OpenFoodFacts/types/SelectedImages.swift deleted file mode 100644 index df3f52f..0000000 --- a/Sources/OpenFoodFacts/types/SelectedImages.swift +++ /dev/null @@ -1,11 +0,0 @@ -public struct SelectedImages: Codable, ObjectDebugger { - public var front: SelectedImage? - public var ingredients: SelectedImage? - public var nutrition: SelectedImage? - - public init(front: SelectedImage?, ingredients: SelectedImage?, nutrition: SelectedImage?) { - self.front = front - self.ingredients = ingredients - self.nutrition = nutrition - } -} diff --git a/Sources/OpenFoodFacts/types/Source.swift b/Sources/OpenFoodFacts/types/Source.swift deleted file mode 100644 index a20ca3b..0000000 --- a/Sources/OpenFoodFacts/types/Source.swift +++ /dev/null @@ -1,19 +0,0 @@ -public struct Source: Codable, ObjectDebugger { - public let fields: [String] = [] - public let id: String? = nil - public let images: [String] = [] - public let importT: Int = 0 - public let manufacturer: String? = nil - public let name: String? = nil - public let url: String? = nil - - private enum CodingKeys: String, CodingKey { - case fields - case id - case images - case importT = "import_t" - case manufacturer - case name - case url - } -}