// The HTTP/JSON gateway's address book: which URL each gRPC method answers on, and
// which two messages the call carries. etcd publishes the same services on both
// transports, so the mapping is a table and nothing else — a transport that has the
// table can serve every handle the gRPC transport serves.
///|
/// One call of etcd's HTTP/JSON gateway: the URL to `POST` to, the request message,
/// the reply message, and whether the reply arrives as a stream of messages rather
/// than as one.
pub(all) struct GatewayCall {
path : String
request : String
reply : String
streaming : Bool
}
///|
/// The call a gRPC method such as `/etcdserverpb.KV/Range` has on the gateway, or
/// `None` for a method etcd does not publish there.
pub fn gateway_call(grpc_method : String) -> GatewayCall? {
match grpc_method {
"/etcdserverpb.KV/Range" =>
Some(unary("/v3/kv/range", "RangeRequest", "RangeResponse"))
"/etcdserverpb.KV/Put" =>
Some(unary("/v3/kv/put", "PutRequest", "PutResponse"))
"/etcdserverpb.KV/DeleteRange" =>
Some(
unary("/v3/kv/deleterange", "DeleteRangeRequest", "DeleteRangeResponse"),
)
"/etcdserverpb.KV/Txn" =>
Some(unary("/v3/kv/txn", "TxnRequest", "TxnResponse"))
"/etcdserverpb.KV/Compact" =>
Some(
unary("/v3/kv/compaction", "CompactionRequest", "CompactionResponse"),
)
"/etcdserverpb.Lease/LeaseGrant" =>
Some(unary("/v3/lease/grant", "LeaseGrantRequest", "LeaseGrantResponse"))
"/etcdserverpb.Lease/LeaseRevoke" =>
Some(
unary("/v3/lease/revoke", "LeaseRevokeRequest", "LeaseRevokeResponse"),
)
"/etcdserverpb.Lease/LeaseTimeToLive" =>
Some(
unary(
"/v3/lease/timetolive", "LeaseTimeToLiveRequest", "LeaseTimeToLiveResponse",
),
)
"/etcdserverpb.Lease/LeaseLeases" =>
Some(
unary("/v3/lease/leases", "LeaseLeasesRequest", "LeaseLeasesResponse"),
)
"/etcdserverpb.Lease/LeaseKeepAlive" =>
Some(
streamed(
"/v3/lease/keepalive", "LeaseKeepAliveRequest", "LeaseKeepAliveResponse",
),
)
"/etcdserverpb.Watch/Watch" =>
Some(streamed("/v3/watch", "WatchRequest", "WatchResponse"))
"/etcdserverpb.Cluster/MemberAdd" =>
Some(
unary("/v3/cluster/member/add", "MemberAddRequest", "MemberAddResponse"),
)
"/etcdserverpb.Cluster/MemberRemove" =>
Some(
unary(
"/v3/cluster/member/remove", "MemberRemoveRequest", "MemberRemoveResponse",
),
)
"/etcdserverpb.Cluster/MemberUpdate" =>
Some(
unary(
"/v3/cluster/member/update", "MemberUpdateRequest", "MemberUpdateResponse",
),
)
"/etcdserverpb.Cluster/MemberList" =>
Some(
unary(
"/v3/cluster/member/list", "MemberListRequest", "MemberListResponse",
),
)
"/etcdserverpb.Cluster/MemberPromote" =>
Some(
unary(
"/v3/cluster/member/promote", "MemberPromoteRequest", "MemberPromoteResponse",
),
)
"/etcdserverpb.Maintenance/Alarm" =>
Some(unary("/v3/maintenance/alarm", "AlarmRequest", "AlarmResponse"))
"/etcdserverpb.Maintenance/Status" =>
Some(unary("/v3/maintenance/status", "StatusRequest", "StatusResponse"))
"/etcdserverpb.Maintenance/Defragment" =>
Some(
unary(
"/v3/maintenance/defragment", "DefragmentRequest", "DefragmentResponse",
),
)
"/etcdserverpb.Maintenance/Hash" =>
Some(unary("/v3/maintenance/hash", "HashRequest", "HashResponse"))
"/etcdserverpb.Maintenance/HashKV" =>
Some(unary("/v3/maintenance/hashkv", "HashKVRequest", "HashKVResponse"))
"/etcdserverpb.Maintenance/Snapshot" =>
Some(
streamed(
"/v3/maintenance/snapshot", "SnapshotRequest", "SnapshotResponse",
),
)
"/etcdserverpb.Maintenance/MoveLeader" =>
Some(
unary(
"/v3/maintenance/transfer-leadership", "MoveLeaderRequest", "MoveLeaderResponse",
),
)
"/etcdserverpb.Maintenance/Downgrade" =>
Some(
unary(
"/v3/maintenance/downgrade", "DowngradeRequest", "DowngradeResponse",
),
)
"/etcdserverpb.Auth/AuthEnable" =>
Some(unary("/v3/auth/enable", "AuthEnableRequest", "AuthEnableResponse"))
"/etcdserverpb.Auth/AuthDisable" =>
Some(
unary("/v3/auth/disable", "AuthDisableRequest", "AuthDisableResponse"),
)
"/etcdserverpb.Auth/AuthStatus" =>
Some(unary("/v3/auth/status", "AuthStatusRequest", "AuthStatusResponse"))
"/etcdserverpb.Auth/Authenticate" =>
Some(
unary(
"/v3/auth/authenticate", "AuthenticateRequest", "AuthenticateResponse",
),
)
"/etcdserverpb.Auth/UserAdd" =>
Some(
unary("/v3/auth/user/add", "AuthUserAddRequest", "AuthUserAddResponse"),
)
"/etcdserverpb.Auth/UserGet" =>
Some(
unary("/v3/auth/user/get", "AuthUserGetRequest", "AuthUserGetResponse"),
)
"/etcdserverpb.Auth/UserList" =>
Some(
unary(
"/v3/auth/user/list", "AuthUserListRequest", "AuthUserListResponse",
),
)
"/etcdserverpb.Auth/UserDelete" =>
Some(
unary(
"/v3/auth/user/delete", "AuthUserDeleteRequest", "AuthUserDeleteResponse",
),
)
"/etcdserverpb.Auth/UserChangePassword" =>
Some(
unary(
"/v3/auth/user/changepw", "AuthUserChangePasswordRequest", "AuthUserChangePasswordResponse",
),
)
"/etcdserverpb.Auth/UserGrantRole" =>
Some(
unary(
"/v3/auth/user/grant", "AuthUserGrantRoleRequest", "AuthUserGrantRoleResponse",
),
)
"/etcdserverpb.Auth/UserRevokeRole" =>
Some(
unary(
"/v3/auth/user/revoke", "AuthUserRevokeRoleRequest", "AuthUserRevokeRoleResponse",
),
)
"/etcdserverpb.Auth/RoleAdd" =>
Some(
unary("/v3/auth/role/add", "AuthRoleAddRequest", "AuthRoleAddResponse"),
)
"/etcdserverpb.Auth/RoleGet" =>
Some(
unary("/v3/auth/role/get", "AuthRoleGetRequest", "AuthRoleGetResponse"),
)
"/etcdserverpb.Auth/RoleList" =>
Some(
unary(
"/v3/auth/role/list", "AuthRoleListRequest", "AuthRoleListResponse",
),
)
"/etcdserverpb.Auth/RoleDelete" =>
Some(
unary(
"/v3/auth/role/delete", "AuthRoleDeleteRequest", "AuthRoleDeleteResponse",
),
)
"/etcdserverpb.Auth/RoleGrantPermission" =>
Some(
unary(
"/v3/auth/role/grant", "AuthRoleGrantPermissionRequest", "AuthRoleGrantPermissionResponse",
),
)
"/etcdserverpb.Auth/RoleRevokePermission" =>
Some(
unary(
"/v3/auth/role/revoke", "AuthRoleRevokePermissionRequest", "AuthRoleRevokePermissionResponse",
),
)
"/v3lockpb.Lock/Lock" =>
Some(unary("/v3/lock/lock", "LockRequest", "LockResponse"))
"/v3lockpb.Lock/Unlock" =>
Some(unary("/v3/lock/unlock", "UnlockRequest", "UnlockResponse"))
"/v3electionpb.Election/Campaign" =>
Some(
unary("/v3/election/campaign", "CampaignRequest", "CampaignResponse"),
)
"/v3electionpb.Election/Proclaim" =>
Some(
unary("/v3/election/proclaim", "ProclaimRequest", "ProclaimResponse"),
)
"/v3electionpb.Election/Leader" =>
Some(unary("/v3/election/leader", "LeaderRequest", "LeaderResponse"))
"/v3electionpb.Election/Observe" =>
Some(unary("/v3/election/observe", "LeaderRequest", "LeaderResponse"))
"/v3electionpb.Election/Resign" =>
Some(unary("/v3/election/resign", "ResignRequest", "ResignResponse"))
_ => None
}
}
///|
fn unary(path : String, request : String, reply : String) -> GatewayCall {
{ path, request, reply, streaming: false, }
}
///|
fn streamed(path : String, request : String, reply : String) -> GatewayCall {
{ path, request, reply, streaming: true, }
}
///|
/// The JSON body that carries `body` — one request message in its wire form — to the
/// gateway.
pub fn GatewayCall::encode_request(
self : GatewayCall,
body : Bytes,
) -> String raise EtcdError {
Json::stringify(message_json(self.request, body))
}
///|
/// The reply message in its wire form, from one JSON body or one JSON frame of a
/// streamed reply. A streamed reply wraps each message as `{"result": ...}`; an
/// error that ends a stream arrives in the same place, so it is read here rather
/// than surfacing as a missing field.
pub fn GatewayCall::decode_reply(
self : GatewayCall,
text : StringView,
) -> Bytes raise EtcdError {
let json = @json.parse(text) catch {
_ => raise gateway_error(13, "gateway: the reply is not JSON")
}
match json {
Object(members) =>
if members.get("code") is Some(_) && members.get("message") is Some(_) {
raise gateway_failure(text)
} else if self.streaming {
match members.get("result") {
Some(result) => message_wire(self.reply, result)
None =>
raise gateway_error(
13,
"gateway: a streamed reply has no result: " + text.to_owned(),
)
}
} else {
message_wire(self.reply, json)
}
_ => raise gateway_error(13, "gateway: the reply is not a JSON object")
}
}
///|
/// The error a failed call ended with. The gateway answers with the gRPC status it
/// would have returned — `{"error": ..., "code": 3, "message": ...}` — so the same
/// mapping applies as over gRPC; a body that is not a status (a proxy in the way,
/// say) becomes `Refused` with what it did say.
pub fn gateway_failure(text : StringView) -> EtcdError {
let json = @json.parse(text) catch {
_ =>
return EtcdError::Refused(Status::{ code: 2, message: text.to_owned(), })
}
match json {
Object(members) => {
let message = match members.get("message") {
Some(String(message)) => message
_ =>
match members.get("error") {
Some(String(error)) => error
_ => text.to_owned()
}
}
let code = match members.get("code") {
Some(Number(code, ..)) => code.to_int()
_ => 2
}
EtcdError::of(code, message)
}
_ => EtcdError::Refused(Status::{ code: 2, message: text.to_owned(), })
}
}