Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb338777

Browse files
chore: add API errors to SDK (#12)
1 parenteb61235 commitb338777

File tree

10 files changed

+169
-101
lines changed

10 files changed

+169
-101
lines changed

‎Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.
Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
import SwiftUI
2+
import Alamofire
23

34
structPreviewClient:Client{
45
init(url _:URL, token _:String?=nil){}
56

6-
func user(_:String)asyncthrows->User{
7-
tryawaitTask.sleep(for:.seconds(1))
8-
returnUser(
9-
id:UUID(),
10-
username:"admin",
11-
avatar_url:"",
12-
name:"admin",
13-
email:"admin@coder.com",
14-
created_at:Date.now,
15-
updated_at:Date.now,
16-
last_seen_at:Date.now,
17-
status:"active",
18-
login_type:"none",
19-
theme_preference:"dark",
20-
organization_ids:[],
21-
roles:[]
22-
)
7+
func user(_:String)asyncthrows(ClientError)->User{
8+
do{
9+
tryawaitTask.sleep(for:.seconds(1))
10+
returnUser(
11+
id:UUID(),
12+
username:"admin",
13+
avatar_url:"",
14+
name:"admin",
15+
email:"admin@coder.com",
16+
created_at:Date.now,
17+
updated_at:Date.now,
18+
last_seen_at:Date.now,
19+
status:"active",
20+
login_type:"none",
21+
theme_preference:"dark",
22+
organization_ids:[],
23+
roles:[]
24+
)
25+
}catch{
26+
throwClientError.reqError(AFError.explicitlyCancelled)
27+
}
2328
}
2429
}

‎Coder Desktop/Coder Desktop/SDK/Client.swift

Lines changed: 93 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Foundation
33

44
protocolClient{
55
init(url:URL, token:String?)
6-
func user(_ ident:String)asyncthrows->User
6+
func user(_ ident:String)asyncthrows(ClientError)->User
77
}
88

99
structCoderClient:Client{
@@ -25,38 +25,117 @@ struct CoderClient: Client {
2525
func request<T:Encodable>(
2626
_ path:String,
2727
method:HTTPMethod,
28-
body:T
29-
)async->DataResponse<Data,AFError>{
28+
body:T?=nil
29+
)asyncthrows(ClientError)->HTTPResponse{
3030
leturl=self.url.appendingPathComponent(path)
31-
letheaders:HTTPHeaders=[Headers.sessionToken:token??""]
32-
returnawaitAF.request(
31+
letheaders:HTTPHeaders?=token.map{[Headers.sessionToken:$0]}
32+
letout=awaitAF.request(
3333
url,
3434
method: method,
3535
parameters: body,
36-
encoder:JSONParameterEncoder.default,
3736
headers: headers
3837
).serializingData().response
38+
switch out.result{
39+
case.success(let data):
40+
returnHTTPResponse(resp: out.response!, data: data, req: out.request)
41+
case.failure(let error):
42+
throwClientError.reqError(error)
43+
}
3944
}
4045

4146
func request(
4247
_ path:String,
4348
method:HTTPMethod
44-
)async->DataResponse<Data,AFError>{
49+
)asyncthrows(ClientError)->HTTPResponse{
4550
leturl=self.url.appendingPathComponent(path)
46-
letheaders:HTTPHeaders=[Headers.sessionToken:token??""]
47-
returnawaitAF.request(
51+
letheaders:HTTPHeaders?=token.map{[Headers.sessionToken:$0]}
52+
letout=awaitAF.request(
4853
url,
4954
method: method,
5055
headers: headers
5156
).serializingData().response
57+
switch out.result{
58+
case.success(let data):
59+
returnHTTPResponse(resp: out.response!, data: data, req: out.request)
60+
case.failure(let error):
61+
throwClientError.reqError(error)
62+
}
5263
}
64+
65+
func responseAsError(_ resp:HTTPResponse)->ClientError{
66+
do{
67+
letbody=tryCoderClient.decoder.decode(Response.self, from: resp.data)
68+
letout=APIError(
69+
response: body,
70+
statusCode: resp.resp.statusCode,
71+
method: resp.req?.httpMethod,
72+
url: resp.req?.url
73+
)
74+
returnClientError.apiError(out)
75+
}catch{
76+
returnClientError.unexpectedResponse(resp.data[...1024])
77+
}
78+
}
79+
80+
enumHeaders{
81+
staticletsessionToken="Coder-Session-Token"
82+
}
83+
5384
}
5485

55-
enumClientError:Error{
56-
case unexpectedStatusCode
57-
case badResponse
86+
structHTTPResponse{
87+
letresp:HTTPURLResponse
88+
letdata:Data
89+
letreq:URLRequest?
5890
}
5991

60-
enumHeaders{
61-
staticletsessionToken="Coder-Session-Token"
92+
structAPIError:Decodable{
93+
letresponse:Response
94+
letstatusCode:Int
95+
letmethod:String?
96+
leturl:URL?
97+
98+
vardescription:String{
99+
varcomponents:[String]=[]
100+
iflet method= method,let url= url{
101+
components.append("\(method)\(url.absoluteString)")
102+
}
103+
components.append("Unexpected status code\(statusCode):\n\(response.message)")
104+
iflet detail= response.detail{
105+
components.append("\tError:\(detail)")
106+
}
107+
iflet validations= response.validations, !validations.isEmpty{
108+
letvalidationMessages= validations.map{"\t\($0.field):\($0.detail)"}
109+
components.append(contentsOf: validationMessages)
110+
}
111+
return components.joined(separator:"\n")
112+
}
113+
}
114+
115+
structResponse:Decodable{
116+
letmessage:String
117+
letdetail:String?
118+
letvalidations:[ValidationError]?
119+
}
120+
121+
structValidationError:Decodable{
122+
letfield:String
123+
letdetail:String
124+
}
125+
126+
enumClientError:Error{
127+
case apiError(APIError)
128+
case reqError(AFError)
129+
case unexpectedResponse(Data)
130+
131+
vardescription:String{
132+
switchself{
133+
case.apiError(let error):
134+
return error.description
135+
case.reqError(let error):
136+
return error.localizedDescription
137+
case.unexpectedResponse(let data):
138+
return"Unexpected response:\(data)"
139+
}
140+
}
62141
}

‎Coder Desktop/Coder Desktop/SDK/User.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import Foundation
22

33
extensionCoderClient{
4-
func user(_ ident:String)asyncthrows->User{
5-
letresp=awaitrequest("/api/v2/users/\(ident)", method:.get)
6-
guardlet response= resp.response, response.statusCode==200else{
7-
throwClientError.unexpectedStatusCode
4+
func user(_ ident:String)asyncthrows(ClientError)->User{
5+
letres=tryawaitrequest("/api/v2/users/\(ident)", method:.get)
6+
guardres.resp.statusCode==200else{
7+
throwresponseAsError(res)
88
}
9-
guardlet data= resp.dataelse{
10-
throwClientError.badResponse
9+
do{
10+
returntryCoderClient.decoder.decode(User.self, from: res.data)
11+
}catch{
12+
throwClientError.unexpectedResponse(res.data[...1024])
1113
}
12-
returntryCoderClient.decoder.decode(User.self, from: data)
1314
}
1415
}
1516

‎Coder Desktop/Coder Desktop/Views/LoginForm.swift

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,29 @@ struct LoginForm<C: Client, S: Session>: View {
3737
}
3838
.animation(.easeInOut, value: currentPage)
3939
.onAppear{
40-
loginError=nil
4140
baseAccessURL= session.baseAccessURL?.absoluteString?? baseAccessURL
4241
sessionToken=""
43-
}.padding(.top,35)
44-
VStack(alignment:.center){
45-
iflet loginError{
46-
Text("\(loginError.description)")
47-
.font(.headline)
48-
.foregroundColor(.red)
49-
.multilineTextAlignment(.center)
42+
}.padding(.vertical,35)
43+
.alert("Error", isPresented:Binding(
44+
get:{ loginError!=nil},
45+
set:{ isPresentedin
46+
if !isPresented{
47+
loginError=nil
48+
}
49+
}
50+
)){
51+
Button("OK", role:.cancel){}.keyboardShortcut(.defaultAction)
52+
} message:{
53+
Text(loginError?.description??"")
5054
}
51-
}
52-
.frame(height:35)
5355
}.padding()
5456
.frame(width:450, height:220)
5557
.disabled(loading)
5658
.onReceive(inspection.notice){self.inspection.visit(self, $0)} // ViewInspector
5759
}
5860

5961
internalfunc submit()async{
60-
loginError=nil
6162
guard sessionToken!=""else{
62-
loginError=.invalidToken
6363
return
6464
}
6565
guardlet url=URL(string: baseAccessURL), url.scheme=="https"else{
@@ -69,11 +69,10 @@ struct LoginForm<C: Client, S: Session>: View {
6969
loading=true
7070
defer{ loading=false}
7171
letclient=C(url: url, token: sessionToken)
72-
do{
72+
dothrows(ClientError){
7373
_=tryawait client.user("me")
7474
} catch{
75-
loginError=.failedAuth
76-
print("Set error")
75+
loginError=.failedAuth(error)
7776
return
7877
}
7978
session.store(baseAccessURL: url, sessionToken: sessionToken)
@@ -142,7 +141,9 @@ struct LoginForm<C: Client, S: Session>: View {
142141
}
143142

144143
privatefunc next(){
145-
loginError=nil
144+
guard baseAccessURL!=""else{
145+
return
146+
}
146147
guardlet url=URL(string: baseAccessURL), url.scheme=="https"else{
147148
loginError=.invalidURL
148149
return
@@ -155,7 +156,6 @@ struct LoginForm<C: Client, S: Session>: View {
155156

156157
privatefunc back(){
157158
withAnimation{
158-
loginError=nil
159159
currentPage=.serverURL
160160
focusedField=.baseAccessURL
161161
}
@@ -164,17 +164,14 @@ struct LoginForm<C: Client, S: Session>: View {
164164

165165
enumLoginError{
166166
case invalidURL
167-
case invalidToken
168-
case failedAuth
167+
case failedAuth(ClientError)
169168

170169
vardescription:String{
171170
switchself{
172171
case.invalidURL:
173172
return"Invalid URL"
174-
case.invalidToken:
175-
return"Invalid Session Token"
176-
case.failedAuth:
177-
return"Could not authenticate with Coder deployment"
173+
case.failedAuth(let err):
174+
return"Could not authenticate with Coder deployment:\n\(err.description)"
178175
}
179176
}
180177
}

‎Coder Desktop/Coder DesktopTests/AgentsTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ struct AgentsTests {
5656
vpn.state=.connected
5757
vpn.agents=createMockAgents(count:7)
5858

59-
tryawaitViewHosting.host(view){ _in
59+
tryawaitViewHosting.host(view){
6060
tryawait sut.inspection.inspect{ viewin
6161
vartoggle=try view.find(ViewType.Toggle.self)
6262
#expect(try toggle.labelView().text().string()=="Show All")

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp