From f4064c19ec140987e15c64e80a3bf0c70025b31b Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Sat, 30 Jun 2018 17:34:22 -0700 Subject: history for container entities --- rust/fatcat-api/README.md | 3 +- rust/fatcat-api/api.yaml | 49 ++++--- rust/fatcat-api/api/swagger.yaml | 180 +++++++++++++++++++++++--- rust/fatcat-api/examples/client.rs | 11 +- rust/fatcat-api/examples/server_lib/server.rs | 17 ++- rust/fatcat-api/src/client.rs | 74 ++++++++++- rust/fatcat-api/src/lib.rs | 20 +++ rust/fatcat-api/src/mimetypes.rs | 16 +++ rust/fatcat-api/src/models.rs | 69 +--------- rust/fatcat-api/src/server.rs | 98 +++++++++++++- rust/src/api_server.rs | 56 ++++++++ rust/src/database_models.rs | 26 +++- rust/tests/test_api_server.rs | 56 ++++++++ 13 files changed, 562 insertions(+), 113 deletions(-) (limited to 'rust') diff --git a/rust/fatcat-api/README.md b/rust/fatcat-api/README.md index 37d2a3b2..9a444e87 100644 --- a/rust/fatcat-api/README.md +++ b/rust/fatcat-api/README.md @@ -13,7 +13,7 @@ To see how to make this your own, look here: [README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md) - API version: 0.1.0 -- Build date: 2018-06-30T23:46:33.844Z +- Build date: 2018-07-01T00:33:31.410Z This autogenerated project defines an API crate `fatcat` which contains: * An `Api` trait defining the API in Rust. @@ -69,6 +69,7 @@ cargo run --example client CreateReleaseBatch cargo run --example client CreateWork cargo run --example client CreateWorkBatch cargo run --example client GetContainer +cargo run --example client GetContainerHistory cargo run --example client GetCreator cargo run --example client GetCreatorReleases cargo run --example client GetEditgroup diff --git a/rust/fatcat-api/api.yaml b/rust/fatcat-api/api.yaml index 1796f32f..bdcb3ca8 100644 --- a/rust/fatcat-api/api.yaml +++ b/rust/fatcat-api/api.yaml @@ -181,21 +181,19 @@ definitions: <<: *ENTITYPROPS work_type: type: string - entity_history: - type: array - items: - type: object - required: - - edit - - editgroup - - changelog - properties: - edit: - $ref: "#/definitions/entity_edit" - editgroup: - $ref: "#/definitions/editgroup" - changelog: - $ref: "#/definitions/changelog_entry" + entity_history_entry: + type: object + required: + - edit + - editgroup + - changelog + properties: + edit: + $ref: "#/definitions/entity_edit" + editgroup: + $ref: "#/definitions/editgroup" + changelog: + $ref: "#/definitions/changelog_entry" entity_edit: type: object required: @@ -394,6 +392,27 @@ paths: schema: $ref: "#/definitions/container_entity" <<: *ENTITYRESPONSES + /container/{id}/history: + parameters: + - name: id + in: path + type: string + required: true + - name: limit + in: query + type: integer + format: int64 + required: false + get: + operationId: "get_container_history" + responses: + 200: + description: Found Entity History + schema: + type: array + items: + $ref: "#/definitions/entity_history_entry" + <<: *ENTITYRESPONSES /container/lookup: get: operationId: "lookup_container" diff --git a/rust/fatcat-api/api/swagger.yaml b/rust/fatcat-api/api/swagger.yaml index dcb4e74f..c333e60f 100644 --- a/rust/fatcat-api/api/swagger.yaml +++ b/rust/fatcat-api/api/swagger.yaml @@ -184,6 +184,67 @@ paths: path: "/container/:id" HttpMethod: "Get" httpmethod: "get" + /container/{id}/history: + get: + operationId: "get_container_history" + parameters: + - name: "id" + in: "path" + required: true + type: "string" + formatString: "\\\"{}\\\"" + example: "\"id_example\".to_string()" + - name: "limit" + in: "query" + required: false + type: "integer" + format: "int64" + formatString: "{:?}" + example: "Some(789)" + responses: + 200: + description: "Found Entity History" + schema: + type: "array" + items: + $ref: "#/definitions/entity_history_entry" + x-responseId: "FoundEntityHistory" + x-uppercaseResponseId: "FOUND_ENTITY_HISTORY" + uppercase_operation_id: "GET_CONTAINER_HISTORY" + uppercase_data_type: "VEC" + producesJson: true + 400: + description: "Bad Request" + schema: + $ref: "#/definitions/error_response" + x-responseId: "BadRequest" + x-uppercaseResponseId: "BAD_REQUEST" + uppercase_operation_id: "GET_CONTAINER_HISTORY" + uppercase_data_type: "ERRORRESPONSE" + producesJson: true + 404: + description: "Not Found" + schema: + $ref: "#/definitions/error_response" + x-responseId: "NotFound" + x-uppercaseResponseId: "NOT_FOUND" + uppercase_operation_id: "GET_CONTAINER_HISTORY" + uppercase_data_type: "ERRORRESPONSE" + producesJson: true + 500: + description: "Generic Error" + schema: + $ref: "#/definitions/error_response" + x-responseId: "GenericError" + x-uppercaseResponseId: "GENERIC_ERROR" + uppercase_operation_id: "GET_CONTAINER_HISTORY" + uppercase_data_type: "ERRORRESPONSE" + producesJson: true + operation_id: "get_container_history" + uppercase_operation_id: "GET_CONTAINER_HISTORY" + path: "/container/:id/history" + HttpMethod: "Get" + httpmethod: "get" /container/lookup: get: operationId: "lookup_container" @@ -1862,11 +1923,103 @@ definitions: state: "wip" revision: 42 upperCaseName: "WORK_ENTITY" - entity_history: - type: "array" - items: - $ref: "#/definitions/entity_history_inner" - upperCaseName: "ENTITY_HISTORY" + entity_history_entry: + type: "object" + required: + - "changelog" + - "edit" + - "editgroup" + properties: + edit: + $ref: "#/definitions/entity_edit" + editgroup: + $ref: "#/definitions/editgroup" + changelog: + $ref: "#/definitions/changelog_entry" + example: + editgroup: + extra: "{}" + edits: + works: + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + creators: + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + files: + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + containers: + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + releases: + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + - ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + description: "description" + editor_id: 6 + id: 0 + edit: + ident: "00000000-0000-0000-adce-000000000001" + edit_id: 847 + extra: "{}" + redirect_ident: "00000000-0000-0000-adce-000000000002" + editgroup_id: 16 + revision: 42 + changelog: + index: 1 + editgroup_id: 5 + timestamp: "2000-01-23T04:56:07.000+00:00" + upperCaseName: "ENTITY_HISTORY_ENTRY" entity_edit: type: "object" required: @@ -2019,8 +2172,8 @@ definitions: type: "string" format: "date-time" example: - index: 0 - editgroup_id: 6 + index: 1 + editgroup_id: 5 timestamp: "2000-01-23T04:56:07.000+00:00" upperCaseName: "CHANGELOG_ENTRY" release_ref: @@ -2081,19 +2234,6 @@ definitions: example: extra: "{}" upperCaseName: "STATS_RESPONSE" - entity_history_inner: - required: - - "changelog" - - "edit" - - "editgroup" - properties: - edit: - $ref: "#/definitions/entity_edit" - editgroup: - $ref: "#/definitions/editgroup" - changelog: - $ref: "#/definitions/changelog_entry" - upperCaseName: "ENTITY_HISTORY_INNER" editgroup_edits: properties: containers: diff --git a/rust/fatcat-api/examples/client.rs b/rust/fatcat-api/examples/client.rs index 28fac094..93b2d21d 100644 --- a/rust/fatcat-api/examples/client.rs +++ b/rust/fatcat-api/examples/client.rs @@ -13,8 +13,9 @@ use clap::{App, Arg}; #[allow(unused_imports)] use fatcat::{AcceptEditgroupResponse, ApiError, ApiNoContext, ContextWrapperExt, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, CreateFileBatchResponse, CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, - GetContainerResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileResponse, GetReleaseFilesResponse, - GetReleaseResponse, GetStatsResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; + GetContainerHistoryResponse, GetContainerResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileResponse, + GetReleaseFilesResponse, GetReleaseResponse, GetStatsResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, + LookupReleaseResponse}; #[allow(unused_imports)] use futures::{future, stream, Future, Stream}; @@ -31,6 +32,7 @@ fn main() { "CreateReleaseBatch", "CreateWorkBatch", "GetContainer", + "GetContainerHistory", "GetCreator", "GetCreatorReleases", "GetEditgroup", @@ -140,6 +142,11 @@ fn main() { println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from(""))); } + Some("GetContainerHistory") => { + let result = client.get_container_history("id_example".to_string(), Some(789)).wait(); + println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from(""))); + } + Some("GetCreator") => { let result = client.get_creator("id_example".to_string()).wait(); println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from(""))); diff --git a/rust/fatcat-api/examples/server_lib/server.rs b/rust/fatcat-api/examples/server_lib/server.rs index 5f4d7acb..0155782b 100644 --- a/rust/fatcat-api/examples/server_lib/server.rs +++ b/rust/fatcat-api/examples/server_lib/server.rs @@ -11,9 +11,9 @@ use swagger; use fatcat::models; use fatcat::{AcceptEditgroupResponse, Api, ApiError, Context, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, - CreateFileBatchResponse, CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerResponse, - GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseResponse, - GetStatsResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; + CreateFileBatchResponse, CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerHistoryResponse, + GetContainerResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileResponse, GetReleaseFilesResponse, + GetReleaseResponse, GetStatsResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; #[derive(Copy, Clone)] pub struct Server; @@ -101,6 +101,17 @@ impl Api for Server { Box::new(futures::failed("Generic failure".into())) } + fn get_container_history(&self, id: String, limit: Option, context: &Context) -> Box + Send> { + let context = context.clone(); + println!( + "get_container_history(\"{}\", {:?}) - X-Span-ID: {:?}", + id, + limit, + context.x_span_id.unwrap_or(String::from("")).clone() + ); + Box::new(futures::failed("Generic failure".into())) + } + fn get_creator(&self, id: String, context: &Context) -> Box + Send> { let context = context.clone(); println!("get_creator(\"{}\") - X-Span-ID: {:?}", id, context.x_span_id.unwrap_or(String::from("")).clone()); diff --git a/rust/fatcat-api/src/client.rs b/rust/fatcat-api/src/client.rs index 8c7e4ef3..02f0e155 100644 --- a/rust/fatcat-api/src/client.rs +++ b/rust/fatcat-api/src/client.rs @@ -35,9 +35,9 @@ use swagger::{ApiError, Context, XSpanId}; use models; use {AcceptEditgroupResponse, Api, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, CreateFileBatchResponse, - CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerResponse, GetCreatorReleasesResponse, GetCreatorResponse, - GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseResponse, GetStatsResponse, GetWorkReleasesResponse, GetWorkResponse, - LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; + CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerHistoryResponse, GetContainerResponse, + GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseResponse, + GetStatsResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; /// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes. fn into_base_path(input: T, correct_scheme: Option<&'static str>) -> Result { @@ -990,6 +990,74 @@ impl Api for Client { Box::new(futures::done(result)) } + fn get_container_history(&self, param_id: String, param_limit: Option, context: &Context) -> Box + Send> { + // Query parameters + let query_limit = param_limit.map_or_else(String::new, |query| format!("limit={limit}&", limit = query.to_string())); + + let url = format!( + "{}/v0/container/{id}/history?{limit}", + self.base_path, + id = utf8_percent_encode(¶m_id.to_string(), PATH_SEGMENT_ENCODE_SET), + limit = utf8_percent_encode(&query_limit, QUERY_ENCODE_SET) + ); + + let hyper_client = (self.hyper_client)(); + let request = hyper_client.request(hyper::method::Method::Get, &url); + let mut custom_headers = hyper::header::Headers::new(); + + context.x_span_id.as_ref().map(|header| custom_headers.set(XSpanId(header.clone()))); + + let request = request.headers(custom_headers); + + // Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists). + fn parse_response(mut response: hyper::client::response::Response) -> Result { + match response.status.to_u16() { + 200 => { + let mut buf = String::new(); + response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?; + let body = serde_json::from_str::>(&buf)?; + + Ok(GetContainerHistoryResponse::FoundEntityHistory(body)) + } + 400 => { + let mut buf = String::new(); + response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?; + let body = serde_json::from_str::(&buf)?; + + Ok(GetContainerHistoryResponse::BadRequest(body)) + } + 404 => { + let mut buf = String::new(); + response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?; + let body = serde_json::from_str::(&buf)?; + + Ok(GetContainerHistoryResponse::NotFound(body)) + } + 500 => { + let mut buf = String::new(); + response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?; + let body = serde_json::from_str::(&buf)?; + + Ok(GetContainerHistoryResponse::GenericError(body)) + } + code => { + let mut buf = [0; 100]; + let debug_body = match response.read(&mut buf) { + Ok(len) => match str::from_utf8(&buf[..len]) { + Ok(body) => Cow::from(body), + Err(_) => Cow::from(format!("", &buf[..len].to_vec())), + }, + Err(e) => Cow::from(format!("", e)), + }; + Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", code, response.headers, debug_body))) + } + } + } + + let result = request.send().map_err(|e| ApiError(format!("No response received: {}", e))).and_then(parse_response); + Box::new(futures::done(result)) + } + fn get_creator(&self, param_id: String, context: &Context) -> Box + Send> { let url = format!("{}/v0/creator/{id}", self.base_path, id = utf8_percent_encode(¶m_id.to_string(), PATH_SEGMENT_ENCODE_SET)); diff --git a/rust/fatcat-api/src/lib.rs b/rust/fatcat-api/src/lib.rs index f45e113e..291dcaf4 100644 --- a/rust/fatcat-api/src/lib.rs +++ b/rust/fatcat-api/src/lib.rs @@ -186,6 +186,18 @@ pub enum GetContainerResponse { GenericError(models::ErrorResponse), } +#[derive(Debug, PartialEq)] +pub enum GetContainerHistoryResponse { + /// Found Entity History + FoundEntityHistory(Vec), + /// Bad Request + BadRequest(models::ErrorResponse), + /// Not Found + NotFound(models::ErrorResponse), + /// Generic Error + GenericError(models::ErrorResponse), +} + #[derive(Debug, PartialEq)] pub enum GetCreatorResponse { /// Found Entity @@ -386,6 +398,8 @@ pub trait Api { fn get_container(&self, id: String, context: &Context) -> Box + Send>; + fn get_container_history(&self, id: String, limit: Option, context: &Context) -> Box + Send>; + fn get_creator(&self, id: String, context: &Context) -> Box + Send>; fn get_creator_releases(&self, id: String, context: &Context) -> Box + Send>; @@ -445,6 +459,8 @@ pub trait ApiNoContext { fn get_container(&self, id: String) -> Box + Send>; + fn get_container_history(&self, id: String, limit: Option) -> Box + Send>; + fn get_creator(&self, id: String) -> Box + Send>; fn get_creator_releases(&self, id: String) -> Box + Send>; @@ -544,6 +560,10 @@ impl<'a, T: Api> ApiNoContext for ContextWrapper<'a, T> { self.api().get_container(id, &self.context()) } + fn get_container_history(&self, id: String, limit: Option) -> Box + Send> { + self.api().get_container_history(id, limit, &self.context()) + } + fn get_creator(&self, id: String) -> Box + Send> { self.api().get_creator(id, &self.context()) } diff --git a/rust/fatcat-api/src/mimetypes.rs b/rust/fatcat-api/src/mimetypes.rs index be7e792f..0384b319 100644 --- a/rust/fatcat-api/src/mimetypes.rs +++ b/rust/fatcat-api/src/mimetypes.rs @@ -208,6 +208,22 @@ pub mod responses { lazy_static! { pub static ref GET_CONTAINER_GENERIC_ERROR: Mime = mime!(Application / Json); } + /// Create Mime objects for the response content types for GetContainerHistory + lazy_static! { + pub static ref GET_CONTAINER_HISTORY_FOUND_ENTITY_HISTORY: Mime = mime!(Application / Json); + } + /// Create Mime objects for the response content types for GetContainerHistory + lazy_static! { + pub static ref GET_CONTAINER_HISTORY_BAD_REQUEST: Mime = mime!(Application / Json); + } + /// Create Mime objects for the response content types for GetContainerHistory + lazy_static! { + pub static ref GET_CONTAINER_HISTORY_NOT_FOUND: Mime = mime!(Application / Json); + } + /// Create Mime objects for the response content types for GetContainerHistory + lazy_static! { + pub static ref GET_CONTAINER_HISTORY_GENERIC_ERROR: Mime = mime!(Application / Json); + } /// Create Mime objects for the response content types for GetCreator lazy_static! { pub static ref GET_CREATOR_FOUND_ENTITY: Mime = mime!(Application / Json); diff --git a/rust/fatcat-api/src/models.rs b/rust/fatcat-api/src/models.rs index 7ff39789..f33ed9db 100644 --- a/rust/fatcat-api/src/models.rs +++ b/rust/fatcat-api/src/models.rs @@ -275,68 +275,7 @@ impl EntityEdit { } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct EntityHistory(Vec); - -impl ::std::convert::From> for EntityHistory { - fn from(x: Vec) -> Self { - EntityHistory(x) - } -} - -impl ::std::convert::From for Vec { - fn from(x: EntityHistory) -> Self { - x.0 - } -} - -impl ::std::iter::FromIterator for EntityHistory { - fn from_iter>(u: U) -> Self { - EntityHistory(Vec::::from_iter(u)) - } -} - -impl ::std::iter::IntoIterator for EntityHistory { - type Item = EntityHistoryInner; - type IntoIter = ::std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a> ::std::iter::IntoIterator for &'a EntityHistory { - type Item = &'a EntityHistoryInner; - type IntoIter = ::std::slice::Iter<'a, EntityHistoryInner>; - - fn into_iter(self) -> Self::IntoIter { - (&self.0).into_iter() - } -} - -impl<'a> ::std::iter::IntoIterator for &'a mut EntityHistory { - type Item = &'a mut EntityHistoryInner; - type IntoIter = ::std::slice::IterMut<'a, EntityHistoryInner>; - - fn into_iter(self) -> Self::IntoIter { - (&mut self.0).into_iter() - } -} - -impl ::std::ops::Deref for EntityHistory { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl ::std::ops::DerefMut for EntityHistory { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct EntityHistoryInner { +pub struct EntityHistoryEntry { #[serde(rename = "edit")] pub edit: models::EntityEdit, @@ -347,9 +286,9 @@ pub struct EntityHistoryInner { pub changelog: models::ChangelogEntry, } -impl EntityHistoryInner { - pub fn new(edit: models::EntityEdit, editgroup: models::Editgroup, changelog: models::ChangelogEntry) -> EntityHistoryInner { - EntityHistoryInner { +impl EntityHistoryEntry { + pub fn new(edit: models::EntityEdit, editgroup: models::Editgroup, changelog: models::ChangelogEntry) -> EntityHistoryEntry { + EntityHistoryEntry { edit: edit, editgroup: editgroup, changelog: changelog, diff --git a/rust/fatcat-api/src/server.rs b/rust/fatcat-api/src/server.rs index faa9e293..f01dde1b 100644 --- a/rust/fatcat-api/src/server.rs +++ b/rust/fatcat-api/src/server.rs @@ -37,9 +37,9 @@ use swagger::{ApiError, Context, XSpanId}; #[allow(unused_imports)] use models; use {AcceptEditgroupResponse, Api, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, CreateFileBatchResponse, - CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerResponse, GetCreatorReleasesResponse, GetCreatorResponse, - GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseResponse, GetStatsResponse, GetWorkReleasesResponse, GetWorkResponse, - LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; + CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerHistoryResponse, GetContainerResponse, + GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseResponse, + GetStatsResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; header! { (Warning, "Warning") => [String] } @@ -1415,6 +1415,98 @@ where "GetContainer", ); + let api_clone = api.clone(); + router.get( + "/v0/container/:id/history", + move |req: &mut Request| { + let mut context = Context::default(); + + // Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists). + fn handle_request(req: &mut Request, api: &T, context: &mut Context) -> Result + where + T: Api, + { + context.x_span_id = Some(req.headers.get::().map(XSpanId::to_string).unwrap_or_else(|| self::uuid::Uuid::new_v4().to_string())); + context.auth_data = req.extensions.remove::(); + context.authorization = req.extensions.remove::(); + + // Path parameters + let param_id = { + let param = req.extensions + .get::() + .ok_or_else(|| Response::with((status::InternalServerError, "An internal error occurred".to_string())))? + .find("id") + .ok_or_else(|| Response::with((status::BadRequest, "Missing path parameter id".to_string())))?; + percent_decode(param.as_bytes()) + .decode_utf8() + .map_err(|_| Response::with((status::BadRequest, format!("Couldn't percent-decode path parameter as UTF-8: {}", param))))? + .parse() + .map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse path parameter id: {}", e))))? + }; + + // Query parameters (note that non-required or collection query parameters will ignore garbage values, rather than causing a 400 response) + let query_params = req.get::().unwrap_or_default(); + let param_limit = query_params.get("limit").and_then(|list| list.first()).and_then(|x| x.parse::().ok()); + + match api.get_container_history(param_id, param_limit, context).wait() { + Ok(rsp) => match rsp { + GetContainerHistoryResponse::FoundEntityHistory(body) => { + let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize"); + + let mut response = Response::with((status::Status::from_u16(200), body_string)); + response.headers.set(ContentType(mimetypes::responses::GET_CONTAINER_HISTORY_FOUND_ENTITY_HISTORY.clone())); + + context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone()))); + + Ok(response) + } + GetContainerHistoryResponse::BadRequest(body) => { + let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize"); + + let mut response = Response::with((status::Status::from_u16(400), body_string)); + response.headers.set(ContentType(mimetypes::responses::GET_CONTAINER_HISTORY_BAD_REQUEST.clone())); + + context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone()))); + + Ok(response) + } + GetContainerHistoryResponse::NotFound(body) => { + let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize"); + + let mut response = Response::with((status::Status::from_u16(404), body_string)); + response.headers.set(ContentType(mimetypes::responses::GET_CONTAINER_HISTORY_NOT_FOUND.clone())); + + context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone()))); + + Ok(response) + } + GetContainerHistoryResponse::GenericError(body) => { + let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize"); + + let mut response = Response::with((status::Status::from_u16(500), body_string)); + response.headers.set(ContentType(mimetypes::responses::GET_CONTAINER_HISTORY_GENERIC_ERROR.clone())); + + context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone()))); + + Ok(response) + } + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + Err(Response::with((status::InternalServerError, "An internal error occurred".to_string()))) + } + } + } + + handle_request(req, &api_clone, &mut context).or_else(|mut response| { + context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone()))); + Ok(response) + }) + }, + "GetContainerHistory", + ); + let api_clone = api.clone(); router.get( "/v0/creator/:id", diff --git a/rust/src/api_server.rs b/rust/src/api_server.rs index aaf3d9a7..efe40165 100644 --- a/rust/src/api_server.rs +++ b/rust/src/api_server.rs @@ -129,6 +129,31 @@ macro_rules! wrap_lookup_handler { } } +macro_rules! wrap_history_handler { + ($get_fn:ident, $get_handler:ident, $get_resp:ident) => { + fn $get_fn( + &self, + id: String, + limit: Option, + _context: &Context, + ) -> Box + Send> { + let ret = match self.$get_handler(id.clone(), limit) { + Ok(history) => + $get_resp::FoundEntityHistory(history), + Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => + $get_resp::NotFound(ErrorResponse { message: format!("No such entity {}: {}", stringify!($model), id) }), + Err(Error(ErrorKind::Uuid(e), _)) => + $get_resp::BadRequest(ErrorResponse { message: e.to_string() }), + Err(e) => { + error!("{}", e); + $get_resp::GenericError(ErrorResponse { message: e.to_string() }) + }, + }; + Box::new(futures::done(Ok(ret))) + } + } +} + macro_rules! count_entity { ($table:ident, $conn:expr) => {{ let count: i64 = $table::table @@ -350,6 +375,32 @@ impl Server { container_row2entity(Some(ident), rev) } + fn get_container_history_handler( + &self, + id: String, + limit: Option, + ) -> Result> { + let conn = self.db_pool.get().expect("db_pool error"); + let id = uuid::Uuid::parse_str(&id)?; + let limit = limit.unwrap_or(50); + + let rows: Vec<(EditgroupRow, ChangelogRow, ContainerEditRow)> = editgroup::table + .inner_join(changelog::table) + .inner_join(container_edit::table) + .filter(container_edit::ident_id.eq(id)) + .limit(limit) + .get_results(&conn)?; + + let history: Vec = rows.into_iter() + .map(|(eg_row, cl_row, ce_row)| EntityHistoryEntry { + edit: ce_row.to_model().expect("edit row to model"), + editgroup: eg_row.to_model_partial(), + changelog_entry: cl_row.to_model(), + }) + .collect(); + Ok(history) + } + fn get_creator_handler(&self, id: String) -> Result { let conn = self.db_pool.get().expect("db_pool error"); let id = uuid::Uuid::parse_str(&id)?; @@ -1084,6 +1135,11 @@ impl Api for Server { doi, String ); + wrap_history_handler!( + get_container_history, + get_container_history_handler, + GetContainerHistoryResponse + ); // Rename "wrap_lookup_handler"? wrap_lookup_handler!( diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs index dd84748d..6fa9b7b7 100644 --- a/rust/src/database_models.rs +++ b/rust/src/database_models.rs @@ -1,7 +1,7 @@ use chrono; use database_schema::*; use errors::*; -use fatcat_api::models::EntityEdit; +use fatcat_api::models::{ChangelogEntry, Editgroup, EntityEdit}; use serde_json; use uuid::Uuid; @@ -243,6 +243,20 @@ pub struct EditgroupRow { pub description: Option, } +impl EditgroupRow { + /// Returns an Edigroup API model *without* the entity edits actually populated. Useful for, + /// eg, entity history queries (where we already have the entity edit we want) + pub fn to_model_partial(self) -> Editgroup { + Editgroup { + id: Some(self.id), + editor_id: self.editor_id, + description: self.description, + extra: self.extra_json, + edits: None, + } + } +} + #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "editor"] pub struct EditorRow { @@ -259,3 +273,13 @@ pub struct ChangelogRow { pub editgroup_id: i64, pub timestamp: chrono::NaiveDateTime, } + +impl ChangelogRow { + pub fn to_model(self) -> ChangelogEntry { + ChangelogEntry { + index: self.id, + editgroup_id: self.editgroup_id, + timestamp: chrono::DateTime::from_utc(self.timestamp, chrono::Utc), + } + } +} diff --git a/rust/tests/test_api_server.rs b/rust/tests/test_api_server.rs index 3db01f65..aadd65af 100644 --- a/rust/tests/test_api_server.rs +++ b/rust/tests/test_api_server.rs @@ -111,6 +111,62 @@ fn test_entity_404() { ); } +#[test] +fn test_entity_history() { + let (headers, router, _conn) = setup(); + + check_response( + request::get( + "http://localhost:9411/v0/container/00000000-0000-0000-1111-000000000002/history", + headers.clone(), + &router, + ), + status::Ok, + Some("changelog"), + ); + /* + check_response( + request::get( + "http://localhost:9411/v0/creator/00000000-0000-0000-2222-000000000001/history", + headers.clone(), + &router, + ), + status::Ok, + Some("changelog"), + ); + + check_response( + request::get( + "http://localhost:9411/v0/file/00000000-0000-0000-3333-000000000002/history", + headers.clone(), + &router, + ), + status::Ok, + Some("changelog"), + ); + + check_response( + request::get( + "http://localhost:9411/v0/release/00000000-0000-0000-4444-000000000002/history", + headers.clone(), + &router, + ), + status::Ok, + Some("changelog"), + ); + + check_response( + request::get( + "http://localhost:9411/v0/work/00000000-0000-0000-5555-000000000002/history", + headers.clone(), + &router, + ), + status::Ok, + Some("changelog"), + ); +*/ +} + #[test] fn test_lookups() { let (headers, router, _conn) = setup(); -- cgit v1.2.3