Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion rfd-api/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use octorust::{
use partial_struct::partial;
use rfd_data::{
content::{RfdContent, RfdDocument, RfdTemplate, TemplateError},
RfdNumber,
RfdNumber, RfdState, SingleLineString,
};
use rfd_github::{GitHubError, GitHubNewRfdNumber, GitHubRfdRepo};
use rfd_model::{
Expand Down Expand Up @@ -81,6 +81,14 @@ pub struct ContentContext {
pub new_template: RfdTemplate,
}

/// A single attribute change to apply to the header of a RFD document
#[derive(Debug)]
pub enum RfdAttrUpdate {
Discussion(SingleLineString),
Labels(SingleLineString),
State(RfdState),
}

#[derive(Debug, Error)]
pub enum UpdateRfdContentError {
#[error(transparent)]
Expand Down Expand Up @@ -691,6 +699,50 @@ impl RfdContext {
}
}

#[allow(clippy::result_large_err)]
#[instrument(skip(self, caller))]
pub async fn update_rfd_attr(
&self,
caller: &Caller<RfdPermission>,
rfd_number: i32,
update: RfdAttrUpdate,
message: Option<&str>,
) -> ResourceResult<RfdContent<'static>, UpdateRfdContentError> {
if caller.any(
[
RfdPermission::UpdateRfd(rfd_number),
RfdPermission::UpdateRfdsAll,
]
.iter(),
) {
let latest_revision = self
.get_latest_rfd_revision(caller, rfd_number)
.await
.inner_err_into()?;

let sha = latest_revision.commit.clone();
let mut content: RfdContent = latest_revision.into();

match update {
RfdAttrUpdate::Discussion(value) => content.update_discussion(&value),
RfdAttrUpdate::Labels(value) => content.update_labels(&value),
RfdAttrUpdate::State(state) => content.update_state(&state.into()),
};

tracing::info!("Updated attribute in RFD document");

// Persist the data back to GitHub. Note that we do not store this back to the database.
// We rely on GitHub as the source of truth and revisions are required to be linked to
// commits
self.commit_rfd_document(caller, rfd_number.into(), content.raw(), message, sha, None)
.await?;

Ok(content)
} else {
resource_restricted()
}
}

#[allow(clippy::result_large_err)]
#[instrument(skip(self, caller, document))]
pub async fn update_rfd_document(
Expand Down
41 changes: 10 additions & 31 deletions rfd-api/src/endpoints/rfd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use v_model::{permissions::Caller, storage::ListPagination};
use crate::{
caller::CallerExt,
context::{
RfdContext, RfdRevisionIdentifier, RfdRevisionMeta, RfdRevisionMetadataChange, RfdWithPdf,
RfdWithRaw, RfdWithoutContent,
RfdAttrUpdate, RfdContext, RfdRevisionIdentifier, RfdRevisionMeta,
RfdRevisionMetadataChange, RfdWithPdf, RfdWithRaw, RfdWithoutContent,
},
endpoints::UNLIMITED,
permissions::RfdPermission,
Expand Down Expand Up @@ -749,41 +749,20 @@ async fn set_rfd_attr_op(
body: &RfdAttrValue,
) -> Result<HttpResponseAccepted<RfdAttr>, HttpError> {
if let Ok(rfd_number) = number.parse::<i32>() {
// Get the latest revision
let revision = ctx.view_rfd_revision(caller, rfd_number, None).await?;

// TODO: Get rid of these clones
let mut content = match revision.content_format {
ContentFormat::Asciidoc => RfdContent::Asciidoc(RfdAsciidoc::new(revision.content)),
ContentFormat::Markdown => RfdContent::Markdown(RfdMarkdown::new(revision.content)),
};

// Update the requested attribute
match &attr {
RfdAttrName::Discussion => content.update_discussion(&body.value),
RfdAttrName::Labels => content.update_labels(&body.value),
let update = match &attr {
RfdAttrName::Discussion => RfdAttrUpdate::Discussion(body.value.clone()),
RfdAttrName::Labels => RfdAttrUpdate::Labels(body.value.clone()),
RfdAttrName::State => {
let state: RfdState = body.value.as_str().try_into().map_err(|err| {
RfdAttrUpdate::State(body.value.as_str().try_into().map_err(|err| {
tracing::info!(?err, "Invalid state was supplied");
HttpError::for_bad_request(None, "Invalid RFD state".to_string())
})?;
content.update_state(&state.into())
})?)
}
};

tracing::info!("Updated attribute in RFD document");

// Persist the data back to GitHub. Note that we do not store this back to the database.
// We rely on GitHub as the source of truth and revisions are required to tbe linked to
// commits
ctx.update_rfd_document(
caller,
rfd_number,
content.raw(),
body.message.as_deref(),
None,
)
.await?;
let content = ctx
.update_rfd_attr(caller, rfd_number, update, body.message.as_deref())
.await?;

extract_attr(&attr, &content).map(HttpResponseAccepted)
} else {
Expand Down
26 changes: 17 additions & 9 deletions rfd-data/src/content/asciidoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use regex::{regex, Regex};
use regex::{regex, NoExpand, Regex};
use std::borrow::Cow;

use super::RfdDocument;
Expand All @@ -28,20 +28,21 @@ impl<'a> RfdAsciidoc<'a> {
}

/// Resolve attribute references within the document. Currently only `{authors}` is supported.
/// Only the first reference is expanded, which bounds growth to the size of one value.
fn resolve_references(content: &str) -> String {
let pattern = regex!(r"(?<b>[^\\]?)(\{authors\})");

if let Some(captures) = pattern.captures(content) {
let prefix = captures.get(1).map(|c| c.as_str()).unwrap_or_default();
let replacement = format!(
Comment thread
notpeter marked this conversation as resolved.
Resolved
"{}{}",
prefix,
Self::attr("authors", content).unwrap_or_default()
);

// NoExpand: don't treat `$` as regex capture reference ($1, etc)
pattern
.replace_all(
content,
format!(
"{}{}",
prefix,
Self::attr("authors", content).unwrap_or_default()
),
)
.replacen(content, 1, NoExpand(&replacement))
.to_string()
} else {
content.to_string()
Expand Down Expand Up @@ -1052,4 +1053,11 @@ include::sub_doc2.adoc[]"#;
include.perform_replacement(original, replacement_content)
);
}

#[test]
fn test_reference_value_inserted_literally() {
let content = ":authors: Cost $1 ${b} $$\n\n= RFD 123 Place\n\nBy {authors}\n";
let rfd = RfdAsciidoc::new(content);
assert!(rfd.resolved.contains("By Cost $1 ${b} $$\n"));
}
}
Loading