Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MOBILE-1090 Fix context extraction. Mapping for exceptions and errors occurrences and tests implementation #3

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Source/Error/ApioError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,33 @@
* @author Paulo Cruz
*/
enum ApioError : Error {
case cantParseToThing
case emptyVocab
case invalidRequestUrl
case requestFailedException(statusCode: Int, title: String, description: String?)
case thingNotFound
case thingWithoutOperation(thingId: String, operationId: String)
}

extension ApioError {
func getErrorMessage() -> String {
switch self {
case .cantParseToThing:
return "Can't parse to thing"
case .emptyVocab:
return "Empty vocab"
case .invalidRequestUrl:
return "Invalid request URL"
case .requestFailedException(_, title: let title, description: let description):
guard let description = description else {
return "\(title)"
}

return "\(title): \(description)"
case .thingNotFound:
return "Thing not found"
case .thingWithoutOperation(thingId: let thingId, operationId: let operationId):
return "Thing \(thingId) doesn't have the operation \(operationId)"
}
}
}
255 changes: 144 additions & 111 deletions Source/Parser/JsonLDParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,39 @@ class JsonLDParser {

typealias OptionalAttributes = [String: Any?]
typealias Attributes = [String: Any]
typealias Context = [Any]
typealias JsonObject = [Any]

static func contextFrom(context: Context?, parentContext: Context?) -> Context {
if let context = context, let parentContext = parentContext {
if !hasVocab(jsonObject: context) && hasVocab(jsonObject: parentContext) {
var context = context

context.append( ["@vocab": getVocab(context: parentContext) ?? ""])

return context
}
}

return context ?? parentContext ?? []
}
static func contextFrom(jsonObject: JsonObject?, parentContext: Context?) throws -> Context? {
var vocabContext: String
var attributesContext: [String : Any] = [:]

if let context = jsonObject, let vocab = getVocab(context: context), vocab != "" {
vocabContext = vocab
}
else {
guard let vocab = parentContext?.vocab, vocab != "" else {
throw ApioError.emptyVocab
}

vocabContext = vocab
}

fillProperties(jsonObject: jsonObject, attributesContext: &attributesContext)

return Context(vocab: vocabContext, attributeContext: attributesContext)
}

static func fillProperties(jsonObject: JsonObject?, attributesContext: inout [String : Any]) {
jsonObject?.forEach { property in
if let property = property as? [String : Any] {
for (key, value) in property {
if (key != "@vocab") {
attributesContext[key] = [key : value]
}
}
}
}
}

static func filterProperties(
json: OptionalAttributes, properties: [String]) -> OptionalAttributes {
Expand All @@ -59,86 +77,98 @@ class JsonLDParser {
}

static func flatten(
context: Context, foldedAttributes: inout OptionalAttributes, attributes: Attributes)
context: Context?, foldedAttributes: inout OptionalAttributes, attribute: Attributes) throws
-> OptionalAttributes {

let key = attributes.keys.first ?? ""
let value = attributes.values.first ?? ""

var attributes = foldedAttributes["attributes"] as? OptionalAttributes ?? OptionalAttributes()
var things = foldedAttributes["things"] as? OptionalAttributes ?? OptionalAttributes()

if let value = value as? OptionalAttributes {
parseObject(key: key, value: value, context: context, attributes: &attributes,
things: &things)
}
else if let value = value as? [Attributes] {
parseObjectArray(key: key, value: value, context: context, attributes: &attributes,
things: &things)
}
else if isId(name: key, context: context) {
let value = value as? String ?? ""
let relation = Relation(id: value, thing: nil)

let null: Any? = nil
things[value] = null

attributes[key] = relation
}
else {
attributes[key] = value
}

return ["attributes" : attributes, "things" : things]
var attributes = foldedAttributes["attributes"] as? Attributes ?? Attributes()
var things = foldedAttributes["things"] as? OptionalAttributes ?? OptionalAttributes()

let key = attribute.keys.first ?? ""
let value = attribute.values.first ?? ""

if let value = value as? [String : Any?] {
try parseObject(key: key, value: value, context: context, attributes: &attributes, things: &things)
}
else if let value = value as? [[String : Any]] {
try parseObjectArray(key: key, value: value, context: context, attributes: &attributes, things: &things)
}
else if isId(name: key, context: context) {
let value = value as? String ?? ""
let relation = Relation(id: value, thing: nil)

things[value] = nil

attributes[key] = relation
}
else {
attributes[key] = value
}

return ["attributes" : attributes, "things" : things]
}

static func getVocab(context: Context) -> String? {
return context.first { item in
if let item = item as? Attributes, item.keys.contains("@vocab") {
return true
}

return false
} as? String
}
static func getVocab(context: JsonObject) -> String? {
var vocabValue: String

if (hasVocab(jsonObject: context)) {
vocabValue = ""
context.forEach { attrs in
if let items = attrs as? [String : Any] {
for item in items {
if (item.key == "@vocab") {
vocabValue = item.value as? String ?? ""
}
}
}
}

return vocabValue
}

return nil
}

static func hasVocab(jsonObject: Context) -> Bool {
return getVocab(context: jsonObject) != nil
static func hasVocab(jsonObject: JsonObject) -> Bool {
var flagVocab: Bool = false
jsonObject.forEach { item in
if let item = item as? [String : Any], item.keys.contains("@vocab") {
flagVocab = true
}
}

return flagVocab
}

static func isEmbeddedThingArray(value: Context) -> Bool {
if let firstPair = value.first as? Attributes {
static func isEmbeddedThingArray(value: JsonObject) -> Bool {
if let firstPair = value.first as? [String : Any] {
return firstPair["@id"] != nil
}

return false
}

static func isId(name: String, context: Context?) -> Bool {
guard let context = context else {
return false
}

return context.first { item in
if let attrs = item as? OptionalAttributes,
let value = attrs[name] as? OptionalAttributes,
let type = value["@type"] as? String, type == "@id" {

return true
}

return false
} != nil
guard let context = context else {
return false
}

if let typeContext = context.attributeContext["@type"] as? String {
if typeContext == "@id" {
return true
}
}

return false
}

static func parseAttributes(
json: OptionalAttributes, context: Context) -> (Attributes, OptionalAttributes) {
json: OptionalAttributes, context: Context?) throws -> (Attributes, OptionalAttributes) {
let filteredJson = filterProperties(json: json, properties: ["@id", "@context", "@type"])

let result = filteredJson.keys
let result = try filteredJson.keys
.reduce(
into: ["attributes": OptionalAttributes(), "things": OptionalAttributes()], { acc, key in
acc = flatten(context: context, foldedAttributes: &acc, attributes: [key : json[key] as Any] as Attributes)
into: ["attributes": Attributes(), "things": OptionalAttributes()], { acc, key in
acc = try flatten(context: context, foldedAttributes: &acc, attribute: [key : filteredJson[key] as Any] as Attributes)
})

let attributes = result["attributes"] as? Attributes ?? [:]
Expand All @@ -148,36 +178,35 @@ class JsonLDParser {
}

static func parseObject(
key: String, value: OptionalAttributes, context: Context, attributes: inout OptionalAttributes, things: inout OptionalAttributes) {
key: String, value: OptionalAttributes, context: Context?, attributes: inout Attributes, things: inout OptionalAttributes) throws {

if (value.keys.contains("@id")) {
let (thing, embbededThings) = parseThing(json: value, parentContext: context)

attributes[key] = Relation(id: thing.id, thing: thing)

things.merge(embbededThings) { first, second in
return first
}

things[thing.id] = thing
}
else {
let (parsedAttributes, parsedEmbbededThings) = parseAttributes(json: value,
context: context)

attributes[key] = parsedAttributes

things.merge(parsedEmbbededThings) { first, second in
return first
}
}
if (value.keys.contains("@id")) {
let (thing, embbededThings) = try parseThing(json: value, parentContext: context)

attributes[key] = Relation(id: thing.id, thing: thing)

things.merge(embbededThings) { first, second in
return first
}

things[thing.id] = thing
}
else {
let (parsedAttributes, parsedEmbbededThings) = try parseAttributes(json: value, context: context)

attributes[key] = parsedAttributes

things.merge(parsedEmbbededThings) { first, second in
return first
}
}
}

static func parseObjectArray(
key: String, value: Context, context: Context, attributes: inout OptionalAttributes, things: inout OptionalAttributes) {
key: String, value: JsonObject, context: Context?, attributes: inout Attributes, things: inout OptionalAttributes) throws {

if (isEmbeddedThingArray(value: value)) {
let collection = value.map { parseThing(json: $0 as? OptionalAttributes ?? [:]) }
let collection = try value.map { try parseThing(json: $0 as? OptionalAttributes ?? [:], parentContext: context) }

var relations = [Relation]()

Expand All @@ -187,7 +216,7 @@ class JsonLDParser {
relations.append(relation)

things.merge(embbededThings) { first, second in
return second
return second
}

things[thing.id] = thing
Expand All @@ -197,7 +226,7 @@ class JsonLDParser {
}
else {
let collection =
value.map { parseAttributes(json: $0 as? OptionalAttributes ?? [:], context: context) }
try value.map { try parseAttributes(json: $0 as? OptionalAttributes ?? [:], context: context) }

var attributesList = [OptionalAttributes]()

Expand Down Expand Up @@ -231,18 +260,22 @@ class JsonLDParser {
}
}

static func parseThing(json: OptionalAttributes, parentContext: Context? = nil) -> (Thing, OptionalAttributes) {
let id = json["@id"] as? String ?? ""
let types = parseType(json["@type"] ?? nil)
let context =
contextFrom(context: json["@context"] as? Context ?? [], parentContext: parentContext)

let operations = parseOperations(json: json)
let (attributes, things) = parseAttributes(json: json, context: context)

let thing = Thing(id: id, types: types, attributes: attributes, operations: operations)
static func parseThing(json: OptionalAttributes, parentContext: Context? = nil) throws -> (Thing, OptionalAttributes) {
do {
let id = json["@id"] as? String ?? ""
let types = parseType(json["@type"] ?? nil)
let context =
try contextFrom(jsonObject: json["@context"] as? JsonObject ?? [], parentContext: parentContext)

let operations = parseOperations(json: json)
let (attributes, things) = try parseAttributes(json: json, context: context)

let thing = Thing(id: id, types: types, attributes: attributes, operations: operations)

return (thing, things)
return (thing, things)
} catch {
throw ApioError.cantParseToThing
}
}

static func parseType(_ type: Any?) -> [String] {
Expand Down
Loading