Follow the filesystem contract that dropped resource ids and publishing
This commit is contained in:
@@ -19,8 +19,7 @@ Listing, reading, writing, creating directories, renaming and deleting are
|
|||||||
supported. Reads and writes stream in pieces of at most 1 MiB, so a file of any
|
supported. Reads and writes stream in pieces of at most 1 MiB, so a file of any
|
||||||
size moves without either side holding it whole. Conditional writes use
|
size moves without either side holding it whole. Conditional writes use
|
||||||
`If-Match` and `If-None-Match`, which servers that return etags honour. Change
|
`If-Match` and `If-None-Match`, which servers that return etags honour. Change
|
||||||
polling and publishing are not supported, and a rename never replaces an
|
polling is not supported, and a rename never replaces an existing target.
|
||||||
existing target.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|||||||
+11
-23
@@ -52,13 +52,10 @@ fn transport(error: host::HostError) -> ErrorCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The path a locator names. This provider addresses by path only, because
|
/// The path a locator names, the only way the contract addresses a resource.
|
||||||
/// WebDAV has no identity separate from the URL.
|
fn route(target: &Locator) -> String {
|
||||||
fn route(target: &Locator) -> Result<String, ErrorCode> {
|
let Locator::Path(path) = target;
|
||||||
match target {
|
path.clone()
|
||||||
Locator::Path(path) => Ok(path.clone()),
|
|
||||||
Locator::Id(_) => Err(ErrorCode::InvalidInput),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn session<T>(
|
fn session<T>(
|
||||||
@@ -143,7 +140,6 @@ fn info_of(found: &dav::Found) -> FileInfo {
|
|||||||
},
|
},
|
||||||
size: found.length,
|
size: found.length,
|
||||||
revision: found.etag.clone(),
|
revision: found.etag.clone(),
|
||||||
id: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +190,6 @@ impl GuestWriter for DavWriter {
|
|||||||
kind: EntryKind::File,
|
kind: EntryKind::File,
|
||||||
size: 0,
|
size: 0,
|
||||||
revision,
|
revision,
|
||||||
id: None,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,8 +231,6 @@ impl Guest for Webdav {
|
|||||||
// will refuse the header rather than silently overwrite.
|
// will refuse the header rather than silently overwrite.
|
||||||
conditional_write: true,
|
conditional_write: true,
|
||||||
watch: false,
|
watch: false,
|
||||||
staged_write: false,
|
|
||||||
publishable: false,
|
|
||||||
virtual_directories: false,
|
virtual_directories: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -247,7 +240,7 @@ impl Guest for Webdav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn stat(target: Locator) -> Result<FileInfo, ErrorCode> {
|
fn stat(target: Locator) -> Result<FileInfo, ErrorCode> {
|
||||||
let path = route(&target)?;
|
let path = route(&target);
|
||||||
let body = exchange(
|
let body = exchange(
|
||||||
host::Method::Propfind,
|
host::Method::Propfind,
|
||||||
&path,
|
&path,
|
||||||
@@ -266,7 +259,7 @@ impl Guest for Webdav {
|
|||||||
cursor: Option<String>,
|
cursor: Option<String>,
|
||||||
limit: u32,
|
limit: u32,
|
||||||
) -> Result<ListPage, ErrorCode> {
|
) -> Result<ListPage, ErrorCode> {
|
||||||
let path = route(&target)?;
|
let path = route(&target);
|
||||||
let start: usize = match cursor {
|
let start: usize = match cursor {
|
||||||
Some(value) => value.parse().map_err(|_| ErrorCode::InvalidInput)?,
|
Some(value) => value.parse().map_err(|_| ErrorCode::InvalidInput)?,
|
||||||
None => 0,
|
None => 0,
|
||||||
@@ -296,7 +289,7 @@ impl Guest for Webdav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn open_read(target: Locator, offset: u64) -> Result<Reader, ErrorCode> {
|
fn open_read(target: Locator, offset: u64) -> Result<Reader, ErrorCode> {
|
||||||
let path = route(&target)?;
|
let path = route(&target);
|
||||||
let call = begin(
|
let call = begin(
|
||||||
host::Method::Get,
|
host::Method::Get,
|
||||||
&path,
|
&path,
|
||||||
@@ -320,7 +313,7 @@ impl Guest for Webdav {
|
|||||||
condition: WriteCondition,
|
condition: WriteCondition,
|
||||||
_size_hint: Option<u64>,
|
_size_hint: Option<u64>,
|
||||||
) -> Result<Writer, ErrorCode> {
|
) -> Result<Writer, ErrorCode> {
|
||||||
let path = route(&target)?;
|
let path = route(&target);
|
||||||
let guard = match condition {
|
let guard = match condition {
|
||||||
WriteCondition::CreateOnly => Some(host::Header {
|
WriteCondition::CreateOnly => Some(host::Header {
|
||||||
name: "if-none-match".to_owned(),
|
name: "if-none-match".to_owned(),
|
||||||
@@ -340,13 +333,12 @@ impl Guest for Webdav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mkdir(target: Locator) -> Result<FileInfo, ErrorCode> {
|
fn mkdir(target: Locator) -> Result<FileInfo, ErrorCode> {
|
||||||
let path = route(&target)?;
|
let path = route(&target);
|
||||||
exchange(host::Method::Mkcol, &path, Vec::new(), &[])?;
|
exchange(host::Method::Mkcol, &path, Vec::new(), &[])?;
|
||||||
Ok(FileInfo {
|
Ok(FileInfo {
|
||||||
kind: EntryKind::Directory,
|
kind: EntryKind::Directory,
|
||||||
size: 0,
|
size: 0,
|
||||||
revision: None,
|
revision: None,
|
||||||
id: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +346,7 @@ impl Guest for Webdav {
|
|||||||
target: Locator,
|
target: Locator,
|
||||||
destination: String,
|
destination: String,
|
||||||
) -> Result<FileInfo, ErrorCode> {
|
) -> Result<FileInfo, ErrorCode> {
|
||||||
let path = route(&target)?;
|
let path = route(&target);
|
||||||
let to = session(|session| Ok(dav::url(&session.root, &destination)))?;
|
let to = session(|session| Ok(dav::url(&session.root, &destination)))?;
|
||||||
exchange(
|
exchange(
|
||||||
host::Method::Move,
|
host::Method::Move,
|
||||||
@@ -377,7 +369,7 @@ impl Guest for Webdav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove(target: Locator) -> Result<(), ErrorCode> {
|
fn remove(target: Locator) -> Result<(), ErrorCode> {
|
||||||
let path = route(&target)?;
|
let path = route(&target);
|
||||||
exchange(host::Method::Delete, &path, Vec::new(), &[])?;
|
exchange(host::Method::Delete, &path, Vec::new(), &[])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -385,10 +377,6 @@ impl Guest for Webdav {
|
|||||||
fn poll(_cursor: Option<String>) -> Result<ChangePage, ErrorCode> {
|
fn poll(_cursor: Option<String>) -> Result<ChangePage, ErrorCode> {
|
||||||
Err(ErrorCode::Unsupported)
|
Err(ErrorCode::Unsupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn publish() -> Result<Option<String>, ErrorCode> {
|
|
||||||
Err(ErrorCode::Unsupported)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sdk::export!(Webdav with_types_in sdk);
|
sdk::export!(Webdav with_types_in sdk);
|
||||||
|
|||||||
+10
-25
@@ -74,9 +74,6 @@ interface host {
|
|||||||
variant setting { text(string), secret(string), toggle(bool) }
|
variant setting { text(string), secret(string), toggle(bool) }
|
||||||
/// Reads configuration or a session-only credential for this connection.
|
/// Reads configuration or a session-only credential for this connection.
|
||||||
config: func(key: string) -> option<setting>;
|
config: func(key: string) -> option<setting>;
|
||||||
/// Reports that the current access token was rejected. The host refreshes
|
|
||||||
/// it once; a second rejection surfaces to the caller as denied.
|
|
||||||
invalidate-token: func();
|
|
||||||
|
|
||||||
/// Emits a diagnostic event. Never include secrets or private document data.
|
/// Emits a diagnostic event. Never include secrets or private document data.
|
||||||
log: func(level: log-level, message: string);
|
log: func(level: log-level, message: string);
|
||||||
@@ -101,36 +98,28 @@ interface provider {
|
|||||||
|
|
||||||
/// An opaque version token, at most 4096 UTF-8 bytes.
|
/// An opaque version token, at most 4096 UTF-8 bytes.
|
||||||
type revision = string;
|
type revision = string;
|
||||||
/// An opaque backend identity, at most 4096 UTF-8 bytes. The host never
|
|
||||||
/// parses, orders, or shows one, and never keeps one past the connection,
|
|
||||||
/// because a backend may reuse it.
|
|
||||||
type resource-id = string;
|
|
||||||
|
|
||||||
/// How one resource is named. A provider that hands out ids must accept
|
/// How one resource is named: a path within the connection root.
|
||||||
/// them back; one that does not may reject every id as invalid-input.
|
variant locator { path(string) }
|
||||||
variant locator { path(string), id(resource-id) }
|
|
||||||
|
|
||||||
/// Optional operations. False capabilities must return unsupported.
|
/// Optional operations. False capabilities must return unsupported.
|
||||||
/// Conditional-write promises atomic create-only and revision comparisons,
|
/// Conditional-write promises atomic create-only and revision comparisons.
|
||||||
/// and cannot be combined with staged-write, where nothing to compare
|
/// Virtual-directories says a directory is only ever a prefix of something
|
||||||
/// exists until a publish lands. Virtual-directories says a directory is
|
/// stored, as in a flat object store: the host then never offers to create
|
||||||
/// only ever a prefix of something stored, as in a flat object store: the
|
/// an empty one, warns that removing the last child removes the folder,
|
||||||
/// host then never offers to create an empty one, warns that removing the
|
/// and does not call a vanished folder an error. Such a provider may still
|
||||||
/// last child removes the folder, and does not call a vanished folder an
|
/// accept mkdir by writing a marker, and must answer unsupported when
|
||||||
/// error. Such a provider may still accept mkdir by writing a marker, and
|
/// asked to rename a directory.
|
||||||
/// must answer unsupported when asked to rename a directory.
|
|
||||||
record capabilities {
|
record capabilities {
|
||||||
write: bool, mkdir: bool, rename: bool, remove: bool,
|
write: bool, mkdir: bool, rename: bool, remove: bool,
|
||||||
conditional-write: bool, watch: bool,
|
conditional-write: bool, watch: bool,
|
||||||
staged-write: bool, publishable: bool,
|
|
||||||
virtual-directories: bool,
|
virtual-directories: bool,
|
||||||
}
|
}
|
||||||
/// Byte length, an opaque revision, and a backend identity, each if known.
|
/// Byte length and an opaque revision, each if known.
|
||||||
record file-info {
|
record file-info {
|
||||||
kind: entry-kind,
|
kind: entry-kind,
|
||||||
size: u64,
|
size: u64,
|
||||||
revision: option<revision>,
|
revision: option<revision>,
|
||||||
id: option<resource-id>,
|
|
||||||
}
|
}
|
||||||
/// One child name, without slashes, dot segments, or control characters.
|
/// One child name, without slashes, dot segments, or control characters.
|
||||||
record entry { name: string, info: file-info }
|
record entry { name: string, info: file-info }
|
||||||
@@ -198,10 +187,6 @@ interface provider {
|
|||||||
/// Polls for a bounded change page if the watch capability is present. No
|
/// Polls for a bounded change page if the watch capability is present. No
|
||||||
/// background callback or persistent subscription is installed by the host.
|
/// background callback or persistent subscription is installed by the host.
|
||||||
poll: func(cursor: option<string>) -> result<change-page, error-code>;
|
poll: func(cursor: option<string>) -> result<change-page, error-code>;
|
||||||
/// Makes every staged write visible as one new root, if the publishable
|
|
||||||
/// capability is present, and names it, for example as a content
|
|
||||||
/// identifier. A none result means the publish needs another call.
|
|
||||||
publish: func() -> result<option<revision>, error-code>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Memona filesystem extension ABI 0.1.0, distributed as a WebAssembly component.
|
/// Memona filesystem extension ABI 0.1.0, distributed as a WebAssembly component.
|
||||||
|
|||||||
Reference in New Issue
Block a user