diff options
| author | Bryan Newbold <bnewbold@robocracy.org> | 2018-06-30 17:34:22 -0700 | 
|---|---|---|
| committer | Bryan Newbold <bnewbold@robocracy.org> | 2018-06-30 17:34:22 -0700 | 
| commit | f4064c19ec140987e15c64e80a3bf0c70025b31b (patch) | |
| tree | e89548d5d5f350f65e9c294a34c71a2225a14099 /rust | |
| parent | 3ed7db573438d3620d295813a81237acb91155cb (diff) | |
| download | fatcat-f4064c19ec140987e15c64e80a3bf0c70025b31b.tar.gz fatcat-f4064c19ec140987e15c64e80a3bf0c70025b31b.zip | |
history for container entities
Diffstat (limited to 'rust')
| -rw-r--r-- | rust/fatcat-api/README.md | 3 | ||||
| -rw-r--r-- | rust/fatcat-api/api.yaml | 49 | ||||
| -rw-r--r-- | rust/fatcat-api/api/swagger.yaml | 180 | ||||
| -rw-r--r-- | rust/fatcat-api/examples/client.rs | 11 | ||||
| -rw-r--r-- | rust/fatcat-api/examples/server_lib/server.rs | 17 | ||||
| -rw-r--r-- | rust/fatcat-api/src/client.rs | 74 | ||||
| -rw-r--r-- | rust/fatcat-api/src/lib.rs | 20 | ||||
| -rw-r--r-- | rust/fatcat-api/src/mimetypes.rs | 16 | ||||
| -rw-r--r-- | rust/fatcat-api/src/models.rs | 69 | ||||
| -rw-r--r-- | rust/fatcat-api/src/server.rs | 98 | ||||
| -rw-r--r-- | rust/src/api_server.rs | 56 | ||||
| -rw-r--r-- | rust/src/database_models.rs | 26 | ||||
| -rw-r--r-- | rust/tests/test_api_server.rs | 56 | 
13 files changed, 562 insertions, 113 deletions
| 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(¶m_id.to_string(), PATH_SEGMENT_ENCODE_SET), +            limit = utf8_percent_encode(&query_limit, QUERY_ENCODE_SET) +        ); + +        let hyper_client = (self.hyper_client)(); +        let request = hyper_client.request(hyper::method::Method::Get, &url); +        let mut custom_headers = hyper::header::Headers::new(); + +        context.x_span_id.as_ref().map(|header| custom_headers.set(XSpanId(header.clone()))); + +        let request = request.headers(custom_headers); + +        // Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists). +        fn parse_response(mut response: hyper::client::response::Response) -> Result<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(¶m_id.to_string(), PATH_SEGMENT_ENCODE_SET)); diff --git a/rust/fatcat-api/src/lib.rs b/rust/fatcat-api/src/lib.rs index f45e113e..291dcaf4 100644 --- a/rust/fatcat-api/src/lib.rs +++ b/rust/fatcat-api/src/lib.rs @@ -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(); | 
