Wednesday, August 8, 2018

Building an Oxford API Client: Subclassing the OxfordAPIRequest class

In the previous section, we defined the OxfordAPIRequest base class, whose method, getURLString, will be overridden in its subclasses so as to generate URL strings that will be tailored to different API endpoints.
To that end, let’s define a new class, OxfordWordlistAPIRequest, which inherits from the OxfordAPIRequest base class.  We will have to provide a unique initializer since the WordList API endpoint does not require a query word as a target parameter.  Rather, this endpoint returns a list of words for a given source language, a given language register, a given language domain, or a given lexical category.  The custom initializer will therefore take as arguments these different query parameters. At the same time, it will have to call the base class initializer, for which it will pass in an empty string as an argument (for the queryWord parameter) along with all the filters that will be used to filter the list of words returned as JSON data.  In addition, the endpoint must be set to the ‘wordlist’ enum case.
 
    
    init(forSourceLanguage sourceLanguage: OxfordAPILanguage, forDomainFilters domainFilters: [OxfordDomain], forRegionFilters regionFilters: [OxfordRegion], forRegisterFilters registerFilters: [OxfordLanguageRegister], forLexicalCategoryFilters lexicalCategoryFilters: [OxfordLexicalCategory]){
        
        let domainFilterStrings = domainFilters.map({$0.rawValue})
        let regionFilterStrings = regionFilters.map({$0.rawValue})
        let registerFilterStrings = registerFilters.map({$0.rawValue})
        let lexicalCategoryStrings = lexicalCategoryFilters.map({$0.rawValue})
        
        let mDomainFilters = domainFilterStrings.isEmpty ? [] : [OxfordAPIEndpoint.OxfordAPIFilter.domains(domainFilterStrings)]
        
        let mRegionFilters = regionFilterStrings.isEmpty ? [] : [OxfordAPIEndpoint.OxfordAPIFilter.regions(regionFilterStrings)]
        
        let mRegisterFilters = registerFilterStrings.isEmpty ? [] : [OxfordAPIEndpoint.OxfordAPIFilter.registers(registerFilterStrings)]
        
        let mLexCategoryFilters = lexicalCategoryStrings.isEmpty ? [] : [OxfordAPIEndpoint.OxfordAPIFilter.lexicalCategory(lexicalCategoryStrings)]
        
        let allFilters = mDomainFilters + mRegionFilters + mRegisterFilters + mLexCategoryFilters
        
        super.init(withQueryWord: String(), forRegions: nil, forLanguage: .English, withFilterForDictionaryEntryLookup: nil, withQueryFilters: allFilters)!
        
        self.endpoint = .wordlist
        
        
    }
Having defined this initializer, we can go on to override the getURLStringmethod in such a way that it will produce a URL string appropriate to this endpoint:
override func getURLString() -> String {
        
        var urlStr = getURLStringFromAppendingEndpointSpecifier(relativeToURLString: baseURLString)
        
        urlStr = getURLStringFromAppendingLanguageSpecifier(relativeToURLString: urlStr)
        
        
        if let allFilters = self.queryFilters, allFilters.count > 0{
            
            addFilters(filters: allFilters, toURLString: &urlStr)
            
        } else {
            
            urlStr.removeLast()
        }
        
    
        
        return urlStr
    }
You will notice that our implementation of the getURLString method calls many of the URL string-building helper methods that we defined in the base class.  Basically, the URL string for the WordList API endpoint starts with the baseURLString, to which we add the string for the endpoint parameter, then the string for language parameter, and finally the strings for the different filter query parameters for which we want to get a list of words.
Let’s define another derived class, OxfordSentencesAPIRequest, which will likewise have its own custom initializer along with its own implementation of the getURLString method:
class OxfordSentencesAPIRequest: OxfordAPIRequest{
    
    
   init(withQueryWord queryWord: String) {
    
        super.init(withQueryWord: queryWord, forRegions: nil, forLanguage: .English, withFilterForDictionaryEntryLookup: nil, withQueryFilters: nil)!
    
    }
    
    override func getURLString() -> String {
        
        var urlString = self.baseURLString
        
        
        urlString = getURLStringFromAppendingEndpointSpecifier(relativeToURLString: urlString)

        urlString = getURLStringFromAppendingLanguageSpecifier(relativeToURLString: urlString)
        
        urlString = getURLStringFromAppendingQueryWord(relativeToURLString: urlString)
        
        return urlString.appending("sentences")
        
    }
}


The initializer is simple enough – it only takes the query word as a parameter, which is all that is required since this API endpoint returns a list of sentences corresponding to a particular word. The getURLString method starts with the baseURLString, adds the string for the ‘entries’ endpoint parameter, then adds the string for the language specifier, then adds the string for the query word, and finally appends “sentences” to indicate that this is a call to the Sentences API endpoint.
Let’s continue by defining another derived class that inherits from the OxfordAPIRequest class.  This time will define a subclass named OxfordThesaurusAPIRequest, which will generate an API request that can connect to the Oxford Thesaurus API.  The custom initializer provided for this subclass as well as the overridden version of getURLString are shown below:
class OxfordThesaurusAPIRequest: OxfordAPIRequest{
    
    /** Additional stored properties for making request to the Thesaurus API **/
    
    private var hasRequestedSynonyms: Bool = false
    private var hasRequestAntonyms: Bool = false
    
    init(withWord queryWord: String, isAntonymRequest: Bool, isSynonymRequest: Bool, forLanguage queryLanguage: OxfordAPILanguage = .English){
        
        super.init(withQueryWord: queryWord, forRegions: nil, forLanguage: .English, withFilterForDictionaryEntryLookup: nil, withQueryFilters: nil)!
        
        self.hasRequestAntonyms = isAntonymRequest
        self.hasRequestedSynonyms = isSynonymRequest
        
        
    }
    /** Appends the Thesaurus query parameters to the ULR string; **/
    
    
    override func getURLString() -> String {
        
        var urlString = self.baseURLString
        
        urlString  = getURLStringFromAppendingEndpointSpecifier(relativeToURLString: urlString)
        
        urlString = getURLStringFromAppendingLanguageSpecifier(relativeToURLString: urlString)
        
        urlString = getURLStringFromAppendingQueryWord(relativeToURLString: urlString)
        
        urlString = getURLStringFromAppendingThesaurusQueryParameters(relativeToURLString: urlString)
        
        
        return urlString
    }
    
    private func getURLStringFromAppendingThesaurusQueryParameters(relativeToURLString urlString: String) -> String{
        
        
        if(hasRequestedSynonyms && hasRequestAntonyms){
            
            let finalURLString = urlString.appending("synonyms;antonyms")
            
            return finalURLString
            
        } else if(hasRequestedSynonyms){
            
            let finalURLString = urlString.appending("synonyms")
            
            return finalURLString
            
        } else if(hasRequestAntonyms){
            
            let finalURLString = urlString.appending("antonyms")
            
            return finalURLString
            
            /** Return synonyms by default **/
        } else {
            
            let finalURLString = urlString.appending("synonyms")
            
            return finalURLString
        
        }
        
        
        
        
    }
}


You will notice that this subclass has additional stored properties – the boolean flags hasRequestedAntonyms and hasRequestedSynonyms, which must be set in the custom initializer along with the query word whose synonyms or antonyms are being looked up.  The getURLString method then calls the helper method getURLStringFromAppendingThesaurusQueryParameters(relativeToURLString🙂 to build a query string whose thesaurus query parameters are determined based on the how the boolean flags hasRequestedAntonyms and hasRequestedSynonyms are set during initialization.
Now that we have subclassed our OxfordAPIRequest base class and created a set of derived classes whose overridden getURLString method will allow us to generate API request unique to different API endpoints, let’s get back to the task of finishing our OxfordAPIClient. Click here to continue.

No comments:

Post a Comment