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

Commit7b04709

Browse files
committed
chore: add API errors to SDK
1 parentc070ac7 commit7b04709

File tree

10 files changed

+173
-100
lines changed

10 files changed

+173
-100
lines changed

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

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎Coder Desktop/Coder Desktop/Preview Content/PreviewClient.swift

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,26 @@ import SwiftUI
33
structPreviewClient:Client{
44
init(url _:URL, token _:String?=nil){}
55

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

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

Lines changed: 97 additions & 13 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,122 @@ 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+
guardlet response= out.responseelse{
39+
throwClientError.noResponse
40+
}
41+
switch out.result{
42+
case.success(let data):
43+
returnHTTPResponse(resp: response, data: data, req: out.request)
44+
case.failure:
45+
throwClientError.badResponse
46+
}
3947
}
4048

4149
func request(
4250
_ path:String,
4351
method:HTTPMethod
44-
)async->DataResponse<Data,AFError>{
52+
)asyncthrows(ClientError)->HTTPResponse{
4553
leturl=self.url.appendingPathComponent(path)
46-
letheaders:HTTPHeaders=[Headers.sessionToken:token??""]
47-
returnawaitAF.request(
54+
letheaders:HTTPHeaders?=token.map{[Headers.sessionToken:$0]}
55+
letout=awaitAF.request(
4856
url,
4957
method: method,
5058
headers: headers
5159
).serializingData().response
60+
guardlet response= out.responseelse{
61+
throwClientError.noResponse
62+
}
63+
switch out.result{
64+
case.success(let data):
65+
returnHTTPResponse(resp: response, data: data, req: out.request)
66+
case.failure:
67+
throwClientError.badResponse
68+
}
5269
}
70+
71+
func responseAsError(_ resp:HTTPResponse)throws(ClientError)->APIError{
72+
do{
73+
letbody=tryCoderClient.decoder.decode(Response.self, from: resp.data)
74+
returnAPIError(
75+
response: body,
76+
statusCode: resp.resp.statusCode,
77+
method: resp.req?.httpMethod,
78+
url: resp.req?.url
79+
)
80+
}catch{
81+
throwClientError.badResponse
82+
}
83+
}
84+
85+
enumHeaders{
86+
staticletsessionToken="Coder-Session-Token"
87+
}
88+
89+
}
90+
91+
structHTTPResponse{
92+
letresp:HTTPURLResponse
93+
letdata:Data
94+
letreq:URLRequest?
95+
}
96+
97+
structAPIError:Decodable{
98+
letresponse:Response
99+
letstatusCode:Int
100+
letmethod:String?
101+
leturl:URL?
102+
103+
vardescription:String{
104+
varcomponents:[String]=[]
105+
iflet method= method,let url= url{
106+
components.append("\(method)\(url.absoluteString)")
107+
}
108+
components.append("Unexpected status code\(statusCode):\n\(response.message)")
109+
iflet detail= response.detail{
110+
components.append("\tError:\(detail)")
111+
}
112+
iflet validations= response.validations, !validations.isEmpty{
113+
letvalidationMessages= validations.map{"\t\($0.field):\($0.detail)"}
114+
components.append(contentsOf: validationMessages)
115+
}
116+
return components.joined(separator:"\n")
117+
}
118+
}
119+
120+
structResponse:Decodable{
121+
letmessage:String
122+
letdetail:String?
123+
letvalidations:[ValidationError]?
124+
}
125+
126+
structValidationError:Decodable{
127+
letfield:String
128+
letdetail:String
53129
}
54130

55131
enumClientError:Error{
56-
caseunexpectedStatusCode
132+
caseapiError(APIError)
57133
case badResponse
58-
}
134+
case noResponse
59135

60-
enumHeaders{
61-
staticletsessionToken="Coder-Session-Token"
136+
vardescription:String{
137+
switchself{
138+
case.apiError(let error):
139+
return error.description
140+
case.badResponse:
141+
return"Bad response"
142+
case.noResponse:
143+
return"No response"
144+
}
145+
}
62146
}

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
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+
guard res.resp.statusCode==200else{
7+
leterror=tryresponseAsError(res)
8+
throwClientError.apiError(error)
89
}
9-
guardlet data= resp.dataelse{
10+
do{
11+
returntryCoderClient.decoder.decode(User.self, from: res.data)
12+
}catch{
1013
throwClientError.badResponse
1114
}
12-
returntryCoderClient.decoder.decode(User.self, from: data)
1315
}
1416
}
1517

‎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