스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift OpenAPI 생성기 알아보기
iOS 앱을 확장하거나 Swift로 서버를 작성할 때, Swift OpenAPI 생성기가 HTTP 서버 API 작업을 하는 데 어떻게 도움이 되는지 알아보세요. 이 패키지 플러그인으로 OpenAPI 문서에서 코드를 생성함으로써 워크플로를 간결하게 만들고 코드베이스를 단순화하는 법을 보여드리겠습니다.
챕터
- 0:44 - Considerations when making API calls
- 1:52 - Meet OpenAPI
- 6:15 - Making API calls from your app
- 12:33 - Adapting as the API evolves
- 14:23 - Testing your app with mocks
- 16:12 - Server development in Swift
- 19:24 - Adding a new operation
리소스
- Swift OpenAPI Generator package plugin
- Swift OpenAPI Generator Runtime
- URLSession Transport for Swift OpenAPI Generator
관련 비디오
WWDC22
-
다운로드
♪ ♪
전 Apple의 Swift on Server 팀의 사이라고 합니다 오늘 이 영상에서는 Swift OpenAPI 생성기를 서버 API 작업에 활용하는 법을 보여드리겠습니다 iOS 앱을 확장할 때도 Swift에서 백엔드 서버를 작성할 때도 Swift의 이 새 패키지 플러그인은 워크플로를 간소화하고 코드베이스를 단순화할 수 있습니다 올해 보셨다시피, 기기에서 데이터 작업을 하기는 한결 쉬워졌습니다 하지만 때로는 여러분이 구현하고자 하는 기능에 서버 컴포넌트가 제공하는 동적 콘텐츠가 필요하기도 합니다 이는 원격 서비스에 네트워크 요청을 하여 서비스 API를 호출해야 한다는 뜻입니다 하지만 네트워크 요청을 적절하게 하려면 고려해야 할 점이 많습니다 서버의 베이스 URL은 무엇인가요? 어떤 경로 컴포넌트가 API 엔드포인트를 구성하나요? 어떤 HTTP 메서드를 써야 할까요? 그리고 매개변수는 어떻게 제공해야 할까요? 서버 API를 호출할 때 고려해야 할 질문은 이것 외에도 많은데요 API의 관여 정도가 높을수록 고려해야 할 것도 많습니다 그러면 이 질문에는 어떻게 답해야 할까요?
대부분의 서비스에는 어떤 형태로든 API 문서가 있지만 손으로 쓴 문서는 부정확하거나 너무 뒤떨어졌을 수 있습니다 서비스가 급격하게 발전한다면 특히 더 그렇겠죠 소스 코드를 얻을 수 있다면 구현이 어떻게 됐는지 살펴보거나 API를 가지고 수동으로 실험해 볼 수 있기는 합니다 하지만 그러면 서비스 동작을 완전하게 이해할 수는 없죠 지원 포럼을 검색하거나 다른 기관에서 지식을 얻어도 되기는 하지만 아무리 의도가 좋은 사람들이라도 정보가 충분하지 못하거나 답변에 일관성이 없어서 오히려 여러분을 더 혼란하게 할 수도 있습니다 이런 자료는 다소 도움이 되지만 전체 그림을 보여주지는 못합니다 형식과 구조를 더 잘 갖춘 API 설명을 활용하면 모호함을 없애는 데 도움이 됩니다 지금 소개할 OpenAPI는 HTTP 서비스를 정의하는 공개 사양입니다
OpenAPI는 업계 표준이며 OpenAPI가 널리 도입되고 성숙함에 따라 API를 사용하는 데 도움이 되는 규칙과 모범 사례가 확립되어 있습니다 OpenAPI를 사용하면 서비스 동작이 YAML이나 JSON으로 문서화되는데 기계로 읽을 수 있는 이런 형식을 이용하면 풍부한 도구 생태계의 도움을 받을 수 있습니다 테스트 생성 도구 런타임 검증 도구 상호운용성 도구 등등 아주 많은 도구가 있죠 OpenAPI는 상호 작용형 문서를 생성하는 도구를 갖춘 것으로 특히 유명합니다 하지만 OpenAPI의 핵심 동기는 코드 생성입니다 코드 생성을 채택함으로써 사양 중심 개발을 할 수 있죠 예시로 들었던 API 엔드포인트 기억하시죠? 이 요청을 받으면 서버는 개인화된 인사말을 JSON 객체로 반환합니다 코드 생성을 사용하지 않고 이 API를 호출하기 위해 작성해야 하는 코드를 봅시다
먼저 서버의 베이스 URL을 알아야 서버의 컴포넌트로 변환할 수 있습니다 그런 뒤 경로 컴포넌트를 추가하여 API 엔드포인트를 구성하고 매개변수를 쿼리 항목으로 명시합니다 그다음 URLRequest를 구성하고 URLSession을 사용하여 HTTP 요청을 만듭니다
그다음 예상한 유형의 반응에 예상한 상태 코드와 콘텐츠 유형이 있는지 확인해야 합니다
다음으로는 응답에서 바이트를 디코딩해야 하는데 그러려면 Decodable을 준수하는 Swift 유형을 정의하고 JSONDecoder를 사용합니다 마지막으로 응답으로부터 메시지 프로퍼티를 반환합니다
이런 코드를 작성해도 되지만 이건 사소한 API 연산에 대한 요청 한 개에 지나지 않았죠 현실 세계의 API에는 수백 가지 연산이 있고 요청 및 응답 유형, 헤더 필드 매개변수 등도 다양하게 있습니다 연산마다 이런 코드를 작성하면 작업이 반복적이고 장황한 데다 오류가 나기도 쉽습니다 그리고 코드베이스에 잡다한 일거리가 많아지면 앱의 핵심 로직이 손상됩니다
OpenAPI를 사용하면 이런 코드는 대부분 도구로 생성할 수 있으므로 여러분은 사용자가 상호 작용하는 코드에만 집중할 수 있습니다 예제 API를 이용해 OpenAPI 문서를 살펴보겠습니다 모든 OpenAPI 문서는 문서가 사용하는 OpenAPI Specification의 버전을 밝힙니다 문서는 API의 메타데이터를 제공하는데 여기에는 이름과 버전 서버 URL의 목록이 포함됩니다 그다음 API를 구성하는 경로와 HTTP 메서드를 나열합니다 이 API에 연산은 getGreeting 하나밖에 없는데 이 연산은 greet 경로에 있는 get 메서드의 동작을 정의합니다 이 예제에서 서버는 항상 200이라고 응답하는데 이건 'OK'를 나타내는 HTTP 상태 코드입니다 그리고 JSON 객체를 반환하는데 이 객체는 JSON 스키마를 사용해 정의합니다
예제는 단순하게 만들었지만 연산에는 응답이 여럿 올 수 있고 응답의 상태 코드와 콘텐츠 유형도 다양할 수 있으므로 모든 시나리오를 문서화할 수 있습니다 오류가 발생하는 시나리오도 포함해서요 그리고 연산이 매개변수를 받아들인다면 매개변수도 OpenAPI 문서에 포함할 수 있습니다
이 연산이 지원하는 선택적 쿼리 매개변수인 name이라는 문자열값은 인사말을 개인화하는 데 쓰입니다
Swift OpenAPI 생성기의 도움을 받으면 훨씬 적은 코드로 같은 API 호출을 할 수 있습니다
유형 안전성이 있는 입력을 할 수 있고 출력값은 다양한 열거형 유형이므로 컴파일러는 문서화된 모든 응답과 콘텐츠 유형이 확실하게 처리되도록 합니다 또한 응답 본문에 있는 연결된 값은 유형 안전성이 있는 프로퍼티를 갖춘 값 유형입니다 입력을 인코딩하고 요청을 하고 응답을 파싱하고 출력을 디코딩하는 데 따르는 모든 잡다한 일거리를 생성된 코드가 전부 처리합니다
Swift OpenAPI 생성기는 Swift 패키지 플러그인으로 빌드 타임에 실행됩니다 즉, 생성된 코드는 OpenAPI 문서와 항상 동기화되어 있기에 소스 리포지토리에 커밋될 필요가 없습니다 Swift 패키지 플러그인에 대해 더 자세히 알고 싶다면 'Swift 패키지 플러그인 알아보기' 세션을 참고하세요 Swift OpenAPI 생성기를 단순한 iOS 앱에서 사용하는 법을 알아봅시다 그러려면 호출할 수 있는 API가 필요한데요 이 예제에서 호출할 단순한 API는 고양이 이모티콘 열 개 중 하나를 임의로 반환합니다 일단 SwiftUI 템플릿 앱으로 시작해 샘플 콘텐츠 대신 커다란 이모티콘을 넣고 누를 때마다 서버에서 새 이모티콘을 가져오는 버튼도 넣습니다 이미 로컬호스트에서 대기 중인 서버가 실행 중이며 터미널에서 curl을 사용하면 쿼리할 수 있습니다
이건 분명 잘 만든 API입니다만 OpenAPI를 사용해 정의했기 때문에 더 나은 API가 될 수 있는 겁니다 이번에는 아주 다른 고양이로 이 서비스의 OpenAPI 문서를 나타내겠습니다
이 API에 있는 연산은 getEmoji 하나뿐인데 이걸 앱에서 호출해 UI를 업데이트하겠습니다 먼저 Xcode로 넘어가겠습니다
이 예제 iOS앱의 UI는 SwiftUI로 정의한 기본 UI로 Xcode Preview에서 볼 수 있습니다 앞으로 몇 분 동안 여기에 UI 컴포넌트 대신 서버에서 가져온 동적 콘텐츠를 넣고 Swift OpenAPI 생성기를 이용해 원래는 손수 써야 하는 API를 호출 코드를 단순화하겠습니다 먼저 프로젝트에 필수적인 패키지 종속성을 추가한 다음 코드 생성에 플러그인을 사용하도록 타깃을 구성하고 OpenAPI 문서와 플러그인 config 파일을 타깃 소스 디렉토리에 추가합니다 프로젝트 구성이 끝나면 UI 컴포넌트를 대체하고 생성된 클라이언트 유형을 사용해 서버에 API 호출을 합니다 앱이 Swift OpenAPI 생성기를 사용하도록 구성하려면 프로젝트 에디터로 가서 '패키지 종속성' 탭을 선택하고 클릭해서 새 종속성을 추가하면 됩니다
이 예제에서는 로컬 패키지 컬렉션을 사용했지만 세션 노트를 보시면 패키지 URL도 있습니다 먼저, 패키지 플러그인을 제공하는 swift-openapi-generator에 종속성을 추가하겠습니다
그다음, swift-openapi-runtime에 종속성을 추가합니다 swift-openapi-runtime은 생성된 코드에서 사용하는 공통 유형과 추상화를 제공하죠
생성된 코드는 특정 HTTP 클라이언트 라이브러리에 매여 있지 않으므로 우리가 사용하고 싶은 라이브러리에 해당하는 통합 패키지를 선택해야 합니다 우리는 iOS 앱을 만드니 URLSession 패키지를 사용하겠지만 다른 예제의 문서와 직접 패키지를 작성하는 법에 관한 문서도 확인하시기를 바랍니다 종속성을 전부 추가하고 나면 OpenAPI 생성기 플러그인을 사용할 타깃을 구성할 수 있습니다 '타깃 설정'에서 Build Phases를 선택하고 Run build-tool plugins 섹션을 확장하세요 클릭해서 새 플러그인을 추가하고 목록에서 'OpenAPI 생성기'를 선택하세요
이 플러그인은 타깃 소스 디렉토리에 입력 파일이 두 개 있으리라 예상하는데 바로 OpenAPI 문서와 플러그인 config 파일입니다 지금 프로젝트에 추가하죠
플러그인 구성은 단순한 YAML 스키마를 사용해 작성했는데 이 스키마는 플러그인이 생성해야 할 코드를 지정합니다 이 예제에서는 types를 생성할 텐데 이건 OpenAPI 문서에서 도출한 재사용 가능한 유형입니다 클라이언트 코드도 생성할 텐데 이걸 사용하면 어느 HTTP 클라이언트로든 API 호출을 할 수 있습니다 ContentView.swift로 돌아오면 프로젝트가 재컴파일되고 생성된 코드를 앱에서 사용할 준비가 끝납니다
이건 보안 조치인데 플러그인을 처음 사용할 때는 플러그인을 신뢰하라는 요청이 뜰 겁니다
프로젝트를 재컴파일했으니 이제는 UI 컴포넌트를 대체하고 생성된 클라이언트 유형을 이용해 서버에 API 호출을 하고 뷰를 업데이트할 수 있습니다
먼저 이모티콘 뷰에 새 상태 프로퍼티를 추가하고 플레이스홀더값으로 프로퍼티를 초기화하겠습니다 그다음 지구본 이미지를 이모티콘이 든 Text 뷰로 대체하고 'Hello, world' 메시지를 버튼으로 대체하고 뷰에 들어갈 버튼 스타일을 설정합니다
생성된 코드는 Client라는 이름의 유형을 제공하는데 이걸 이용해 API 호출을 할 수 있습니다 하지만 그 전에 OpenAPI 런타임과 전송 모듈을 가져와야 합니다
그러면 클라이언트 프로퍼티를 뷰에 추가할 수 있고 OpenAPI 문서에 정의된 로컬호스트 서버 URL을 사용하도록 구성된 이니셜라이저도 추가할 수 있습니다
이제 이 클라이언트로 서버에 API 호출을 하는 함수를 추가하도록 하겠습니다
API 요청을 하기 위해 직접 작성해야 할 코드는 이게 전부입니다 나머지는 생성된 코드가 알아서 처리하죠 응답은 문서화된 모든 응답과 콘텐츠 유형을 모델링하는 유형의 열거형 값이며 모든 시나리오를 처리할 수 있게 돕습니다 그래서 switch문을 이용해 응답 본문으로부터 이모티콘을 추출해야 합니다
뭔가가 빠졌군요 컴파일러 말로는 우리가 모든 시나리오를 처리하지는 못했답니다 Xcode가 빠진 switch case를 채워 넣게 하겠습니다
혹시 서버가 OpenAPI 문서에 지정되지 않은 무언가로 응답한다고 하더라도 우아하게 처리할 기회는 있습니다 이 예제에서는 콘솔에 대해 경고를 출력하고 이모티콘을 고양이 외의 다른 것으로 업데이트하겠습니다
이제 버튼을 탭하면 이 함수를 호출할 수 있습니다
그리고 버튼으로 새 고양이 이모티콘을 가져와 UI를 업데이트할 수 있고요
서버에 새 기능이 추가될수록 서버 API도 발전합니다 그리고 서버를 OpenAPI로 문서화하면 Swift OpenAPI 생성기는 새 기능을 앱에서 더 쉽게 사용할 수 있게 해 줍니다 OpenAPI 문서가 발전함에 따라 앱을 업데이트하는 방법을 예제로 보여드리겠습니다
이모티콘은 많을수록 좋으므로 우리는 서비스 API를 확장해 API가 새 선택적 쿼리 매개변수인 count를 써서 이모티콘 여러 개를 가져올 수 있게 했습니다
고양이 하나 대신 셋을 가져오는 버튼도 추가하여 앱을 확장하겠습니다
먼저 OpenAPI 문서에 매개변수를 추가하겠습니다 프로젝트를 재컴파일하면 매개변수를 앱에서 쓸 수 있게 됩니다 그다음, 이 매개변수를 사용해 API 호출을 하는 버튼을 새로 만들겠습니다 일단 OpenAPI 문서에 새 매개변수를 추가합니다
이 매개변수는 이름이 count이고 선택적 매개변수입니다 이 매개변수는 URL 쿼리의 일부로서 제공되는 정숫값입니다 ContentView.swift로 되돌아가서 updateEmoji 함수를 확장해 매개변수를 받아들이게 하겠습니다
API 호출을 할 때 이 매개변수를 사용하도록 합시다
버튼을 복제하고 레이블을 'More cats'로 바꿉니다
이 버튼을 탭하면 호출되는 함수는 같지만 이번에는 개수가 3이 됩니다
이제 미리보기에서 'Get cat'을 눌러 고양이 한 마리를 불러오거나 'More cats'를 눌러 세 마리를 불러올 수 있습니다 지금까지 우리는 실제 서버에 요청을 보냈는데 이렇게 하는 건 늘 실용적이지도 바람직하지도 않습니다 개발 과정에서는 특히 더 그렇죠 생성된 클라이언트 유형은 Swift 프로토콜을 준수하므로 네트워크 연결이나 전송 라이브러리가 필요 없는 가짜를 쉽게 작성할 수 있습니다 생성된 프로토콜의 이름은 APIProtocol이므로 먼저 이 프로토콜을 채택한 MockClient 유형을 새로 정의합니다 그다음 뷰를 업데이트해서 APIProtocol을 준수하는 모든 유형에 대해 제네릭하게 만들고 이니셜라이저를 업데이트하여 종속성 주입을 지원하게 합니다 그다음 MockClient를 사용해 Xcode에서 UI를 미리보기합니다 먼저 MockClient 유형부터 선언하도록 하겠습니다
우리는 이 유형이 APIProtocol을 채택한다고 선언했으므로 컴파일러는 이 유형이 프로토콜의 요건을 충족하는지 확인할 것입니다 빠진 핸들러는 Xcode가 API 연산에 추가하게 합니다
그다음 비즈니스 로직을 추가해 로봇 이모티콘을 반환하게 합니다 실제 서비스와 구분하기 위해서죠
이제 이 프로토콜을 준수하는 유형에 대해 뷰를 제네릭하게 만들고 클라이언트 프로퍼티를 업데이트하여 제네릭 유형 매개변수를 사용하게 할 수 있습니다
클라이언트를 매개변수로 쓰는 이니셜라이저를 추가하고 기존의 이니셜라이저를 제네릭한 where 절로 업데이트하겠습니다 그래야 클라이언트가 제공되지 않을 때 전과 같은 것을 사용할 수 있으니까요
앱을 실행하면 앱은 계속 진짜 서버를 사용하지만 UI를 Xcode에서 미리보기할 때는 MockClient를 주입할 수 있습니다
이제 UI 미리보기에서 버튼을 탭하면 고양이 대신 로봇이 나오고, 네트워크 연결도 작동하는 서버도 필요 없어집니다
가짜 클라이언트를 추가하기 전에 우리의 iOS 앱은 제 로컬 기계에서 돌아가는 실제 서버에 요청을 보내고 있었는데 이 서버도 Swift OpenAPI 생성기의 도움을 받아 Swift로 작성됐습니다 이 서버는 단순한 Swift 패키지로 Swift OpenAPI 생성기의 패키지 플러그인을 사용해 코드를 생성합니다 우리는 생성된 서버 코드를 사용하려고 APIProtocol이라는 이름으로 생성된 프로토콜을 준수하는 유형을 정의했고 API 연산에 비즈니스 로직만을 구현했습니다 그리고 우리는 서버를 구성하려고 registerHandlers라는 생성된 함수를 사용했는데 이 함수는 API 연산에 대해 수신되는 HTTP 요청을 비즈니스 로직을 제공하는 핸들러에 연결합니다 살펴봅시다
콘솔을 확장하면 예제 iOS 앱에서 실제로 보내는 요청을 볼 수 있습니다
서버를 구현하기 위해 직접 작성해야 하는 Swift 코드는 이게 전부입니다 우리는 서비스를 문서화하는 데만 OpenAPI를 사용하는 대신 OpenAPI 문서에서 시작해서 Swift OpenAPI 생성기를 이용해 API 사양을 구현하는 서버 작성을 단순화했습니다
생성된 APIProtocol을 준수하고 API 연산에 필요한 비즈니스 로직만을 제공하는 유형을 정의했습니다 그리고 생성된 함수를 사용해 API 엔드포인트에 대한 메서드를 HTTP 서버에 등록했습니다 이 예제에서는 Swift용 오픈 소스 웹 프레임워크인 Vapor를 사용했지만 생성된 코드는 Swift OpenAPI 생성기 통합 패키지를 제공하는 모든 웹 프레임워크에서 사용할 수 있습니다 문서를 참고하시면 다른 선택지로는 무엇이 있는지 어떻게 여러분이 직접 작성할 수 있는지 알 수 있습니다
주 함수에 새 Vapor Application을 만들어서 그걸 사용해 OpenAPI 전송을 만듭니다 그다음 우리의 핸들러 유형의 인스턴스를 만들고 생성된 registerHandlers 함수를 사용해 API 연산 각각에 대해 HTTP 서버 안에서 라우팅을 설정합니다 이렇게 하지 않으면 수동으로 설정할 수밖에 없죠 마지막으로 Vapor 앱을 실행합니다 앱을 수동으로 구성했을 때와 똑같은 방식으로요
Swift는 서버를 개발하기에 아주 좋은 언어이니 Swift로 백엔드 서비스를 작성하는 법을 더 잘 알고 싶다면 '서버 측 개발에 Xcode 사용하기' 세션을 보시기 바랍니다
Swift OpenAPI 생성기를 사용하기 위해 패키지를 어떻게 구성하는지 살펴봅시다 이 서버는 Swift 패키지로서 구현되고 Package.swift 파일을 이용해 정의됩니다 이 패키지에는 실행 가능한 타깃이 단 하나뿐인데 CatService라는 이 타깃은 Swift OpenAPI 생성기 플러그인을 활용합니다 생성된 서버 코드는 런타임 라이브러리의 공통 유형 및 추상화에 좌우되며 통합 패키지를 제공하는 모든 웹 프레임워크에서 사용할 수 있으므로 이 타깃은 swift-openapi-runtime과 swift-openapi-vapor 그리고 Vapor 자체에 종속성이 있습니다 우리는 타깃 소스 디렉토리에 예제 iOS 앱에 사용한 것과 동일한 OpenAPI 문서를 추가했고 플러그인 config 파일도 추가했습니다 이 타깃에 대해 유형과 서버 스텁을 생성하겠습니다 사양 중심 개발을 함으로써 서비스에 새 기능을 추가하는 것이 어떻게 쉬워지는지를 살펴보겠습니다
고양이 이모티콘도 좋지만 여러 근거에 따르면 인터넷의 주목적은 고양이 영상을 교환하는 데 있으므로 우리 서버에도 그 기능을 추가하겠습니다
사양 중심 개발을 하면 단 두 단계만 거쳐 새 API 연산을 추가할 수 있습니다 먼저 새 연산을 OpenAPI 문서에 추가합니다 그러면 이제 생성된 프로토콜에 새 함수 요건이 생겼기 때문에 컴파일러는 핸들러에 메서드를 정의하고 비즈니스 로직을 구현하라고 주장할 겁니다 시작하기 전에 고양이 동영상이 필요한데 영상은 타깃의 Resources 폴더에 넣어 두었습니다
OpenAPI 문서로 넘어가 새 연산을 추가하겠습니다
getClip이라는 이 연산의 응답은 이진 응답이며 응답의 콘텐츠 유형은 응답 본문에 동영상 데이터가 들어 있음을 가리킵니다
패키지를 재컴파일하려 하면 실패할 겁니다
핸들러가 생성된 프로토콜을 더 이상 준수하지 않기 때문입니다 핸들러가 새 연산에 대한 함수를 제공하지 않으니까요 프로토콜 스텁은 Xcode가 우리 대신 넣게 하고 우리는 비즈니스 로직을 제시할 텐데 비즈니스 로직은 동영상 리소스 파일의 바이트를 읽어 이진 본문으로 OK 응답을 반환할 겁니다 유형 안정성이 있는 생성된 코드는 이 함수가 이진 응답 본문만을 반환하게 한다는 데 주의하세요 이 연산에 대한 OpenAPI 문서에 그렇게 명시되어 있으니까요 이제 패키지를 재컴파일하면 컴파일에 성공해서 서버를 다시 실행할 수 있습니다 그리고 Safari로 넘어가면 새 API 엔드포인트를 테스트할 수 있죠
지금까지 OpenAPI로 서비스를 문서화하여 모호성을 없애고 사양 중심 개발을 하는 법을 살펴봤습니다 Swift OpenAPI 생성기를 사용해 iOS 앱에서 서버 API 작업을 단순화하는 방법도 보여드렸습니다 마지막으로, Swift의 언어 기능과 갈수록 성장하는 Swift 온 서버 생태계 덕에 Swift가 백엔드 서비스 구현에 탁월한 선택지라는 점을 봤습니다 그래서 Swift OpenAPI 생성기는 오픈 소스이고 GitHub에 공개되어 있으며 여러분도 여기에서 더 많은 것을 배우고 프로젝트 성장에 기여할 수 있습니다 시청해 주셔서 감사합니다 이상입니다, 야옹!
-
-
4:17 - Example OpenAPI document
openapi: "3.0.3" info: title: "GreetingService" version: "1.0.0" servers: - url: "http://localhost:8080/api" description: "Production" paths: /greet: get: operationId: "getGreeting" parameters: - name: "name" required: false in: "query" description: "Personalizes the greeting." schema: type: "string" responses: "200": description: "Returns a greeting" content: application/json: schema: $ref: "#/components/schemas/Greeting"
-
7:05 - CatService openapi.yaml
openapi: "3.0.3" info: title: CatService version: 1.0.0 servers: - url: http://localhost:8080/api description: "Localhost cats 🙀" paths: /emoji: get: operationId: getEmoji parameters: - name: count required: false in: query description: "The number of cats to return. 😽😽😽" schema: type: integer responses: '200': description: "Returns a random emoji, of a cat, ofc! 😻" content: text/plain: schema: type: string
-
8:03 - Making API calls from your app
import SwiftUI import OpenAPIRuntime import OpenAPIURLSession #Preview { ContentView() } struct ContentView: View { @State private var emoji = "🫥" var body: some View { VStack { Text(emoji).font(.system(size: 100)) Button("Get cat!") { Task { try? await updateEmoji() } } } .padding() .buttonStyle(.borderedProminent) } let client: Client init() { self.client = Client( serverURL: try! Servers.server1(), transport: URLSessionTransport() ) } func updateEmoji() async throws { let response = try await client.getEmoji(Operations.getEmoji.Input()) switch response { case let .ok(okResponse): switch okResponse.body { case .text(let text): emoji = text } case .undocumented(statusCode: let statusCode, _): print("cat-astrophe: \(statusCode)") emoji = "🙉" } } }
-
9:48 - CatServiceClient openapi-generator-config.yaml
generate: - types - client
-
13:24 - Adapting as the API evolves
import SwiftUI import OpenAPIRuntime import OpenAPIURLSession #Preview { ContentView() } struct ContentView: View { @State private var emoji = "🫥" var body: some View { VStack { Text(emoji).font(.system(size: 100)) Button("Get cat!") { Task { try? await updateEmoji() } } Button("More cats!") { Task { try? await updateEmoji(count: 3) } } } .padding() .buttonStyle(.borderedProminent) } let client: Client init() { self.client = Client( serverURL: try! Servers.server1(), transport: URLSessionTransport() ) } func updateEmoji(count: Int = 1) async throws { let response = try await client.getEmoji(Operations.getEmoji.Input( query: Operations.getEmoji.Input.Query(count: count) )) switch response { case let .ok(okResponse): switch okResponse.body { case .text(let text): emoji = text } case .undocumented(statusCode: let statusCode, _): print("cat-astrophe: \(statusCode)") emoji = "🙉" } } }
-
15:06 - Testing your app with mocks
import SwiftUI import OpenAPIRuntime import OpenAPIURLSession #Preview { ContentView(client: MockClient()) } struct ContentView<C: APIProtocol>: View { @State private var emoji = "🫥" var body: some View { VStack { Text(emoji).font(.system(size: 100)) Button("Get cat!") { Task { try? await updateEmoji() } } Button("More cats!") { Task { try? await updateEmoji(count: 3) } } } .padding() .buttonStyle(.borderedProminent) } let client: C init(client: C) { self.client = client } init() where C == Client { self.client = Client( serverURL: try! Servers.server1(), transport: URLSessionTransport() ) } func updateEmoji(count: Int = 1) async throws { let response = try await client.getEmoji(Operations.getEmoji.Input( query: Operations.getEmoji.Input.Query(count: count) )) switch response { case let .ok(okResponse): switch okResponse.body { case .text(let text): emoji = text } case .undocumented(statusCode: let statusCode, _): print("cat-astrophe: \(statusCode)") emoji = "🙉" } } } struct MockClient: APIProtocol { func getEmoji(_ input: Operations.getEmoji.Input) async throws -> Operations.getEmoji.Output { let count = input.query.count ?? 1 let emojis = String(repeating: "🤖", count: count) return .ok(Operations.getEmoji.Output.Ok( body: .text(emojis) )) } }
-
16:58 - Implementing a backend server
import Foundation import OpenAPIRuntime import OpenAPIVapor import Vapor struct Handler: APIProtocol { func getEmoji(_ input: Operations.getEmoji.Input) async throws -> Operations.getEmoji.Output { let candidates = "🐱😹😻🙀😿😽😸😺😾😼" let chosen = String(candidates.randomElement()!) let count = input.query.count ?? 1 let emojis = String(repeating: chosen, count: count) return .ok(Operations.getEmoji.Output.Ok(body: .text(emojis))) } } @main struct CatService { public static func main() throws { let app = Vapor.Application() let transport = VaporTransport(routesBuilder: app) let handler = Handler() try handler.registerHandlers(on: transport, serverURL: Servers.server1()) try app.run() } }
-
18:43 - CatService Package.swift
// swift-tools-version: 5.8 import PackageDescription let package = Package( name: "CatService", platforms: [ .macOS(.v13), ], dependencies: [ .package( url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.1.0") ), .package( url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.0") ), .package( url: "https://github.com/swift-server/swift-openapi-vapor", .upToNextMinor(from: "0.1.0") ), .package( url: "https://github.com/vapor/vapor", .upToNextMajor(from: "4.69.2") ), ], targets: [ .executableTarget( name: "CatService", dependencies: [ .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), .product(name: "OpenAPIVapor", package: "swift-openapi-vapor"), .product(name: "Vapor", package: "vapor"), ], resources: [ .process("Resources/cat.mp4"), ], plugins: [ .plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator"), ] ), ] )
-
19:08 - CatService openapi.yaml
openapi: "3.0.3" info: title: CatService version: 1.0.0 servers: - url: http://localhost:8080/api description: "Localhost cats 🙀" paths: /emoji: get: operationId: getEmoji parameters: - name: count required: false in: query description: "The number of cats to return. 😽😽😽" schema: type: integer responses: '200': description: "Returns a random emoji, of a cat, ofc! 😻" content: text/plain: schema: type: string
-
19:10 - CatService openapi-generator-config.yaml
generate: - types - server
-
20:11 - Adding an operation to the OpenAPI document
openapi: "3.0.3" info: title: CatService version: 1.0.0 servers: - url: http://localhost:8080/api description: "Localhost cats 🙀" paths: /emoji: get: operationId: getEmoji parameters: - name: count required: false in: query description: "The number of cats to return. 😽😽😽" schema: type: integer responses: '200': description: "Returns a random emoji, of a cat, ofc! 😻" content: text/plain: schema: type: string /clip: get: operationId: getClip responses: '200': description: "Returns a cat video! 😽" content: video/mp4: schema: type: string format: binary
-
20:26 - Adding a new API operation
import Foundation import OpenAPIRuntime import OpenAPIVapor import Vapor struct Handler: APIProtocol { func getClip(_ input: Operations.getClip.Input) async throws -> Operations.getClip.Output { let clipResourceURL = Bundle.module.url(forResource: "cat", withExtension: "mp4")! let clipData = try Data(contentsOf: clipResourceURL) return .ok(Operations.getClip.Output.Ok(body: .binary(clipData))) } func getEmoji(_ input: Operations.getEmoji.Input) async throws -> Operations.getEmoji.Output { let candidates = "🐱😹😻🙀😿😽😸😺😾😼" let chosen = String(candidates.randomElement()!) let count = input.query.count ?? 1 let emojis = String(repeating: chosen, count: count) return .ok(Operations.getEmoji.Output.Ok(body: .text(emojis))) } } @main struct CatService { public static func main() throws { let app = Vapor.Application() let transport = VaporTransport(routesBuilder: app) let handler = Handler() try handler.registerHandlers(on: transport, serverURL: Servers.server1()) try app.run() } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.