Bloombox/Swift

View on GitHub
Sources/Client/MenuClient.swift

Summary

Maintainability
A
35 mins
Test Coverage
/**
* Copyright 2019, Momentum Ideas, Co. All rights reserved.
* Source and object computer code contained herein is the private intellectual
* property of Momentum Ideas Co., a Delaware Corporation. Use of this
* code in source form requires permission in writing before use or the
* assembly, distribution, or publishing of derivative works, for commercial
* purposes or any other purpose, from a duly authorized officer of Momentum
* Ideas Co.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import Foundation
import SwiftGRPC


// Type Definitions
public typealias MenuRetrieveCallback = (CallResult, GetMenu.Response?) -> ()
public typealias MenuAPIContext = (partner: PartnerCode, location: LocationCode, apiKey: APIKey)


/// Enumerates code-level errors in the menu client. The errors occur before requests hit the server, and deal with
/// configuration errors in most cases.
public enum MenuClientError: Error {
  /// The partner code was not provided, and could not be resolved, before calling a method that needed it.
  case invalidPartnerCode

  /// The location code was not provided, and could not be resolved, before calling a method that needed it.
  case invalidLocationCode

  /// The API key was not provided, and could not be resolved, before calling a method that needed it.
  case invalidApiKey

  /// An unknown client-side error occurred.
  case unknown
}


/// Provides functionality for the Menu API, which supports operations related to fetching, querying, and managing, menu
/// catalog data (i.e. product content, pricing, materials information, and so on). Menus do not include inventory.
public final class MenuClient: RemoteService {
  /// Name of the Menu API, which is "menu".
  let name = "menu"

  /// Version of this service.
  let version = "v1beta1"

  // MARK: Internals

  /// Client-wide settings.
  internal let settings: Bloombox.Settings

  /// Service client.
  internal var svc: MenuService?

  /// Library-internal initializer.
  ///
  /// - Parameter settings: Client-wide settings to apply.
  internal init(settings: Bloombox.Settings) {
    self.settings = settings
  }

  /// Menu service. Retrieve an implementation of the menu service, capable of retrieving product catalog information,
  /// focused on the display/showcase of items.
  ///
  /// - Parameter apiKey: API Key to use.
  /// - Returns: Prepared Menu API service class.
  internal func service(_ apiKey: APIKey) throws -> MenuService {
    if let s = self.svc {
      return s
    }
    let svc = RPCServiceFactory<MenuService>.factory(
      forService: Transport.config.services.menu,
      withSettings: self.settings)

    try svc.metadata.add(key: "x-api-key", value: apiKey)
    self.svc = svc
    return svc
  }

  /// Resolve an API key, and partner/location context, throwing an error if it cannot be figured out. These items are
  /// required for all calls to the Menu API. Check library defaults and fall back to them if overrides are not given.
  ///
  /// - Parameter partner: Partner account code to use.
  /// - Parameter location: Location account code to use.
  /// - Parameter apiKey: API key to use with the service.
  /// - Returns: Tuple of partner, location, and API key to use.
  /// - Throws: `MenuClientError` codes if items cannot be resolved.
  private func resolveContext(_ partner: PartnerCode? = nil,
                              _ location: LocationCode? = nil,
                              _ apiKey: APIKey? = nil) throws -> MenuAPIContext {
    let partnerCode: PartnerCode? = partner ?? settings.partner
    let locationCode: LocationCode? = location ?? settings.location
    let apiKey: APIKey? = apiKey ?? settings.apiKey

    guard apiKey != nil else {
      throw MenuClientError.invalidApiKey
    }

    // validate partner and location codes
    guard partnerCode != nil, locationCode != nil else {
      // throw error: we require a partner or location code from somewhere
      if partnerCode == nil {
        throw MenuClientError.invalidPartnerCode
      }
      throw MenuClientError.invalidLocationCode
    }
    return (partner: partnerCode!, location: locationCode!, apiKey: apiKey!)
  }

  // MARK: - Public API -

  // MARK: Menu Retrieve

  /// Retrieve the active product catalog (menu) for a given partner/location. Menus are essentially sets of product
  /// catalog information, with a focus on showcasing/displaying the items. This means that items that are currently out
  /// of stock, or not available at this location, or under other similar circumstances, are withheld as results when
  /// querying via the Menu API. These items are available by passing special flags, or via the Inventory API.
  ///
  /// - Parameter partner: Partner account code to use.
  /// - Parameter location: Location account code to use.
  /// - Parameter keysOnly: Flag to indicate keys-only or full data.
  /// - Parameter apiKey: API key to identify ourselves with to the Menu API.
  /// - Returns: Materialized menu response for the subject partner/location.
  /// - Throws: Client and server-side errors, since this method is synchronous.
  public func retrieve(partner: PartnerCode? = nil,
                       location: LocationCode? = nil,
                       keysOnly: Bool = false,
                       apiKey: APIKey? = nil) throws -> GetMenu.Response {
    let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
    let service = try self.service(apiKey)

    return try service.retrieve(GetMenu.Request.with { builder in
      builder.scope = "partners/\(partnerCode)/locations/\(locationCode)"
    })
  }

  /// Retrieve the active product catalog (menu) for a given partner/location, asynchronously. Menus are essentially
  /// sets of product catalog information, with a focus on showcasing/displaying the items. This means that items that
  /// are currently out of stock, or not available at this location, or under other similar circumstances, are withheld
  /// as results when querying via the Menu API. These items are available by passing special flags, or via the
  /// Inventory API.
  ///
  /// The provided callback is dispatched once either a terminal error or RPC response is encountered. The return value
  /// may be used to cancel the call or observe its status.
  ///
  /// - Parameter partner: Partner account code to use.
  /// - Parameter location: Location account code to use.
  /// - Parameter keysOnly: Flag to indicate keys-only or full data.
  /// - Parameter apiKey: API key to identify ourselves with to the Menu API.
  /// - Parameter callback: Callable to dispatch once a result or terminal error is available.
  /// - Returns: RPC call object, which can be observed to track call status, or used to cancel the call.
  /// - Throws: Client-side errors only. Server-side errors are surfaced in the callback.
  @discardableResult
  public func retrieve(partner: PartnerCode? = nil,
                       location: LocationCode? = nil,
                       keysOnly: Bool = false,
                       apiKey: APIKey? = nil,
                       callback: @escaping MenuRetrieveCallback) throws -> GetMenuCall {
    let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
    let service = try self.service(apiKey)

    return try service.retrieve(GetMenu.Request.with { builder in
      builder.scope = "partners/\(partnerCode)/locations/\(locationCode)"
    }) { (response, callResult) in
      callback(callResult, response)
    }
  }

}