diff options
author | Bryan Newbold <bnewbold@robocracy.org> | 2018-06-30 18:40:27 -0700 |
---|---|---|
committer | Bryan Newbold <bnewbold@robocracy.org> | 2018-06-30 18:40:27 -0700 |
commit | c88af3a8a92329a598287b5dd3457030e3b4529f (patch) | |
tree | 12db7b14015ec028f02c1c329612552db7f8b74f | |
parent | 08f7f1642eb8380c5b00f6a54e4b29e55713effd (diff) | |
download | fatcat-c88af3a8a92329a598287b5dd3457030e3b4529f.tar.gz fatcat-c88af3a8a92329a598287b5dd3457030e3b4529f.zip |
generic changelog endpoints
-rw-r--r-- | fatcat-openapi2.yml | 44 | ||||
-rw-r--r-- | rust/fatcat-api/README.md | 4 | ||||
-rw-r--r-- | rust/fatcat-api/api.yaml | 44 | ||||
-rw-r--r-- | rust/fatcat-api/api/swagger.yaml | 225 | ||||
-rw-r--r-- | rust/fatcat-api/examples/client.rs | 18 | ||||
-rw-r--r-- | rust/fatcat-api/examples/server_lib/server.rs | 20 | ||||
-rw-r--r-- | rust/fatcat-api/src/client.rs | 110 | ||||
-rw-r--r-- | rust/fatcat-api/src/lib.rs | 34 | ||||
-rw-r--r-- | rust/fatcat-api/src/mimetypes.rs | 20 | ||||
-rw-r--r-- | rust/fatcat-api/src/models.rs | 5 | ||||
-rw-r--r-- | rust/fatcat-api/src/server.rs | 144 | ||||
-rw-r--r-- | rust/src/api_server.rs | 85 | ||||
-rw-r--r-- | rust/src/database_models.rs | 1 | ||||
-rw-r--r-- | rust/tests/test_api_server.rs | 21 |
14 files changed, 750 insertions, 25 deletions
diff --git a/fatcat-openapi2.yml b/fatcat-openapi2.yml index 3d0690c6..e1c44063 100644 --- a/fatcat-openapi2.yml +++ b/fatcat-openapi2.yml @@ -286,6 +286,8 @@ definitions: timestamp: type: string format: date-time + editgroup: + $ref: "#/definitions/editgroup" release_ref: type: object properties: @@ -911,6 +913,48 @@ paths: description: Generic Error schema: $ref: "#/definitions/error_response" + /changelog: + parameters: + - name: limit + in: query + type: integer + format: int64 + required: false + get: + operationId: "get_changelog" + responses: + 200: + description: Success + schema: + type: array + items: + $ref: "#/definitions/changelog_entry" + 500: + description: Generic Error + schema: + $ref: "#/definitions/error_response" + /changelog/{id}: + parameters: + - name: id + in: path + type: integer + format: int64 + required: true + get: + operationId: "get_changelog_entry" + responses: + 200: + description: Found Changelog Entry + schema: + $ref: "#/definitions/changelog_entry" + 404: + description: Not Found + schema: + $ref: "#/definitions/error_response" + 500: + description: Generic Error + schema: + $ref: "#/definitions/error_response" /stats: get: operationId: "get_stats" diff --git a/rust/fatcat-api/README.md b/rust/fatcat-api/README.md index 12673cfe..c693f229 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-07-01T00:51:27.691Z +- Build date: 2018-07-01T01:27:53.353Z This autogenerated project defines an API crate `fatcat` which contains: * An `Api` trait defining the API in Rust. @@ -68,6 +68,8 @@ cargo run --example client CreateRelease cargo run --example client CreateReleaseBatch cargo run --example client CreateWork cargo run --example client CreateWorkBatch +cargo run --example client GetChangelog +cargo run --example client GetChangelogEntry cargo run --example client GetContainer cargo run --example client GetContainerHistory cargo run --example client GetCreator diff --git a/rust/fatcat-api/api.yaml b/rust/fatcat-api/api.yaml index 3d0690c6..e1c44063 100644 --- a/rust/fatcat-api/api.yaml +++ b/rust/fatcat-api/api.yaml @@ -286,6 +286,8 @@ definitions: timestamp: type: string format: date-time + editgroup: + $ref: "#/definitions/editgroup" release_ref: type: object properties: @@ -911,6 +913,48 @@ paths: description: Generic Error schema: $ref: "#/definitions/error_response" + /changelog: + parameters: + - name: limit + in: query + type: integer + format: int64 + required: false + get: + operationId: "get_changelog" + responses: + 200: + description: Success + schema: + type: array + items: + $ref: "#/definitions/changelog_entry" + 500: + description: Generic Error + schema: + $ref: "#/definitions/error_response" + /changelog/{id}: + parameters: + - name: id + in: path + type: integer + format: int64 + required: true + get: + operationId: "get_changelog_entry" + responses: + 200: + description: Found Changelog Entry + schema: + $ref: "#/definitions/changelog_entry" + 404: + description: Not Found + schema: + $ref: "#/definitions/error_response" + 500: + description: Generic Error + schema: + $ref: "#/definitions/error_response" /stats: get: operationId: "get_stats" diff --git a/rust/fatcat-api/api/swagger.yaml b/rust/fatcat-api/api/swagger.yaml index 15ea229a..d376a078 100644 --- a/rust/fatcat-api/api/swagger.yaml +++ b/rust/fatcat-api/api/swagger.yaml @@ -1787,6 +1787,87 @@ paths: path: "/editgroup/:id/accept" HttpMethod: "Post" httpmethod: "post" + /changelog: + get: + operationId: "get_changelog" + parameters: + - name: "limit" + in: "query" + required: false + type: "integer" + format: "int64" + formatString: "{:?}" + example: "Some(789)" + responses: + 200: + description: "Success" + schema: + type: "array" + items: + $ref: "#/definitions/changelog_entry" + x-responseId: "Success" + x-uppercaseResponseId: "SUCCESS" + uppercase_operation_id: "GET_CHANGELOG" + uppercase_data_type: "VEC<CHANGELOGENTRY>" + producesJson: true + 500: + description: "Generic Error" + schema: + $ref: "#/definitions/error_response" + x-responseId: "GenericError" + x-uppercaseResponseId: "GENERIC_ERROR" + uppercase_operation_id: "GET_CHANGELOG" + uppercase_data_type: "ERRORRESPONSE" + producesJson: true + operation_id: "get_changelog" + uppercase_operation_id: "GET_CHANGELOG" + path: "/changelog" + HttpMethod: "Get" + httpmethod: "get" + /changelog/{id}: + get: + operationId: "get_changelog_entry" + parameters: + - name: "id" + in: "path" + required: true + type: "integer" + format: "int64" + formatString: "{}" + example: "789" + responses: + 200: + description: "Found Changelog Entry" + schema: + $ref: "#/definitions/changelog_entry" + x-responseId: "FoundChangelogEntry" + x-uppercaseResponseId: "FOUND_CHANGELOG_ENTRY" + uppercase_operation_id: "GET_CHANGELOG_ENTRY" + uppercase_data_type: "CHANGELOGENTRY" + producesJson: true + 404: + description: "Not Found" + schema: + $ref: "#/definitions/error_response" + x-responseId: "NotFound" + x-uppercaseResponseId: "NOT_FOUND" + uppercase_operation_id: "GET_CHANGELOG_ENTRY" + 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_CHANGELOG_ENTRY" + uppercase_data_type: "ERRORRESPONSE" + producesJson: true + operation_id: "get_changelog_entry" + uppercase_operation_id: "GET_CHANGELOG_ENTRY" + path: "/changelog/:id" + HttpMethod: "Get" + httpmethod: "get" /stats: get: operationId: "get_stats" @@ -2260,6 +2341,77 @@ definitions: editgroup_id: 16 revision: 42 changelog_entry: + 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 index: 1 editgroup_id: 5 timestamp: "2000-01-23T04:56:07.000+00:00" @@ -2415,7 +2567,80 @@ definitions: timestamp: type: "string" format: "date-time" + editgroup: + $ref: "#/definitions/editgroup" 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 index: 1 editgroup_id: 5 timestamp: "2000-01-23T04:56:07.000+00:00" diff --git a/rust/fatcat-api/examples/client.rs b/rust/fatcat-api/examples/client.rs index eecb0c58..c79110d9 100644 --- a/rust/fatcat-api/examples/client.rs +++ b/rust/fatcat-api/examples/client.rs @@ -13,9 +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, - GetContainerHistoryResponse, GetContainerResponse, GetCreatorHistoryResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, - GetEditorResponse, GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, - GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; + GetChangelogEntryResponse, GetChangelogResponse, GetContainerHistoryResponse, GetContainerResponse, GetCreatorHistoryResponse, GetCreatorReleasesResponse, GetCreatorResponse, + GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseHistoryResponse, GetReleaseResponse, + GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; #[allow(unused_imports)] use futures::{future, stream, Future, Stream}; @@ -31,6 +31,8 @@ fn main() { "CreateFileBatch", "CreateReleaseBatch", "CreateWorkBatch", + "GetChangelog", + "GetChangelogEntry", "GetContainer", "GetContainerHistory", "GetCreator", @@ -141,6 +143,16 @@ fn main() { println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>"))); } + Some("GetChangelog") => { + let result = client.get_changelog(Some(789)).wait(); + println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>"))); + } + + Some("GetChangelogEntry") => { + let result = client.get_changelog_entry(789).wait(); + println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>"))); + } + Some("GetContainer") => { let result = client.get_container("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 062fca02..76d7d13c 100644 --- a/rust/fatcat-api/examples/server_lib/server.rs +++ b/rust/fatcat-api/examples/server_lib/server.rs @@ -11,10 +11,10 @@ use swagger; use fatcat::models; use fatcat::{AcceptEditgroupResponse, Api, ApiError, Context, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, - CreateFileBatchResponse, CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerHistoryResponse, - GetContainerResponse, GetCreatorHistoryResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, - GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, - GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; + CreateFileBatchResponse, CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetChangelogEntryResponse, + GetChangelogResponse, GetContainerHistoryResponse, GetContainerResponse, GetCreatorHistoryResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, + GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse, GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, + GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; #[derive(Copy, Clone)] pub struct Server; @@ -96,6 +96,18 @@ impl Api for Server { Box::new(futures::failed("Generic failure".into())) } + fn get_changelog(&self, limit: Option<i64>, context: &Context) -> Box<Future<Item = GetChangelogResponse, Error = ApiError> + Send> { + let context = context.clone(); + println!("get_changelog({:?}) - X-Span-ID: {:?}", limit, context.x_span_id.unwrap_or(String::from("<none>")).clone()); + Box::new(futures::failed("Generic failure".into())) + } + + fn get_changelog_entry(&self, id: i64, context: &Context) -> Box<Future<Item = GetChangelogEntryResponse, Error = ApiError> + Send> { + let context = context.clone(); + println!("get_changelog_entry({}) - X-Span-ID: {:?}", id, context.x_span_id.unwrap_or(String::from("<none>")).clone()); + Box::new(futures::failed("Generic failure".into())) + } + fn get_container(&self, id: String, context: &Context) -> Box<Future<Item = GetContainerResponse, Error = ApiError> + Send> { let context = context.clone(); println!("get_container(\"{}\") - 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 54e8b86c..b68c43d2 100644 --- a/rust/fatcat-api/src/client.rs +++ b/rust/fatcat-api/src/client.rs @@ -35,10 +35,10 @@ use swagger::{ApiError, Context, XSpanId}; use models; use {AcceptEditgroupResponse, Api, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, CreateFileBatchResponse, - CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerHistoryResponse, GetContainerResponse, GetCreatorHistoryResponse, - GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse, - GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, - LookupFileResponse, LookupReleaseResponse}; + CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerHistoryResponse, + GetContainerResponse, GetCreatorHistoryResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, + GetFileResponse, GetReleaseFilesResponse, GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, 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> { @@ -931,6 +931,108 @@ impl Api for Client { Box::new(futures::done(result)) } + fn get_changelog(&self, param_limit: Option<i64>, context: &Context) -> Box<Future<Item = GetChangelogResponse, 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/changelog?{limit}", self.base_path, 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<GetChangelogResponse, 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::ChangelogEntry>>(&buf)?; + + Ok(GetChangelogResponse::Success(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(GetChangelogResponse::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_changelog_entry(&self, param_id: i64, context: &Context) -> Box<Future<Item = GetChangelogEntryResponse, Error = ApiError> + Send> { + let url = format!("{}/v0/changelog/{id}", self.base_path, id = utf8_percent_encode(¶m_id.to_string(), PATH_SEGMENT_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<GetChangelogEntryResponse, 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::<models::ChangelogEntry>(&buf)?; + + Ok(GetChangelogEntryResponse::FoundChangelogEntry(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(GetChangelogEntryResponse::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(GetChangelogEntryResponse::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_container(&self, param_id: String, context: &Context) -> Box<Future<Item = GetContainerResponse, Error = ApiError> + Send> { let url = format!("{}/v0/container/{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 28636d47..ce5674cd 100644 --- a/rust/fatcat-api/src/lib.rs +++ b/rust/fatcat-api/src/lib.rs @@ -175,6 +175,24 @@ pub enum CreateWorkBatchResponse { } #[derive(Debug, PartialEq)] +pub enum GetChangelogResponse { + /// Success + Success(Vec<models::ChangelogEntry>), + /// Generic Error + GenericError(models::ErrorResponse), +} + +#[derive(Debug, PartialEq)] +pub enum GetChangelogEntryResponse { + /// Found Changelog Entry + FoundChangelogEntry(models::ChangelogEntry), + /// Not Found + NotFound(models::ErrorResponse), + /// Generic Error + GenericError(models::ErrorResponse), +} + +#[derive(Debug, PartialEq)] pub enum GetContainerResponse { /// Found Entity FoundEntity(models::ContainerEntity), @@ -444,6 +462,10 @@ pub trait Api { fn create_work_batch(&self, entity_list: &Vec<models::WorkEntity>, context: &Context) -> Box<Future<Item = CreateWorkBatchResponse, Error = ApiError> + Send>; + fn get_changelog(&self, limit: Option<i64>, context: &Context) -> Box<Future<Item = GetChangelogResponse, Error = ApiError> + Send>; + + fn get_changelog_entry(&self, id: i64, context: &Context) -> Box<Future<Item = GetChangelogEntryResponse, Error = ApiError> + Send>; + 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>; @@ -513,6 +535,10 @@ pub trait ApiNoContext { fn create_work_batch(&self, entity_list: &Vec<models::WorkEntity>) -> Box<Future<Item = CreateWorkBatchResponse, Error = ApiError> + Send>; + fn get_changelog(&self, limit: Option<i64>) -> Box<Future<Item = GetChangelogResponse, Error = ApiError> + Send>; + + fn get_changelog_entry(&self, id: i64) -> Box<Future<Item = GetChangelogEntryResponse, Error = ApiError> + Send>; + 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>; @@ -620,6 +646,14 @@ impl<'a, T: Api> ApiNoContext for ContextWrapper<'a, T> { self.api().create_work_batch(entity_list, &self.context()) } + fn get_changelog(&self, limit: Option<i64>) -> Box<Future<Item = GetChangelogResponse, Error = ApiError> + Send> { + self.api().get_changelog(limit, &self.context()) + } + + fn get_changelog_entry(&self, id: i64) -> Box<Future<Item = GetChangelogEntryResponse, Error = ApiError> + Send> { + self.api().get_changelog_entry(id, &self.context()) + } + fn get_container(&self, id: String) -> Box<Future<Item = GetContainerResponse, Error = ApiError> + Send> { self.api().get_container(id, &self.context()) } diff --git a/rust/fatcat-api/src/mimetypes.rs b/rust/fatcat-api/src/mimetypes.rs index 8d67551a..53b582dc 100644 --- a/rust/fatcat-api/src/mimetypes.rs +++ b/rust/fatcat-api/src/mimetypes.rs @@ -192,6 +192,26 @@ pub mod responses { lazy_static! { pub static ref CREATE_WORK_BATCH_GENERIC_ERROR: Mime = mime!(Application / Json); } + /// Create Mime objects for the response content types for GetChangelog + lazy_static! { + pub static ref GET_CHANGELOG_SUCCESS: Mime = mime!(Application / Json); + } + /// Create Mime objects for the response content types for GetChangelog + lazy_static! { + pub static ref GET_CHANGELOG_GENERIC_ERROR: Mime = mime!(Application / Json); + } + /// Create Mime objects for the response content types for GetChangelogEntry + lazy_static! { + pub static ref GET_CHANGELOG_ENTRY_FOUND_CHANGELOG_ENTRY: Mime = mime!(Application / Json); + } + /// Create Mime objects for the response content types for GetChangelogEntry + lazy_static! { + pub static ref GET_CHANGELOG_ENTRY_NOT_FOUND: Mime = mime!(Application / Json); + } + /// Create Mime objects for the response content types for GetChangelogEntry + lazy_static! { + pub static ref GET_CHANGELOG_ENTRY_GENERIC_ERROR: Mime = mime!(Application / Json); + } /// Create Mime objects for the response content types for GetContainer lazy_static! { pub static ref GET_CONTAINER_FOUND_ENTITY: Mime = mime!(Application / Json); diff --git a/rust/fatcat-api/src/models.rs b/rust/fatcat-api/src/models.rs index 94edf5c0..cd18469c 100644 --- a/rust/fatcat-api/src/models.rs +++ b/rust/fatcat-api/src/models.rs @@ -19,6 +19,10 @@ pub struct ChangelogEntry { #[serde(rename = "timestamp")] pub timestamp: chrono::DateTime<chrono::Utc>, + + #[serde(rename = "editgroup")] + #[serde(skip_serializing_if = "Option::is_none")] + pub editgroup: Option<models::Editgroup>, } impl ChangelogEntry { @@ -27,6 +31,7 @@ impl ChangelogEntry { index: index, editgroup_id: editgroup_id, timestamp: timestamp, + editgroup: None, } } } diff --git a/rust/fatcat-api/src/server.rs b/rust/fatcat-api/src/server.rs index e881b313..35b31691 100644 --- a/rust/fatcat-api/src/server.rs +++ b/rust/fatcat-api/src/server.rs @@ -37,10 +37,10 @@ use swagger::{ApiError, Context, XSpanId}; #[allow(unused_imports)] use models; use {AcceptEditgroupResponse, Api, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, CreateFileBatchResponse, - CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetContainerHistoryResponse, GetContainerResponse, GetCreatorHistoryResponse, - GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse, - GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse, - LookupFileResponse, LookupReleaseResponse}; + CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerHistoryResponse, + GetContainerResponse, GetCreatorHistoryResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, + GetFileResponse, GetReleaseFilesResponse, GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, + LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse}; header! { (Warning, "Warning") => [String] } @@ -1330,6 +1330,142 @@ where let api_clone = api.clone(); router.get( + "/v0/changelog", + 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>(); + + // 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_changelog(param_limit, context).wait() { + Ok(rsp) => match rsp { + GetChangelogResponse::Success(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_CHANGELOG_SUCCESS.clone())); + + context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone()))); + + Ok(response) + } + GetChangelogResponse::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_CHANGELOG_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) + }) + }, + "GetChangelog", + ); + + let api_clone = api.clone(); + router.get( + "/v0/changelog/:id", + 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))))? + }; + + match api.get_changelog_entry(param_id, context).wait() { + Ok(rsp) => match rsp { + GetChangelogEntryResponse::FoundChangelogEntry(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_CHANGELOG_ENTRY_FOUND_CHANGELOG_ENTRY.clone())); + + context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone()))); + + Ok(response) + } + GetChangelogEntryResponse::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_CHANGELOG_ENTRY_NOT_FOUND.clone())); + + context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone()))); + + Ok(response) + } + GetChangelogEntryResponse::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_CHANGELOG_ENTRY_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) + }) + }, + "GetChangelogEntry", + ); + + let api_clone = api.clone(); + router.get( "/v0/container/: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 f895937e..fa767699 100644 --- a/rust/src/api_server.rs +++ b/rust/src/api_server.rs @@ -950,18 +950,50 @@ impl Server { .load(&conn)?; let entries = changes - .iter() - .map(|(row, _)| ChangelogEntry { - index: row.id, - editgroup_id: row.editgroup_id, - timestamp: chrono::DateTime::from_utc(row.timestamp, chrono::Utc), + .into_iter() + .map(|(cl_row, eg_row)| ChangelogEntry { + index: cl_row.id, + editgroup: Some(eg_row.to_model_partial()), + editgroup_id: cl_row.editgroup_id, + timestamp: chrono::DateTime::from_utc(cl_row.timestamp, chrono::Utc), }) .collect(); Ok(entries) } - /// "more" parameter isn't used, but could be to indicate that "expensive" database queries - /// should be run + fn get_changelog_handler(&self, limit: Option<i64>) -> Result<Vec<ChangelogEntry>> { + let conn = self.db_pool.get().expect("db_pool error"); + let limit = limit.unwrap_or(50); + + let changes: Vec<(ChangelogRow, EditgroupRow)> = changelog::table + .inner_join(editgroup::table) + .order(changelog::id.desc()) + .limit(limit) + .load(&conn)?; + + let entries = changes + .into_iter() + .map(|(cl_row, eg_row)| ChangelogEntry { + index: cl_row.id, + editgroup: Some(eg_row.to_model_partial()), + editgroup_id: cl_row.editgroup_id, + timestamp: chrono::DateTime::from_utc(cl_row.timestamp, chrono::Utc), + }) + .collect(); + Ok(entries) + } + + fn get_changelog_entry_handler(&self, id: i64) -> Result<ChangelogEntry> { + let conn = self.db_pool.get().expect("db_pool error"); + + let cl_row: ChangelogRow = changelog::table.find(id).first(&conn)?; + let editgroup = self.get_editgroup_handler(cl_row.editgroup_id)?; + + let mut entry = cl_row.to_model(); + entry.editgroup = Some(editgroup); + Ok(entry) + } + fn get_stats_handler(&self, more: Option<String>) -> Result<StatsResponse> { let conn = self.db_pool.get().expect("db_pool error"); @@ -1304,6 +1336,45 @@ impl Api for Server { Box::new(futures::done(Ok(ret))) } + fn get_changelog( + &self, + limit: Option<i64>, + _context: &Context, + ) -> Box<Future<Item = GetChangelogResponse, Error = ApiError> + Send> { + let ret = match self.get_changelog_handler(limit) { + Ok(changelog) => GetChangelogResponse::Success(changelog), + Err(e) => { + error!("{}", e); + GetChangelogResponse::GenericError(ErrorResponse { + message: e.to_string(), + }) + } + }; + Box::new(futures::done(Ok(ret))) + } + + fn get_changelog_entry( + &self, + id: i64, + _context: &Context, + ) -> Box<Future<Item = GetChangelogEntryResponse, Error = ApiError> + Send> { + let ret = match self.get_changelog_entry_handler(id) { + Ok(entry) => GetChangelogEntryResponse::FoundChangelogEntry(entry), + Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => { + GetChangelogEntryResponse::NotFound(ErrorResponse { + message: format!("No such changelog entry: {}", id), + }) + } + Err(e) => { + error!("{}", e); + GetChangelogEntryResponse::GenericError(ErrorResponse { + message: e.to_string(), + }) + } + }; + Box::new(futures::done(Ok(ret))) + } + fn get_stats( &self, more: Option<String>, diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs index 6fa9b7b7..8489b336 100644 --- a/rust/src/database_models.rs +++ b/rust/src/database_models.rs @@ -279,6 +279,7 @@ impl ChangelogRow { ChangelogEntry { index: self.id, editgroup_id: self.editgroup_id, + editgroup: None, 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 b46367e0..b00d22eb 100644 --- a/rust/tests/test_api_server.rs +++ b/rust/tests/test_api_server.rs @@ -501,16 +501,33 @@ fn test_accept_editgroup() { } #[test] +fn test_changelog() { + let (headers, router, _conn) = setup(); + + check_response( + request::get("http://localhost:9411/v0/changelog", headers.clone(), &router), + status::Ok, + Some("editgroup_id"), + ); + + check_response( + request::get("http://localhost:9411/v0/changelog/1", headers.clone(), &router), + status::Ok, + Some("files"), + ); +} + +#[test] fn test_stats() { let (headers, router, _conn) = setup(); check_response( - request::get("http://localhost:9411/v0/stats", headers, &router), + request::get("http://localhost:9411/v0/stats", headers.clone(), &router), status::Ok, Some("merged_editgroups"), ); check_response( - request::get("http://localhost:9411/v0/stats?more=yes", headers, &router), + request::get("http://localhost:9411/v0/stats?more=yes", headers.clone(), &router), status::Ok, Some("merged_editgroups"), ); |