Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swaggerで始めるAPI定義管理とコードジェネレート

yohei sugigami
September 15, 2017

 Swaggerで始めるAPI定義管理とコードジェネレート

iOSDC 2017
2017/09/15@早稲田大学理工学部西早稲田キャンパス 63号館

Yohei Suginami ( @susieyy )

yohei sugigami

September 15, 2017
Tweet

More Decks by yohei sugigami

Other Decks in Technology

Transcript

  1. SwaggerͰ࢝ΊΔAPIఆٛ؅ཧͱ
    ίʔυδΣωϨʔτ
    ɹ
    iOSDC 2017
    2017/09/15@ૣҴాେֶཧ޻ֶ෦੢ૣҴాΩϟϯύε 63߸ؗ
    Yohei Suginami ( @susieyy )

    View Slide

  2. Profile
    — Yohei Sugigami
    — susieyy
    — Twitter / Qiita / Github
    — New App Development
    Specialization
    — Clients
    — Folio.inc
    — New app developer
    — Wantedly.inc
    — Technical advisor

    View Slide

  3. Agenda
    — ͳͥAPIఆٛΛ໌จԽ͢Δͷ͔
    — Swaggerͷ঺հ
    — ͳͥίʔυδΣωϨʔτΛ͢Δͷ͔
    — Swagger Codegenͷ঺հ
    — δΣωϨʔτ͞ΕͨSwiftίʔυ
    — ·ͱΊ

    View Slide

  4. ͳͥAPIఆٛΛ໌จԽ͢Δͷ͔
    — ΫϥΠΞϯτͱαʔόͷΤϯδχΞ૒ํ͕ᴥᴪͳ͘ڞ௨ೝ
    ࣝΛ߹ΘͤΒΕɺίϥϘϨʔγϣϯ͕ԁ׈ʹͳΔ
    — ஋ͷܕ΍ɺnullͷ༗ແ΋໌ࣔԽͰ͖Δ(҉໧ͷϧʔϧͷഉআ)
    — APIఆٛΛ͔ͬ͠Γ࣮ࢪ্ͨ͠Ͱɺ࣮૷ʹऔΓֻ͔Δͱख໭
    Γ͕গͳ͍

    View Slide

  5. Swagger
    — The World's Most Popular Framework for APIs
    — MicrosoftɺGoogleɺIBMΒ͕RSETful APIͷఆٛهड़ͷඪ
    ४ԽΛ໨ࢦ͢Open API InitiativeΛ্ཱͪ͛ɺπʔϧʹ
    SwaggerΛ࠾༻
    — 2011೥ΑΓϓϩδΣΫτ͕ελʔτ
    — ఆٛ͸1ͭͷϑΝΠϧʹू໿͞ΕΔ (GitͰߏ੒؅ཧ͕༰қ)

    View Slide

  6. Swagger terminology
    Key Explanation
    Swagger RSETful APIΛఆٛ͢Δ࢓༷ͱͦΕʹؔ࿈͢Δπʔϧ܈
    ͷ૯শ
    Swagger Spec Swagger࢓༷ʹ४ͨ͡ɺRESTful APIΠϯλʔϑΣΠε
    Λهड़͢ΔͨΊͷϑΥʔϚοτ(YAML/JSON)ɺϞσϧ
    ఆٛ͸JSON Schemaͷαϒηοτ
    Swagger Editor SpecϑΝΠϧΛฤू͢ΔΤσΟλʔɺϦΞϧλΠϜߏ
    จνΣοΫɺఆٛՄࢹԽ
    Swagger UI SpecϑΝΠϧ͔ΒAPIυΩϡϝϯτΛੜ੒͢Δπʔϧ
    Swagger Mock Server SpecϑΝΠϧ͔ΒϞοΫαʔόΛੜ੒
    Swagger Codegen SpecϑΝΠϧ͔ΒΫϥΠΞϯτίʔυΛग़ྗ͢ΔίϚ
    ϯυϥΠϯ

    View Slide

  7. Swagger Editor
    ɹ
    ɹ
    ˰ OPEN SOURCE WEB EDITOR

    View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. Swagger UI / Mock Server
    ɹ
    ɹ
    Spec ˰ Documents & Mock API Server

    View Slide

  14. ͳͥίʔυδΣωϨʔτΛ͢Δͷ͔
    ઃܭͱίʔυͷဃ཭๷ࢭ͍ͨ͠
    — ௨৴࣌ʢૄ௨࣌ʣʹαʔόͱΫϥΠΞϯτؒͷ࣮૷ᴥᴪΑ
    Δ՝୊ൃੜΛ཈ࢭ͍ͨ͠
    — ࣮૷޻਺։ൃͷεϐʔυͷ޲্
    — ਓҝతͳϛεͷ཈ࢭʹΑΔ඼࣭ͷ޲্

    View Slide

  15. Swagger Codegen
    Πϯετʔϧ
    $ brew install swagger-codegen
    $ swagger-codegen version
    2.2.3
    2017೥9݄15೔ݱࡏ v2.2.3 ͕࠷৽

    View Slide

  16. Swagger Codegen
    Swift4, KotlinͳͲ৽͍͠ݴޠʹ΋ੵۃతʹରԠʂ
    $ swagger-codegen
    Available languages: swi!4, swi!3, swi!, kotlin, Java
    akka-scala, android, apache2, apex, aspnet5, aspnetcore,
    async-scala, bash, csharp, clojure, cwiki, cpprest,
    CsharpDotNet2, dart, elixir, eiffel, erlang-server, finch,
    flash, python-flask, go, go-server, groovy, haskell, jmeter,
    jaxrs-cxf-client, jaxrs-cxf, java, inflector, jaxrs-cxf-cdi,
    jaxrs-spec, jaxrs, msf4j, java-play-framework, jaxrs-

    View Slide

  17. Swagger Codegen Swi!ͷରԠঢ়گ
    — Swift4ʹઈࢍରԠத
    — JSONͷύʔε͸CodableͰ࣮૷
    — Optionalͷఆٛʹ΋ରԠ
    — EnumରԠ
    — API Client෦෼͸Alamofire͕ϕʔε
    — CocoaPodsͷϥΠϒϥϦͱͯ͠ग़ྗͰ͖Δ
    — RxSwiftͱ΋࿈ܞՄೳʢ Ϩεϙϯε͕ObservableʹͳΔ ʣ

    View Slide

  18. Swagger Codegen ࣮ߦํ๏
    #!/bin/sh
    # PodͷόʔδϣϯΛΠϯΫϦϝϯτͰ͖Δͱศར
    now_version=`cat version-swift`
    next_version=`semver bump patch $now_version`
    # unwrapRequired=trueʹ͢ΔͱRequiredͰఆٛͨ͠ϓϩύςΟ͸NonOptionalʹͳΔ
    URL=https://github.com/susieyy/petstore-api-clinet-swift4
    swagger-codegen generate \
    -i swagger.yaml \
    -l swift4 \
    -o ./iOS-API-CLIENT \
    --additional-properties projectName='Petstore' \
    --additional-properties unwrapRequired=true \
    --additional-properties podVersion="$next_version" \
    --additional-properties podHomepage="$URL" \
    --additional-properties podSummary="PetstoreAPIClinet" \
    --additional-properties podSource="{ :git => '$URL'}.merge({ :tag => '$next_version' })" \
    --additional-properties responseAs='RxSwift'

    View Slide

  19. $ swagger-codegen config-help -l swift4
    CONFIG OPTIONS
    sortParamsByRequiredFlag
    Sort method arguments to place required parameters before optional parameters. (Default: true)
    ensureUniqueParams
    Whether to ensure parameter names are unique in an operation (rename parameters that are not). (Default: true)
    allowUnicodeIdentifiers
    boolean, toggles whether unicode identifiers are allowed in names or not, default is false (Default: false)
    projectName
    Project name in Xcode
    responseAs
    Optionally use libraries to manage response. Currently PromiseKit, RxSwift are available.
    unwrapRequired
    Treat 'required' properties in response as non-optional (which would crash the app if api returns null as opposed to required option specified in json schema
    podSource
    Source information used for Podspec
    podVersion
    Version used for Podspec
    podAuthors
    Authors used for Podspec
    podSocialMediaURL
    Social Media URL used for Podspec
    podDocsetURL
    Docset URL used for Podspec
    podLicense
    License used for Podspec
    podHomepage
    Homepage used for Podspec
    podSummary
    Summary used for Podspec
    podDescription
    Description used for Podspec
    podScreenshots
    Screenshots used for Podspec
    podDocumentationURL
    Documentation URL used for Podspec
    swiftUseApiNamespace
    Flag to make all the API classes inner-class of {{projectName}}API
    hideGenerationTimestamp
    hides the timestamp when files were generated (Default: true)

    View Slide

  20. View Slide

  21. # Petstore.podspec
    Pod::Spec.new do |s|
    s.name = 'Petstore'
    s.ios.deployment_target = '9.0'
    s.osx.deployment_target = '10.11'
    s.version = '0.0.1'
    s.source = {
    :git => 'https://github.com/susieyy/petstore-api-clinet-swift4'
    }.merge({ :tag => '0.0.1' })
    s.authors = 'Swagger Codegen'
    s.license = 'Proprietary'
    s.homepage = 'https://github.com/susieyy/petstore-api-clinet-swift4'
    s.summary = 'PetstoreAPIClinet'
    s.source_files = 'Petstore/Classes/Swaggers/**/*.swift'
    s.dependency 'RxSwift', '~> 3.4.1'
    s.dependency 'Alamofire', '~> 4.5'
    end
    # Podfile
    pod 'PetstoreAPIClinet', \
    git: => 'https://github.com/susieyy/petstore-api-clinet-swift4', \
    tag: => 'v0.0.1'

    View Slide

  22. // Model
    import Foundation
    open class Pet: Codable {
    public enum Status: String, Codable {
    case available = "available"
    case pending = "pending"
    case sold = "sold"
    }
    public var id: Int64?
    public var category: Category?
    public var name: String
    public var photoUrls: [String]
    public var tags: [Tag]?
    /** pet status in the store */
    public var status: Status?
    public init(id: Int64?=nil, category: Category?=nil,
    name: String, photoUrls: [String], tags: [Tag]?=nil, status: Status?=nil) {
    self.id = id
    self.category = category
    self.name = name
    self.photoUrls = photoUrls
    self.tags = tags
    self.status = status
    }
    }

    View Slide

  23. // API Client
    open class PetAPI {
    /**
    Finds Pets by status
    - parameter status: (query) Status values that need to be considered for filter
    - parameter completion: completion handler to receive the data and the error objects
    */
    open class func findPetsByStatus(status: [String], completion: @escaping ((_ data: [Pet]?,_ error: Error?) -> Void)) {
    findPetsByStatusWithRequestBuilder(status: status).execute { (response, error) -> Void in
    completion(response?.body, error);
    }
    }
    /**
    Finds Pets by status
    - parameter status: (query) Status values that need to be considered for filter
    - returns: Observable<[Pet]>
    */
    open class func findPetsByStatus(status: [String]) -> Observable<[Pet]> { // responseAs='RxSwift'ͷ৔߹
    return Observable.create { observer -> Disposable in
    findPetsByStatus(status: status) { data, error in
    if let error = error {
    observer.on(.error(error as Error))
    } else {
    observer.on(.next(data!))
    }
    observer.on(.completed)
    }
    return Disposables.create()
    }
    }

    View Slide

  24. // API Client
    open class PetAPI {
    /**
    Deletes a pet
    - DELETE /pet/{petId}
    -
    - OAuth:
    - type: oauth2
    - name: petstore_auth
    - parameter petId: (path) Pet id to delete
    - parameter apiKey: (header) (optional)
    - returns: RequestBuilder
    */
    open class func deletePetWithRequestBuilder(petId: Int64, apiKey: String? = nil) -> RequestBuilder {
    var path = "/pet/{petId}"
    path = path.replacingOccurrences(of: "{petId}", with: "\(petId)", options: .literal, range: nil)
    let URLString = PetstoreAPI.basePath + path
    let parameters: [String:Any]? = nil
    let url = NSURLComponents(string: URLString)
    let nillableHeaders: [String: Any?] = [
    "api_key": apiKey
    ]
    let headerParameters = APIHelper.rejectNilHeaders(nillableHeaders)
    let requestBuilder: RequestBuilder.Type = PetstoreAPI.requestBuilderFactory.getNonDecodableBuilder()
    return requestBuilder.init(method: "DELETE", URLString: (url?.string ?? URLString),
    parameters: parameters, isBody: false, headers: headerParameters)
    }

    View Slide

  25. Q&Aɹ
    [Q] શϦΫΤετԣஅతʹϦΫΤετ
    ϔομʔ΍ɺϕʔεύεΛΧελϚΠ
    ζͰ͖·͔͢ʁ
    ɹ
    [A] Ͱ͖·͢ (^o^)

    View Slide

  26. # APIs.swift
    open class PetstoreAPI {
    open static var basePath = "http://petstore.swagger.io/v2"
    open static var customHeaders: [String:String] = [:]
    ...
    }
    # Your code / eg.
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions: ...) {
    PetstoreAPI.basePath = "http://susieyy.com/debug"
    PetstoreAPI.customHeaders["APP_VERSION"] = "1.0.0"
    PetstoreAPI.customHeaders["APP_LANG"] = "ja"
    ...
    }
    }

    View Slide

  27. Q&Aɹ
    [Q] ίʔυδΣωϨʔτͷςϯϓϨʔ
    τ͸มߋͰ͖·͔͢ʁ
    ɹ
    [A] Ͱ͖·͢ (^o^)

    View Slide

  28. mustacheg1ݴޠͰςϯϓϨʔτ͕هड़͞Ε͍ͯΔ2
    δΣωϨʔτίϚϯυʹύϥϝʔλʔΛ௥Ճ
    $ swagger-codegen generate \
    -i swagger.yaml \
    -l swift4 \
    -t my-custom-template-dir \ #
    ! ௥Ճ
    -o ./iOS-API-CLIENT
    2 https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/
    src/main/resources/swift4/model.mustache
    1 https://mustache.github.io/

    View Slide

  29. mustacheςϯϓϨʔτͷModel͸͜Μͳײ͡
    import Foundation
    {{#description}}
    /** {{description}} */{{/description}}
    {{#isArrayModel}}
    public typealias {{classname}} = [{{arrayModelType}}]
    {{/isArrayModel}}
    {{^isArrayModel}}
    {{#isEnum}}
    public enum {{classname}}: {{dataType}}, Codable {
    {{#allowableValues}}{{#enumVars}} case {{name}} =

    View Slide

  30. ςϯϓϨʔτΛϑΥʔΫ͢Δ΄ͲͰ͸ͳ͍มߋ͸Patch͕ศར
    $ patch --verbose --no-backup-if-mismatch -p1 < diff-swift-podspec.patch
    diff --git a/iOS-API-CLIENT/API.podspec b/iOS-API-CLIENT/API.podspec
    index d713686..05ae4b3 100644
    --- a/iOS-API-CLIENT/API.podspec
    +++ b/iOS-API-CLIENT/API.podspec
    @@ -8,6 +8,6 @@ Pod::Spec.new do |s|
    s.license = 'Proprietary'
    s.homepage = 'https://github.com/susieyy/Mobile-API-Swagger'
    s.summary = 'SwaggerClientAPI'
    - s.source_files = 'API/Classes/Swaggers/**/*.swift'
    + s.source_files = 'iOS-API-CLIENT/API/Classes/Swaggers/**/*.swift'
    s.dependency 'Alamofire', '~> 4.5'
    end

    View Slide

  31. Q&Aɹ
    [Q] ̍ͭͷswagger.yamnlϑΝΠϧͰ
    ؅ཧ͢ΔͱංେԽͯ͠େมͰ͸ͳ͍Ͱ
    ͔͢ʁ
    ɹ
    [A] େมͰ͢ (^_^;)

    View Slide

  32. Multi-file SwaggerͰఆٛ(swagger.yaml)෼ׂ

    View Slide

  33. Q&Aɹ
    [Q] ͲͷΑ͏ʹఆٛͱίʔυδΣω
    ϨʔτΛӡ༻͍ͯ͠·͔͢ʁ
    ɹ
    [A] Gitͷswagger.yamlͷλάͱ
    PodspecόʔδϣϯͷҰக͕େࣄͰ͢

    View Slide

  34. Github Flow ( PR, Review, Merge and Tagging )

    View Slide

  35. Gitͷswagger.yamlͷλάͱPodspecόʔδϣϯΛҰகͤ͞
    Δ͜ͱͰ
    — δΣωϨʔτ͞ΕͨSwiftίʔυʢCocoaPods)͕ɺͲͷAPI
    ఆٛʢswagger.yamlʣΛݩʹੜ੒͞Ε͔ͨGitͷλάΛ௥͏
    ͜ͱͰಛఆͰ͖Δ
    — ΞϓϦͷϦΫΤετϔομʔʹPodspecόʔδϣϯΛ௥Ճ͠
    ͓ͯ͘ͱɺτϥϒϧγϡʔςΟϯά͠΍͍͢

    View Slide

  36. ·ͱΊ
    — iPhoneɺiOS͕ϓϩμΫτͱͯ͠੒ख़͢ΔதɺSwiftɺiOS
    ։ൃͷ࡞Γํ΋ϊ΢ϋ΢͕ίϛϡχςΟʹཷ·Γɺ੒ख़͕࢝
    ·͖͍ͬͯͯΔ
    — iOS։ൃͷࠓޙ͸ɺ։ൃޮ཰Խ΍඼࣭޲্ͷ؍఺ΛϑΥʔΧ
    ε͢Δέʔε͕૿͑ͯ͘ΔͷͰ͸
    — SwaggerͰͷAPIͷఆٛٴͼɺίʔυδΣωϨʔτ͕Ұॿʹ
    ͳΕ͹ͱئ͍ͬͯΔ

    View Slide