aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2018-06-30 17:34:22 -0700
committerBryan Newbold <bnewbold@robocracy.org>2018-06-30 17:34:22 -0700
commitf4064c19ec140987e15c64e80a3bf0c70025b31b (patch)
treee89548d5d5f350f65e9c294a34c71a2225a14099
parent3ed7db573438d3620d295813a81237acb91155cb (diff)
downloadfatcat-f4064c19ec140987e15c64e80a3bf0c70025b31b.tar.gz
fatcat-f4064c19ec140987e15c64e80a3bf0c70025b31b.zip
history for container entities
-rw-r--r--fatcat-openapi2.yml49
-rw-r--r--rust/fatcat-api/README.md3
-rw-r--r--rust/fatcat-api/api.yaml49
-rw-r--r--rust/fatcat-api/api/swagger.yaml180
-rw-r--r--rust/fatcat-api/examples/client.rs11
-rw-r--r--rust/fatcat-api/examples/server_lib/server.rs17
-rw-r--r--rust/fatcat-api/src/client.rs74
-rw-r--r--rust/fatcat-api/src/lib.rs20
-rw-r--r--rust/fatcat-api/src/mimetypes.rs16
-rw-r--r--rust/fatcat-api/src/models.rs69
-rw-r--r--rust/fatcat-api/src/server.rs98
-rw-r--r--rust/src/api_server.rs56
-rw-r--r--rust/src/database_models.rs26
-rw-r--r--rust/tests/test_api_server.rs56
14 files changed, 596 insertions, 128 deletions
diff --git a/fatcat-openapi2.yml b/fatcat-openapi2.yml
index 1796f32f..bdcb3ca8 100644
--- a/fatcat-openapi2.yml
+++ b/fatcat-openapi2.yml
@@ -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/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<ENTITYHISTORYENTRY>"
+ 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("<none>")));
}
+ 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("<none>")));
+ }
+
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("<none>")));
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<i64>, context: &Context) -> Box<Future<Item = GetContainerHistoryResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "get_container_history(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ limit,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
fn get_creator(&self, id: String, context: &Context) -> Box<Future<Item = GetCreatorResponse, Error = ApiError> + Send> {
let context = context.clone();
println!("get_creator(\"{}\") - X-Span-ID: {:?}", id, context.x_span_id.unwrap_or(String::from("<none>")).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<T: IntoUrl>(input: T, correct_scheme: Option<&'static str>) -> Result<String, ClientInitError> {
@@ -990,6 +990,74 @@ impl Api for Client {
Box::new(futures::done(result))
}
+ fn get_container_history(&self, param_id: String, param_limit: Option<i64>, context: &Context) -> Box<Future<Item = GetContainerHistoryResponse, Error = ApiError> + 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(&param_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<GetContainerHistoryResponse, ApiError> {
+ 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::<Vec<models::EntityHistoryEntry>>(&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::<models::ErrorResponse>(&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::<models::ErrorResponse>(&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::<models::ErrorResponse>(&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!("<Body was not UTF8: {:?}>", &buf[..len].to_vec())),
+ },
+ Err(e) => Cow::from(format!("<Failed to read body: {}>", 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<Future<Item = GetCreatorResponse, Error = ApiError> + Send> {
let url = format!("{}/v0/creator/{id}", self.base_path, id = utf8_percent_encode(&param_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
@@ -187,6 +187,18 @@ pub enum GetContainerResponse {
}
#[derive(Debug, PartialEq)]
+pub enum GetContainerHistoryResponse {
+ /// Found Entity History
+ FoundEntityHistory(Vec<models::EntityHistoryEntry>),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
pub enum GetCreatorResponse {
/// Found Entity
FoundEntity(models::CreatorEntity),
@@ -386,6 +398,8 @@ pub trait Api {
fn get_container(&self, id: String, context: &Context) -> Box<Future<Item = GetContainerResponse, Error = ApiError> + Send>;
+ fn get_container_history(&self, id: String, limit: Option<i64>, context: &Context) -> Box<Future<Item = GetContainerHistoryResponse, Error = ApiError> + Send>;
+
fn get_creator(&self, id: String, context: &Context) -> Box<Future<Item = GetCreatorResponse, Error = ApiError> + Send>;
fn get_creator_releases(&self, id: String, context: &Context) -> Box<Future<Item = GetCreatorReleasesResponse, Error = ApiError> + Send>;
@@ -445,6 +459,8 @@ pub trait ApiNoContext {
fn get_container(&self, id: String) -> Box<Future<Item = GetContainerResponse, Error = ApiError> + Send>;
+ fn get_container_history(&self, id: String, limit: Option<i64>) -> Box<Future<Item = GetContainerHistoryResponse, Error = ApiError> + Send>;
+
fn get_creator(&self, id: String) -> Box<Future<Item = GetCreatorResponse, Error = ApiError> + Send>;
fn get_creator_releases(&self, id: String) -> Box<Future<Item = GetCreatorReleasesResponse, Error = ApiError> + 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<i64>) -> Box<Future<Item = GetContainerHistoryResponse, Error = ApiError> + Send> {
+ self.api().get_container_history(id, limit, &self.context())
+ }
+
fn get_creator(&self, id: String) -> Box<Future<Item = GetCreatorResponse, Error = ApiError> + 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<EntityHistoryInner>);
-
-impl ::std::convert::From<Vec<EntityHistoryInner>> for EntityHistory {
- fn from(x: Vec<EntityHistoryInner>) -> Self {
- EntityHistory(x)
- }
-}
-
-impl ::std::convert::From<EntityHistory> for Vec<EntityHistoryInner> {
- fn from(x: EntityHistory) -> Self {
- x.0
- }
-}
-
-impl ::std::iter::FromIterator<EntityHistoryInner> for EntityHistory {
- fn from_iter<U: IntoIterator<Item = EntityHistoryInner>>(u: U) -> Self {
- EntityHistory(Vec::<EntityHistoryInner>::from_iter(u))
- }
-}
-
-impl ::std::iter::IntoIterator for EntityHistory {
- type Item = EntityHistoryInner;
- type IntoIter = ::std::vec::IntoIter<EntityHistoryInner>;
-
- 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<EntityHistoryInner>;
- 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] }
@@ -1417,6 +1417,98 @@ where
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<T>(req: &mut Request, api: &T, context: &mut Context) -> Result<Response, Response>
+ where
+ T: Api,
+ {
+ context.x_span_id = Some(req.headers.get::<XSpanId>().map(XSpanId::to_string).unwrap_or_else(|| self::uuid::Uuid::new_v4().to_string()));
+ context.auth_data = req.extensions.remove::<AuthData>();
+ context.authorization = req.extensions.remove::<Authorization>();
+
+ // Path parameters
+ let param_id = {
+ let param = req.extensions
+ .get::<Router>()
+ .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::<UrlEncodedQuery>().unwrap_or_default();
+ let param_limit = query_params.get("limit").and_then(|list| list.first()).and_then(|x| x.parse::<i64>().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",
move |req: &mut Request| {
let mut context = Context::default();
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<i64>,
+ _context: &Context,
+ ) -> Box<Future<Item = $get_resp, Error = ApiError> + 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<i64>,
+ ) -> Result<Vec<EntityHistoryEntry>> {
+ 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<EntityHistoryEntry> = 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<CreatorEntity> {
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<String>,
}
+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
@@ -112,6 +112,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();