save: add utoipa for OAPI generation

This commit is contained in:
oxalica 2024-10-16 06:43:54 -04:00
parent 71c5f038fa
commit acaf0f955a
11 changed files with 212 additions and 610 deletions

1
.gitignore vendored
View file

@ -5,6 +5,7 @@
*.profraw *.profraw
result result
result-* result-*
/docs/types.json
# Test configurations. # Test configurations.
config.toml config.toml

25
Cargo.lock generated
View file

@ -280,6 +280,7 @@ dependencies = [
"sha2", "sha2",
"thiserror", "thiserror",
"url", "url",
"utoipa",
] ]
[[package]] [[package]]
@ -2692,6 +2693,30 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utoipa"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "845494cea2113cf53dbb60904638afc8cb6c36fe39be7bcbb0eca1cfa49c3c1a"
dependencies = [
"indexmap 2.6.0",
"serde",
"serde_json",
"utoipa-gen",
]
[[package]]
name = "utoipa-gen"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1223ed4a64c622737615a02d062c20813b978d9d39ceced627337449b195771"
dependencies = [
"proc-macro2",
"quote",
"syn",
"url",
]
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.0"

View file

@ -7,6 +7,9 @@ members = [
] ]
default-members = ["blahd"] default-members = ["blahd"]
[workspace.dependencies]
utoipa = "5"
[workspace.lints.clippy] [workspace.lints.clippy]
allow_attributes_without_reason = "warn" allow_attributes_without_reason = "warn"
dbg_macro = "warn" dbg_macro = "warn"

View file

@ -6,6 +6,11 @@ edition = "2021"
[features] [features]
default = [] default = []
unsafe_use_mock_instant_for_testing = ["dep:mock_instant"] unsafe_use_mock_instant_for_testing = ["dep:mock_instant"]
utoipa = ["dep:utoipa"]
[[example]]
name = "openapi"
required-features = ["utoipa"]
[[bench]] [[bench]]
name = "crypto_ops" name = "crypto_ops"
@ -26,6 +31,7 @@ serde_json = "1"
serde_with = "3" serde_with = "3"
thiserror = "1" thiserror = "1"
url = { version = "2", features = ["serde"] } url = { version = "2", features = ["serde"] }
utoipa = { workspace = true, optional = true, features = ["url"] } # Generics support.
[dev-dependencies] [dev-dependencies]
criterion = "0.5" criterion = "0.5"

View file

@ -0,0 +1,8 @@
#![expect(clippy::print_stdout, reason = "allowed to dump OAPI")]
fn main() {
let json = blah_types::openapi()
.to_pretty_json()
.expect("serialization cannot fail");
println!("{json}");
}

View file

@ -11,6 +11,7 @@ use rand::RngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct UserKey { pub struct UserKey {
pub id_key: PubKey, pub id_key: PubKey,
pub act_key: PubKey, pub act_key: PubKey,
@ -18,6 +19,7 @@ pub struct UserKey {
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(value_type = String))]
pub struct PubKey(#[serde(with = "hex::serde")] pub [u8; PUBLIC_KEY_LENGTH]); pub struct PubKey(#[serde(with = "hex::serde")] pub [u8; PUBLIC_KEY_LENGTH]);
impl FromStr for PubKey { impl FromStr for PubKey {
@ -55,14 +57,17 @@ impl From<&VerifyingKey> for PubKey {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Signed<T> { pub struct Signed<T> {
#[serde(with = "hex::serde")] #[serde(with = "hex::serde")]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub sig: [u8; SIGNATURE_LENGTH], pub sig: [u8; SIGNATURE_LENGTH],
pub signee: Signee<T>, pub signee: Signee<T>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Signee<T> { pub struct Signee<T> {
pub nonce: u32, pub nonce: u32,

View file

@ -12,6 +12,7 @@ use crate::{PubKey, Signed};
/// User identity description structure. /// User identity description structure.
// TODO: Revise and shrink duplicates (pubkey fields). // TODO: Revise and shrink duplicates (pubkey fields).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct UserIdentityDesc { pub struct UserIdentityDesc {
/// User primary identity key, only for signing action keys. /// User primary identity key, only for signing action keys.
pub id_key: PubKey, pub id_key: PubKey,
@ -90,6 +91,7 @@ impl UserIdentityDesc {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename = "user_act_key")] #[serde(tag = "typ", rename = "user_act_key")]
pub struct UserActKeyDesc { pub struct UserActKeyDesc {
pub act_key: PubKey, pub act_key: PubKey,
@ -98,6 +100,7 @@ pub struct UserActKeyDesc {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename = "user_profile")] #[serde(tag = "typ", rename = "user_profile")]
pub struct UserProfile { pub struct UserProfile {
pub preferred_chat_server_urls: Vec<Url>, pub preferred_chat_server_urls: Vec<Url>,
@ -105,6 +108,7 @@ pub struct UserProfile {
} }
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(value_type = Url))]
#[serde(try_from = "Url")] #[serde(try_from = "Url")]
pub struct IdUrl(Url); pub struct IdUrl(Url);

View file

@ -10,3 +10,46 @@ pub mod crypto;
pub mod identity; pub mod identity;
pub mod msg; pub mod msg;
pub mod server; pub mod server;
#[cfg(feature = "utoipa")]
pub fn openapi() -> utoipa::openapi::OpenApi {
use utoipa::OpenApi;
#[derive(OpenApi)]
#[openapi(components(schemas(
crypto::Signed::<msg::AuthPayload>,
crypto::Signed::<msg::ChatPayload>,
crypto::Signed::<msg::CreateRoomPayload>,
crypto::Signed::<msg::DeleteRoomPayload>,
crypto::Signed::<msg::RoomAdminPayload>,
crypto::Signed::<msg::UserRegisterPayload>,
identity::UserIdentityDesc,
identity::UserProfile,
msg::AuthPayload,
msg::ChatPayload,
msg::DeleteRoomPayload,
msg::RichText,
msg::RoomAdminPayload,
msg::UserRegisterPayload,
server::ClientEvent,
server::ErrorResponse,
server::ErrorResponseWithChallenge,
server::RoomList,
server::RoomMetadata,
server::RoomMsgs,
server::ServerCapabilities,
server::ServerEvent,
server::ServerMetadata,
)))]
struct ApiDoc;
ApiDoc::openapi()
}
#[cfg(feature = "utoipa")]
#[test]
#[expect(clippy::print_stdout, reason = "allowed in tests")]
fn test_openapi() {
let json = crate::openapi().to_pretty_json().unwrap();
println!("{json}");
}

View file

@ -15,6 +15,7 @@ use crate::{PubKey, Signed};
/// It's currently serialized as a string for JavaScript's convenience. /// It's currently serialized as a string for JavaScript's convenience.
#[serde_as] #[serde_as]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(value_type = String))]
#[serde(transparent)] #[serde(transparent)]
pub struct Id(#[serde_as(as = "DisplayFromStr")] pub i64); pub struct Id(#[serde_as(as = "DisplayFromStr")] pub i64);
@ -39,6 +40,7 @@ impl Id {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct WithMsgId<T> { pub struct WithMsgId<T> {
pub cid: Id, pub cid: Id,
#[serde(flatten)] #[serde(flatten)]
@ -53,17 +55,20 @@ impl<T> WithMsgId<T> {
/// Register a user on a chat server. /// Register a user on a chat server.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename = "user_register")] #[serde(tag = "typ", rename = "user_register")]
pub struct UserRegisterPayload { pub struct UserRegisterPayload {
pub server_url: Url, pub server_url: Url,
pub id_url: IdUrl, pub id_url: IdUrl,
pub id_key: PubKey, pub id_key: PubKey,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub challenge: Option<UserRegisterChallengeResponse>, pub challenge: Option<UserRegisterChallengeResponse>,
} }
/// The server-specific challenge data for registration. /// The server-specific challenge data for registration.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum UserRegisterChallengeResponse { pub enum UserRegisterChallengeResponse {
/// Proof of work challenge containing the same nonce from server challenge request. /// Proof of work challenge containing the same nonce from server challenge request.
@ -73,6 +78,7 @@ pub enum UserRegisterChallengeResponse {
// FIXME: `deny_unknown_fields` breaks this. // FIXME: `deny_unknown_fields` breaks this.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename = "chat")] #[serde(tag = "typ", rename = "chat")]
pub struct ChatPayload { pub struct ChatPayload {
pub rich_text: RichText, pub rich_text: RichText,
@ -81,6 +87,7 @@ pub struct ChatPayload {
/// Ref: <https://github.com/Blah-IM/Weblah/blob/a3fa0f265af54c846f8d65f42aa4409c8dba9dd9/src/lib/richText.ts> /// Ref: <https://github.com/Blah-IM/Weblah/blob/a3fa0f265af54c846f8d65f42aa4409c8dba9dd9/src/lib/richText.ts>
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(value_type = Vec<RichTextPieceRaw>))]
#[serde(transparent)] #[serde(transparent)]
pub struct RichText(pub Vec<RichTextPiece>); pub struct RichText(pub Vec<RichTextPiece>);
@ -103,8 +110,9 @@ impl Serialize for RichTextPiece {
} }
} }
/// The protocol representation of `RichTextPiece`. /// The representation on wire of `RichTextPiece`.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(untagged)] #[serde(untagged)]
enum RichTextPieceRaw { enum RichTextPieceRaw {
Text(String), Text(String),
@ -148,6 +156,7 @@ impl<'de> Deserialize<'de> for RichText {
// TODO: This protocol format is quite large. Could use bitflags for database. // TODO: This protocol format is quite large. Could use bitflags for database.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct TextAttrs { pub struct TextAttrs {
#[serde(default, rename = "b", skip_serializing_if = "is_default")] #[serde(default, rename = "b", skip_serializing_if = "is_default")]
pub bold: bool, pub bold: bool,
@ -159,6 +168,7 @@ pub struct TextAttrs {
pub italic: bool, pub italic: bool,
// TODO: Should we validate and/or filter the URL. // TODO: Should we validate and/or filter the URL.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default, skip_serializing_if = "is_default")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub link: Option<String>, pub link: Option<String>,
#[serde(default, rename = "s", skip_serializing_if = "is_default")] #[serde(default, rename = "s", skip_serializing_if = "is_default")]
pub strike: bool, pub strike: bool,
@ -271,6 +281,7 @@ pub type SignedChatMsg = Signed<ChatPayload>;
pub type SignedChatMsgWithId = WithMsgId<SignedChatMsg>; pub type SignedChatMsgWithId = WithMsgId<SignedChatMsg>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ")] #[serde(tag = "typ")]
pub enum CreateRoomPayload { pub enum CreateRoomPayload {
#[serde(rename = "create_room")] #[serde(rename = "create_room")]
@ -281,6 +292,7 @@ pub enum CreateRoomPayload {
/// Multi-user room. /// Multi-user room.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct CreateGroup { pub struct CreateGroup {
pub attrs: RoomAttrs, pub attrs: RoomAttrs,
pub title: String, pub title: String,
@ -288,11 +300,13 @@ pub struct CreateGroup {
/// Peer-to-peer chat room with exactly two symmetric users. /// Peer-to-peer chat room with exactly two symmetric users.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct CreatePeerChat { pub struct CreatePeerChat {
pub peer: PubKey, pub peer: PubKey,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename = "delete_room")] #[serde(tag = "typ", rename = "delete_room")]
pub struct DeleteRoomPayload { pub struct DeleteRoomPayload {
pub room: Id, pub room: Id,
@ -302,6 +316,7 @@ pub struct DeleteRoomPayload {
/// 1. Sorted by userkeys. /// 1. Sorted by userkeys.
/// 2. No duplicated users. /// 2. No duplicated users.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(try_from = "Vec<RoomMember>")] #[serde(try_from = "Vec<RoomMember>")]
pub struct RoomMemberList(pub Vec<RoomMember>); pub struct RoomMemberList(pub Vec<RoomMember>);
@ -327,6 +342,7 @@ impl TryFrom<Vec<RoomMember>> for RoomMemberList {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RoomMember { pub struct RoomMember {
pub permission: MemberPermission, pub permission: MemberPermission,
pub user: PubKey, pub user: PubKey,
@ -336,11 +352,13 @@ pub struct RoomMember {
/// ///
/// TODO: Should we use JWT here instead? /// TODO: Should we use JWT here instead?
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename = "auth")] #[serde(tag = "typ", rename = "auth")]
pub struct AuthPayload {} pub struct AuthPayload {}
// FIXME: Remove this. // FIXME: Remove this.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
// `typ` is provided by `RoomAdminOp`. // `typ` is provided by `RoomAdminOp`.
pub struct RoomAdminPayload { pub struct RoomAdminPayload {
#[serde(flatten)] #[serde(flatten)]
@ -348,6 +366,7 @@ pub struct RoomAdminPayload {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename_all = "snake_case", rename = "remove_member")] #[serde(tag = "typ", rename_all = "snake_case", rename = "remove_member")]
pub struct RemoveMemberPayload { pub struct RemoveMemberPayload {
pub room: Id, pub room: Id,
@ -357,6 +376,7 @@ pub struct RemoveMemberPayload {
// TODO: Maybe disallow adding other user without consent? // TODO: Maybe disallow adding other user without consent?
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename_all = "snake_case", rename = "add_member")] #[serde(tag = "typ", rename_all = "snake_case", rename = "add_member")]
pub struct AddMemberPayload { pub struct AddMemberPayload {
pub room: Id, pub room: Id,
@ -365,6 +385,7 @@ pub struct AddMemberPayload {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "typ", rename_all = "snake_case", rename = "update_member")] #[serde(tag = "typ", rename_all = "snake_case", rename = "update_member")]
pub struct UpdateMemberPayload { pub struct UpdateMemberPayload {
pub room: Id, pub room: Id,
@ -373,6 +394,7 @@ pub struct UpdateMemberPayload {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(untagged)] #[serde(untagged)]
pub enum RoomAdminOp { pub enum RoomAdminOp {
AddMember(AddMemberPayload), AddMember(AddMemberPayload),
@ -382,6 +404,7 @@ pub enum RoomAdminOp {
bitflags::bitflags! { bitflags::bitflags! {
/// TODO: Is this a really all about permission, or is a generic `UserFlags`? /// TODO: Is this a really all about permission, or is a generic `UserFlags`?
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(value_type = i32))]
pub struct ServerPermission: i32 { pub struct ServerPermission: i32 {
const CREATE_ROOM = 1 << 0; const CREATE_ROOM = 1 << 0;
@ -391,6 +414,7 @@ bitflags::bitflags! {
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(value_type = i32))]
pub struct MemberPermission: i32 { pub struct MemberPermission: i32 {
const POST_CHAT = 1 << 0; const POST_CHAT = 1 << 0;
const ADD_MEMBER = 1 << 1; const ADD_MEMBER = 1 << 1;
@ -410,6 +434,7 @@ bitflags::bitflags! {
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(value_type = i32))]
pub struct RoomAttrs: i32 { pub struct RoomAttrs: i32 {
// NB. Used by schema. // NB. Used by schema.
const PUBLIC_READABLE = 1 << 0; const PUBLIC_READABLE = 1 << 0;

View file

@ -10,6 +10,7 @@ use crate::PubKey;
/// The response object returned as body on HTTP error status. /// The response object returned as body on HTTP error status.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ErrorResponse<S = String> { pub struct ErrorResponse<S = String> {
/// The error object. /// The error object.
pub error: ErrorObject<S>, pub error: ErrorObject<S>,
@ -18,21 +19,27 @@ pub struct ErrorResponse<S = String> {
/// The response object of `/_blah/user/me` endpoint on HTTP error status. /// The response object of `/_blah/user/me` endpoint on HTTP error status.
/// It contains additional registration information. /// It contains additional registration information.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ErrorResponseWithChallenge<S = String> { pub struct ErrorResponseWithChallenge<S = String> {
/// The error object. /// The error object.
pub error: ErrorObject<S>, pub error: ErrorObject<S>,
/// The challenge metadata returned by the `/_blah/user/me` endpoint for registration. /// The challenge metadata returned by the `/_blah/user/me` endpoint for registration.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub register_challenge: Option<UserRegisterChallenge>, pub register_challenge: Option<UserRegisterChallenge>,
} }
/// The error object. /// The error object.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ErrorObject<S = String> { pub struct ErrorObject<S = String> {
/// A machine-readable error code string. /// A machine-readable error code string.
#[cfg_attr(feature = "utoipa", schema(value_type = String, example = "user_not_found"))]
pub code: S, pub code: S,
/// A human-readable error message. /// A human-readable error message.
#[cfg_attr(feature = "utoipa", schema(value_type = String, example = "the user does not exist"))]
pub message: S, pub message: S,
} }
@ -50,16 +57,20 @@ impl<S: fmt::Display + fmt::Debug> std::error::Error for ErrorObject<S> {}
/// It may contains extra fields and clients should ignore them for future compatibility. /// It may contains extra fields and clients should ignore them for future compatibility.
/// Chat Servers can also include any custom fields here as long they have a `_` prefix. /// Chat Servers can also include any custom fields here as long they have a `_` prefix.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ServerMetadata { pub struct ServerMetadata {
/// A server-defined version string indicating its implementation name and the version. /// A server-defined version string indicating its implementation name and the version.
/// ///
/// It is expected to be in form `<server-name>/<server-version>` but not mandatory. /// It is expected to be in form `<server-name>/<server-version>` but not mandatory.
#[cfg_attr(feature = "utoipa", schema(example = "blahd/0.0.1"))]
pub server: String, pub server: String,
/// The URL to the source code of the Chat Server. /// The URL to the source code of the Chat Server.
/// ///
/// It is expected to be a public accessible maybe-compressed tarball link without /// It is expected to be a public accessible maybe-compressed tarball link without
/// access control. /// access control.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub src_url: Option<Url>, pub src_url: Option<Url>,
/// The server capabilities set. /// The server capabilities set.
@ -67,6 +78,7 @@ pub struct ServerMetadata {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ServerCapabilities { pub struct ServerCapabilities {
/// Whether registration is open to public. /// Whether registration is open to public.
pub allow_public_register: bool, pub allow_public_register: bool,
@ -74,6 +86,7 @@ pub struct ServerCapabilities {
/// Registration challenge information. /// Registration challenge information.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum UserRegisterChallenge { pub enum UserRegisterChallenge {
/// Proof-of-work (PoW) challenge. /// Proof-of-work (PoW) challenge.
@ -86,67 +99,82 @@ pub enum UserRegisterChallenge {
/// Response to list rooms. /// Response to list rooms.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RoomList { pub struct RoomList {
/// Result list of rooms. /// Result list of rooms.
pub rooms: Vec<RoomMetadata>, pub rooms: Vec<RoomMetadata>,
/// The skip-token to fetch the next page. /// The skip-token to fetch the next page.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub skip_token: Option<Id>, pub skip_token: Option<Id>,
} }
/// The metadata of a room. /// The metadata of a room.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RoomMetadata { pub struct RoomMetadata {
/// Room id. /// Room id.
pub rid: Id, pub rid: Id,
/// Plain text room title. None for peer chat. /// Plain text room title. None for peer chat.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub title: Option<String>, pub title: Option<String>,
/// Room attributes. /// Room attributes.
pub attrs: RoomAttrs, pub attrs: RoomAttrs,
// Extra information is only available for some APIs. // Extra information is only available for some APIs.
/// The last message in the room. /// The last message in the room.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub last_msg: Option<SignedChatMsgWithId>, pub last_msg: Option<SignedChatMsgWithId>,
/// The current user's last seen message's `cid`. /// The current user's last seen message's `cid`.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub last_seen_cid: Option<Id>, pub last_seen_cid: Option<Id>,
/// The number of unseen messages, ie. the number of messages from `last_seen_cid` to /// The number of unseen messages, ie. the number of messages from `last_seen_cid` to
/// `last_msg.cid`. /// `last_msg.cid`.
/// This may or may not be a precise number. /// This may or may not be a precise number.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub unseen_cnt: Option<u32>, pub unseen_cnt: Option<u32>,
/// The member permission of current user in the room, or `None` if it is not a member. /// The member permission of current user in the room, or `None` if it is not a member.
/// Only available with authentication. /// Only available with authentication.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub member_permission: Option<MemberPermission>, pub member_permission: Option<MemberPermission>,
/// The peer user, if this is a peer chat room. /// The peer user, if this is a peer chat room.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub peer_user: Option<PubKey>, pub peer_user: Option<PubKey>,
} }
/// Response to list room msgs. /// Response to list room msgs.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RoomMsgs { pub struct RoomMsgs {
/// Result list of msgs. /// Result list of msgs.
pub msgs: Vec<SignedChatMsgWithId>, pub msgs: Vec<SignedChatMsgWithId>,
/// The skip-token to fetch the next page. /// The skip-token to fetch the next page.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub skip_token: Option<Id>, pub skip_token: Option<Id>,
} }
/// Response to list room members. /// Response to list room members.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RoomMemberList { pub struct RoomMemberList {
/// Result list of members. /// Result list of members.
pub members: Vec<RoomMember>, pub members: Vec<RoomMember>,
/// The skip-token to fetch the next page. /// The skip-token to fetch the next page.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub skip_token: Option<Id>, pub skip_token: Option<Id>,
} }
/// The description of a room member. /// The description of a room member.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RoomMember { pub struct RoomMember {
/// The identity key of the member user. /// The identity key of the member user.
pub id_key: PubKey, pub id_key: PubKey,
@ -154,11 +182,13 @@ pub struct RoomMember {
pub permission: MemberPermission, pub permission: MemberPermission,
/// The user's last seen message `cid` in the room. /// The user's last seen message `cid` in the room.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "utoipa", schema(nullable = false))]
pub last_seen_cid: Option<Id>, pub last_seen_cid: Option<Id>,
} }
/// A server-to-client event. /// A server-to-client event.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ServerEvent { pub enum ServerEvent {
/// A message from a joined room. /// A message from a joined room.
@ -170,5 +200,6 @@ pub enum ServerEvent {
/// A client-to-server event. /// A client-to-server event.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ClientEvent {} pub enum ClientEvent {}

View file

@ -12,19 +12,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: object $ref: 'types.json#/components/schemas/ServerMetadata'
properties:
server:
type: string
example: 'blah/0.0.0'
src_url:
type: string
example: 'https://github.com/Blah-IM/blahrs'
capabilities:
type: object
properties:
allow_public_register:
type: boolean
# OAPI does not support WebSocket interface definitions. # OAPI does not support WebSocket interface definitions.
# See: https://github.com/OAI/OpenAPI-Specification/issues/55#issuecomment-929382279 # See: https://github.com/OAI/OpenAPI-Specification/issues/55#issuecomment-929382279
@ -35,7 +23,7 @@ paths:
This endpoint is for server-side-event dispatching. This endpoint is for server-side-event dispatching.
Once connected, client must send a JSON text message of type Once connected, client must send a JSON text message of type
`Signed-Auth` for authentication. `Signed_Auth` for authentication.
If server does not close it immediately, it means success. If server does not close it immediately, it means success.
Since OAPI does not support WebSocket interface, we use request and Since OAPI does not support WebSocket interface, we use request and
@ -54,7 +42,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/WSClientToServer' $ref: 'types.json#/components/schemas/ClientEvent'
responses: responses:
101: 101:
@ -66,7 +54,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/WSServerToClient' $ref: 'types.json#/components/schemas/ServerEvent'
/_blah/user/me: /_blah/user/me:
get: get:
@ -76,7 +64,7 @@ paths:
in: header in: header
description: Optional user authentication token. description: Optional user authentication token.
schema: schema:
$ref: '#/components/schemas/Signed-Auth' $ref: 'types.json#/components/schemas/Signed_AuthPayload'
responses: responses:
204: 204:
@ -88,7 +76,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiErrorWithRegisterChallenge' $ref: 'types.json#/components/schemas/ErrorResponseWithChallenge'
post: post:
summary: Register or update user identity summary: Register or update user identity
@ -116,7 +104,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Signed-UserRegister' $ref: 'types.json#/components/schemas/Signed_UserRegisterPayload'
responses: responses:
204: 204:
@ -127,7 +115,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
401: 401:
description: | description: |
@ -136,7 +124,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
403: 403:
description: | description: |
@ -145,7 +133,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
409: 409:
description: | description: |
@ -153,7 +141,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
422: 422:
description: | description: |
@ -163,7 +151,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room: /_blah/room:
get: get:
@ -207,7 +195,7 @@ paths:
in: header in: header
description: Optional proof of membership for private rooms. description: Optional proof of membership for private rooms.
schema: schema:
$ref: '#/components/schemas/Signed-Auth' $ref: 'types.json#/components/schemas/Signed_AuthPayload'
responses: responses:
200: 200:
@ -215,14 +203,14 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/RoomList' $ref: 'types.json#/components/schemas/RoomList'
401: 401:
description: Missing or invalid Authorization header. description: Missing or invalid Authorization header.
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
post: post:
summary: Create a room summary: Create a room
@ -237,7 +225,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Signed-CreateRoom' $ref: 'types.json#/components/schemas/Signed_CreateRoomPayload'
responses: responses:
200: 200:
@ -253,7 +241,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
404: 404:
description: | description: |
@ -262,14 +250,14 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
409: 409:
description: There is already a peer chat room between the user pair. description: There is already a peer chat room between the user pair.
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room/create: /_blah/room/create:
post: post:
@ -286,7 +274,7 @@ paths:
in: header in: header
description: Optional proof of membership for private rooms. description: Optional proof of membership for private rooms.
schema: schema:
$ref: '#/components/schemas/Signed-Auth' $ref: 'types.json#/components/schemas/Signed_AuthPayload'
responses: responses:
200: 200:
@ -294,7 +282,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/RoomMetadata' $ref: 'types.json#/components/schemas/RoomMetadata'
404: 404:
description: | description: |
@ -302,7 +290,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
delete: delete:
summary: Delete a room summary: Delete a room
@ -310,7 +298,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Signed-DeleteRoom' $ref: 'types.json#/components/schemas/Signed_DeleteRoomPayload'
responses: responses:
204: 204:
@ -321,7 +309,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
404: 404:
description: | description: |
@ -329,7 +317,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room/{rid}/admin: /_blah/room/{rid}/admin:
post: post:
@ -343,7 +331,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Signed-RoomAdmin' $ref: 'types.json#/components/schemas/Signed_RoomAdminPayload'
responses: responses:
204: 204:
@ -356,7 +344,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
409: 409:
description: description:
@ -364,7 +352,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room/{rid}/feed.json: /_blah/room/{rid}/feed.json:
get: get:
@ -385,7 +373,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room/{rid}/feed.atom: /_blah/room/{rid}/feed.atom:
get: get:
@ -408,8 +396,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room/{rid}/msg: /_blah/room/{rid}/msg:
get: get:
@ -426,7 +413,7 @@ paths:
in: header in: header
description: Optional proof of membership for private rooms. description: Optional proof of membership for private rooms.
schema: schema:
$ref: '#/components/schemas/Signed-Auth' $ref: 'types.json#/components/schemas/Signed_AuthPayload'
- name: top - name: top
in: query in: query
@ -450,7 +437,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/RoomMsgs' $ref: 'types.json#/components/schemas/RoomMsgs'
404: 404:
description: | description: |
@ -458,7 +445,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
post: post:
summary: Post a `Msg` into a room summary: Post a `Msg` into a room
@ -466,14 +453,14 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Signed-Chat' $ref: 'types.json#/components/schemas/Signed_ChatPayload'
responses: responses:
200: 200:
content: content:
application/json: application/json:
schema: schema:
type: string $ref: 'types.json#/components/schemas/Id'
description: Newly created message id `cid`. description: Newly created message id `cid`.
403: 403:
@ -481,14 +468,14 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
404: 404:
description: The room does not exist or the user is not a room member. description: The room does not exist or the user is not a room member.
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room/{rid}/msg/{cid}/seen: /_blah/room/{rid}/msg/{cid}/seen:
post: post:
@ -507,7 +494,7 @@ paths:
required: true required: true
description: Proof of membership for private rooms. description: Proof of membership for private rooms.
schema: schema:
$ref: '#/components/schemas/Signed-Auth' $ref: 'types.json#/components/schemas/Signed_AuthPayload'
responses: responses:
204: 204:
@ -519,7 +506,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room/{rid}/member: /_blah/room/{rid}/member:
get: get:
@ -530,7 +517,7 @@ paths:
required: true required: true
description: Proof of membership. description: Proof of membership.
schema: schema:
$ref: '#/components/schemas/Signed-Auth' $ref: 'types.json#/components/schemas/Signed_AuthPayload'
- name: top - name: top
in: query in: query
@ -553,7 +540,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/RoomMemberList' $ref: 'types.json#/components/schemas/RoomMemberList'
403: 403:
description: | description: |
@ -561,7 +548,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
404: 404:
description: | description: |
@ -569,7 +556,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
post: post:
summary: Join a room summary: Join a room
@ -577,7 +564,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Signed-AddMember' $ref: 'types.json#/components/schemas/Signed_AddMemberPayload'
responses: responses:
204: 204:
@ -590,7 +577,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
409: 409:
description: description:
@ -598,7 +585,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
/_blah/room/{rid}/member/{member_id_key}: /_blah/room/{rid}/member/{member_id_key}:
get: get:
@ -616,7 +603,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/RoomMember' $ref: 'types.json#/components/schemas/ErrorResponse'
404: 404:
description: | description: |
@ -625,7 +612,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ErrorResponse'
patch: patch:
summary: Update permission of a room member summary: Update permission of a room member
@ -634,7 +621,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Signed-UpdateMember' $ref: 'types.json#/components/schemas/Signed_UpdateMember'
responses: responses:
204: 204:
@ -647,7 +634,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ServerMetadata'
404: 404:
description: | description: |
@ -656,7 +643,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ServerMetadata'
delete: delete:
summary: Remove a room member. summary: Remove a room member.
@ -665,7 +652,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Signed-RemoveMember' $ref: 'types.json#/components/schemas/Signed_RemoveMember'
responses: responses:
204: 204:
@ -677,7 +664,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ServerMetadata'
404: 404:
description: | description: |
@ -686,540 +673,4 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ApiError' $ref: 'types.json#/components/schemas/ServerMetadata'
# Ideally we should generate these from src, but we need to
# WAIT: https://github.com/juhaku/utoipa/pull/1034
components:
schemas:
WSClientToServer:
anyOf:
- $ref: '#/components/schemas/Signed-Auth'
WSServerToClient:
anyOf:
- type: object
properties:
chat:
$ref: '#/components/schemas/WithMsgId-Signed-Chat'
- type: object
properties:
lagged:
type: object
const: {}
ApiError:
type: object
properties:
error:
type: object
properties:
code:
type: string
description: A machine-readable error code string.
example: invalid_signature
message:
type: string
description: A human-readable error message.
example: signature verification failed
ApiErrorWithRegisterChallenge:
allOf:
- $ref: '#/components/schemas/ApiError'
- type: object
properties:
register_challenge:
type: object
properties:
pow:
type: object
properties:
nonce:
type: integer
format: uint32
difficulty:
type: integer
format: uint32
RoomList:
type: object
required:
- rooms
properties:
rooms:
type: array
items:
$ref: '#/components/schemas/RoomMetadataForList'
next_token:
type: string
description: An opaque token to fetch the next page.
RoomMetadataForList:
type: object
required: ['rid', 'title', 'attrs']
properties:
rid:
type: string
title:
type: string
attrs:
description: Room attributes bitset, see `RoomAttrs`.
type: integer
format: int32
last_msg:
$ref: '#/components/schemas/WithMsgId-Signed-Chat'
last_seen_cid:
description: The `cid` of the last chat being marked as seen.
type: string
unseen_cnt:
description: |
The number of unseen messages. Only available for
GET `/room?filter=unseen`.
type: integer
format: uint32
member_permission:
type: integer
format: int32
peer_user:
type: string
description: |
For peer chat room, this gives the identity of the peer user.
RoomMetadata:
type: object
required: ['rid', 'title', 'attrs']
properties:
rid:
type: string
title:
type: string
attrs:
type: integer
format: int32
RoomMsgs:
type: object
required:
- msgs
properties:
msgs:
description: Room messages in reversed server-received time order.
type: array
items:
$ref: '#/components/schemas/WithMsgId-Signed-Chat'
skip_token:
description: The token for fetching the next page.
type: string
RoomMemberList:
type: object
required:
- members
properties:
members:
description: Room members in server-specified order.
type: array
items:
$ref: '#/components/schemas/RoomMember'
skip_token:
description: The token for fetching the next page.
type: string
RoomMember:
type: object
required:
- id_key
- permission
properties:
id_key:
type: string
permission:
type: integer
format: int32
last_seen_cid:
type: string
RichText:
type: array
items:
anyOf:
- type: string
description: Unstyled text piece.
- type: array
items: false
prefixItems:
- type: string
description: The text piece to apply styles on.
- type: object
properties:
b:
type: boolean
description: Bold.
m:
type: boolean
description: Monospace.
i:
type: boolean
description: Italic.
s:
type: boolean
description: Strikethrough.
u:
type: boolean
description: Underline.
hashtag:
type: boolean
description: Hashtag.
link:
type: string
description: Link target.
Signed-Auth:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'auth'
Signed-RoomAdmin:
oneOf:
- $ref: '#/components/schemas/Signed-AddMember'
- $ref: '#/components/schemas/Signed-RemoveMember'
Signed-AddMember:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'add_member'
room:
type: string
permission:
type: integer
format: int32
user:
type: string
Signed-UpdateMember:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'update_member'
room:
type: string
permission:
type: integer
format: int32
user:
type: string
Signed-RemoveMember:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'remove_member'
room:
type: string
user:
type: string
Signed-Chat:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'chat'
room:
type: string
rich_text:
$ref: '$/components/schemas/RichText'
WithMsgId-Signed-Chat:
allOf:
- $ref: '#/components/schemas/Signed-Chat'
- type: object
properties:
cid:
type: string
description: An opaque server-specific identifier.
Signed-CreateRoom:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
oneOf:
- type: object
properties:
typ:
type: string
const: 'create_room'
title:
type: string
- type: object
properties:
typ:
type: string
const: 'create_peer_chat'
peer:
type: string
Signed-DeleteRoom:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'delete_room'
room:
type: integer
format: in64
Signed-UserRegister:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'user_register'
server_url:
type: string
description: |
The server URL to register on. Must matches chat server's base_url.
It's path segment must be normalized, eg. always contains a `/` path for top-level.
id_url:
type: string
description: |
The identity server URL. Must be in form `https://<domain>/`.
It's path segment must be normalized, eg. always contains a `/` path for top-level.
id_key:
type: string
description: Hex encoded user primary key `id_key`.
challenge:
type: object
properties:
pow:
type: object
properties:
nonce:
type: integer
format: uint32
description: The challenge nonce retrieved from a recent GET response of `/user/me`.
UserIdentityDescription:
type: object
properties:
id_key:
type: string
act_keys:
type: array
items:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'user_act_key'
act_key:
type: string
expire_time:
type: integer
format: uint64
comment:
type: string
profile:
type: object
properties:
sig:
type: string
signee:
type: object
properties:
nonce:
type: integer
format: uint32
timestamp:
type: integer
format: uint64
id_key:
type: string
act_key:
type: string
payload:
type: object
properties:
typ:
type: string
const: 'user_profile'
preferred_chat_server_urls:
type: array
items:
type: string
format: url
id_urls:
type: array
items:
type: string
format: url