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 /rust | |
| parent | 08f7f1642eb8380c5b00f6a54e4b29e55713effd (diff) | |
| download | fatcat-c88af3a8a92329a598287b5dd3457030e3b4529f.tar.gz fatcat-c88af3a8a92329a598287b5dd3457030e3b4529f.zip | |
generic changelog endpoints
Diffstat (limited to 'rust')
| -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 | 
13 files changed, 706 insertions, 25 deletions
| 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"),      ); | 
