diff options
| author | Bryan Newbold <bnewbold@robocracy.org> | 2018-05-26 19:58:54 -0700 | 
|---|---|---|
| committer | Bryan Newbold <bnewbold@robocracy.org> | 2018-05-26 19:58:54 -0700 | 
| commit | 27052a8e1332669e23d0f36b6aad9cd324c9ec51 (patch) | |
| tree | ac47f21111a01f4ebadcd6e271945f349c84c742 /rust | |
| parent | eb989d60e086384500f3a4712260f81bc61232d3 (diff) | |
| download | fatcat-27052a8e1332669e23d0f36b6aad9cd324c9ec51.tar.gz fatcat-27052a8e1332669e23d0f36b6aad9cd324c9ec51.zip | |
even more refactoring
Diffstat (limited to 'rust')
| -rw-r--r-- | rust/src/api_server.rs | 476 | ||||
| -rw-r--r-- | rust/src/database_models.rs | 19 | 
2 files changed, 205 insertions, 290 deletions
| diff --git a/rust/src/api_server.rs b/rust/src/api_server.rs index f3bf4748..c0787c74 100644 --- a/rust/src/api_server.rs +++ b/rust/src/api_server.rs @@ -23,6 +23,8 @@ use fatcat_api::{Api, ApiError, ContainerIdGetResponse, ContainerLookupGetRespon  use futures::{self, Future};  use uuid; +type DbConn = diesel::r2d2::PooledConnection<diesel::r2d2::ConnectionManager<diesel::PgConnection>>; +  // Helper for calling through to handlers  macro_rules! wrap_entity_handlers {      ($get_fn:ident, $get_handler:ident, $get_resp:ident, $post_fn:ident, $post_handler:ident, @@ -33,16 +35,13 @@ macro_rules! wrap_entity_handlers {              _context: &Context,          ) -> Box<Future<Item = $get_resp, Error = ApiError> + Send> {              let ret = match self.$get_handler(id.clone()) { -                //Ok(Some(entity)) =>                  Ok(entity) =>                      $get_resp::FoundEntity(entity), -                //Ok(None) => -                //    $get_resp::NotFound(ErrorResponse { message: format!("No such entity {}: {}", stringify!($model), id) }),                  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) => $get_resp::BadRequest(ErrorResponse { message: e.to_string() }), +                Err(e) => $get_resp::GenericError(ErrorResponse { message: e.to_string() }),              };              Box::new(futures::done(Ok(ret)))          } @@ -52,32 +51,31 @@ macro_rules! wrap_entity_handlers {              body: models::$model,              _context: &Context,          ) -> Box<Future<Item = $post_resp, Error = ApiError> + Send> { -            // TODO: look for diesel foreign key and other type errors, return as BadRequest; other -            // errors are a 500.              let ret = match self.$post_handler(body) {                  Ok(edit) =>                      $post_resp::CreatedEntity(edit), -                Err(e) => $post_resp::BadRequest(ErrorResponse { message: e.to_string() }), +                Err(Error(ErrorKind::Diesel(e), _)) => +                    $post_resp::BadRequest(ErrorResponse { message: e.to_string() }), +                Err(e) => $post_resp::GenericError(ErrorResponse { message: e.to_string() }),              };              Box::new(futures::done(Ok(ret)))          }      }  }  macro_rules! wrap_lookup_handler { -    ($get_fn:ident, $handler:ident, $resp:ident, $idname:ident, $idtype:ident) => { +    ($get_fn:ident, $get_handler:ident, $get_resp:ident, $idname:ident, $idtype:ident) => {          fn $get_fn(              &self,              $idname: $idtype,              _context: &Context, -        ) -> Box<Future<Item = $resp, Error = ApiError> + Send> { -            let ret = match self.$handler($idname) { -                Ok(Some(entity)) => -                    $resp::FoundEntity(entity), -                Ok(None) => -                    $resp::NotFound(ErrorResponse { message: "No such entity".to_string() }), +        ) -> Box<Future<Item = $get_resp, Error = ApiError> + Send> { +            let ret = match self.$get_handler($idname.clone()) { +                Ok(entity) => +                    $get_resp::FoundEntity(entity), +                Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => +                    $get_resp::NotFound(ErrorResponse { message: format!("Not found: {}", $idname) }),                  Err(e) => -                    // TODO: dig in to error type here -                    $resp::BadRequest(ErrorResponse { message: e.to_string() }), +                    $get_resp::BadRequest(ErrorResponse { message: e.to_string() }),              };              Box::new(futures::done(Ok(ret)))          } @@ -89,17 +87,154 @@ pub struct Server {      pub db_pool: ConnectionPool,  } -fn container_row2entity(ident: ContainerIdentRow, rev: ContainerRevRow) -> Result<ContainerEntity> { +fn container_row2entity(ident: Option<ContainerIdentRow>, rev: ContainerRevRow) -> Result<ContainerEntity> { +    let (state, ident_id, redirect_id) = match ident { +        Some(i) => ( +            Some(i.state().unwrap().shortname()), +            Some(i.id.to_string()), +            i.redirect_id.map(|u| u.to_string())), +        None => (None, None, None), +    };      Ok(ContainerEntity {          issnl: rev.issnl,          publisher: rev.publisher,          name: rev.name,          abbrev: rev.abbrev,          coden: rev.coden, -        state: Some(ident.state().unwrap().shortname()), -        ident: Some(ident.id.to_string()), -        revision: ident.rev_id, -        redirect: ident.redirect_id.map(|u| u.to_string()), +        state: state, +        ident: ident_id, +        revision: Some(rev.id), +        redirect: redirect_id, +        extra: rev.extra_json, +        editgroup_id: None, +    }) +} + +fn creator_row2entity(ident: Option<CreatorIdentRow>, rev: CreatorRevRow) -> Result<CreatorEntity> { +    let (state, ident_id, redirect_id) = match ident { +        Some(i) => ( +            Some(i.state().unwrap().shortname()), +            Some(i.id.to_string()), +            i.redirect_id.map(|u| u.to_string())), +        None => (None, None, None), +    }; +    Ok(CreatorEntity { +        full_name: rev.full_name, +        orcid: rev.orcid, +        state: state, +        ident: ident_id, +        revision: Some(rev.id), +        redirect: redirect_id, +        editgroup_id: None, +        extra: rev.extra_json, +    }) +} + +fn file_row2entity(ident: Option<FileIdentRow>, rev: FileRevRow, conn: DbConn) -> Result<FileEntity> { + +    let (state, ident_id, redirect_id) = match ident { +        Some(i) => ( +            Some(i.state().unwrap().shortname()), +            Some(i.id.to_string()), +            i.redirect_id.map(|u| u.to_string())), +        None => (None, None, None), +    }; + +    let releases: Vec<String> = file_release::table +        .filter(file_release::file_rev.eq(rev.id)) +        .get_results(&conn)? +        .iter() +        .map(|r: &FileReleaseRow| r.target_release_ident_id.to_string()) +        .collect(); + +    Ok(FileEntity { +        sha1: rev.sha1, +        md5: rev.md5, +        size: rev.size.map(|v| v as i64), +        url: rev.url, +        releases: Some(releases), +        state: state, +        ident: ident_id, +        revision: Some(rev.id), +        redirect: redirect_id, +        editgroup_id: None, +        extra: rev.extra_json, +    }) +} + +fn release_row2entity(ident: Option<ReleaseIdentRow>, rev: ReleaseRevRow, conn: DbConn) -> Result<ReleaseEntity> { + +    let (state, ident_id, redirect_id) = match ident { +        Some(i) => ( +            Some(i.state().unwrap().shortname()), +            Some(i.id.to_string()), +            i.redirect_id.map(|u| u.to_string())), +        None => (None, None, None), +    }; + +    let refs: Vec<ReleaseRef> = release_ref::table +        .filter(release_ref::release_rev.eq(rev.id)) +        .get_results(&conn) +        .expect("fetch release refs") +        .iter() +        .map(|r: &ReleaseRefRow| ReleaseRef { +            index: r.index.clone(), +            stub: r.stub.clone(), +            target_release_id: r.target_release_ident_id.map(|v| v.to_string()), +        }) +        .collect(); + +    let contribs: Vec<ReleaseContrib> = release_contrib::table +        .filter(release_contrib::release_rev.eq(rev.id)) +        .get_results(&conn) +        .expect("fetch release refs") +        .iter() +        .map(|c: &ReleaseContribRow| ReleaseContrib { +            index: c.index, +            role: c.role.clone(), +            creator_stub: c.stub.clone(), +            creator_id: c.creator_ident_id.map(|v| v.to_string()), +        }) +        .collect(); + +    Ok(ReleaseEntity { +        title: rev.title, +        release_type: rev.release_type, +        date: rev.date +            .map(|v| chrono::DateTime::from_utc(v.and_hms(0, 0, 0), chrono::Utc)), +        doi: rev.doi, +        isbn13: rev.isbn13, +        volume: rev.volume, +        pages: rev.pages, +        issue: rev.issue, +        container_id: rev.container_ident_id.map(|u| u.to_string()), +        publisher: rev.publisher, +        work_id: rev.work_ident_id.to_string(), +        refs: Some(refs), +        contribs: Some(contribs), +        state: state, +        ident: ident_id, +        revision: Some(rev.id), +        redirect: redirect_id, +        editgroup_id: None, +        extra: rev.extra_json, +    }) +} + +fn work_row2entity(ident: Option<WorkIdentRow>, rev: WorkRevRow) -> Result<WorkEntity> { +    let (state, ident_id, redirect_id) = match ident { +        Some(i) => ( +            Some(i.state().unwrap().shortname()), +            Some(i.id.to_string()), +            i.redirect_id.map(|u| u.to_string())), +        None => (None, None, None), +    }; +    Ok(WorkEntity { +        work_type: rev.work_type, +        state: state, +        ident: ident_id, +        revision: Some(rev.id), +        redirect: redirect_id,          editgroup_id: None,          extra: rev.extra_json,      }) @@ -111,42 +246,28 @@ impl Server {          let conn = self.db_pool.get().expect("db_pool error");          let id = uuid::Uuid::parse_str(&id)?; -        //let res: ::std::result::Result<(ContainerIdentRow, ContainerRevRow), _> = +        // TODO: handle Deletions          let (ident, rev): (ContainerIdentRow, ContainerRevRow) =              container_ident::table                  .find(id)                  .inner_join(container_rev::table)                  .first(&conn)?; -/* -        let (ident, rev) = match res { -            Ok(r) => r, -            Err(::diesel::result::Error::NotFound) => return Ok(None), -            Err(e) => return Err(e.into()), -        }; -*/ -        //container_row2entity(ident, rev).map(|e| Some(e)) -        container_row2entity(ident, rev) +        container_row2entity(Some(ident), rev)      } -    fn container_lookup_get_handler(&self, issnl: String) -> Result<Option<ContainerEntity>> { +    fn container_lookup_get_handler(&self, issnl: String) -> Result<ContainerEntity> {          let conn = self.db_pool.get().expect("db_pool error"); -        let res: ::std::result::Result<(ContainerIdentRow, ContainerRevRow), _> = +        let (ident, rev): (ContainerIdentRow, ContainerRevRow) =              container_ident::table                  .inner_join(container_rev::table)                  .filter(container_rev::issnl.eq(&issnl))                  .filter(container_ident::is_live.eq(true))                  .filter(container_ident::redirect_id.is_null()) -                .first(&conn); - -        let (ident, rev) = match res { -            Ok(r) => r, -            Err(::diesel::result::Error::NotFound) => return Ok(None), -            Err(e) => return Err(e.into()), -        }; +                .first(&conn)?; -        container_row2entity(ident, rev).map(|e| Some(e)) +        container_row2entity(Some(ident), rev)      }      fn creator_id_get_handler(&self, id: String) -> Result<CreatorEntity> { @@ -158,47 +279,20 @@ impl Server {              .inner_join(creator_rev::table)              .first(&conn)?; -        let entity = CreatorEntity { -            full_name: rev.full_name, -            orcid: rev.orcid, -            state: Some(ident.state().unwrap().shortname()), -            ident: Some(ident.id.to_string()), -            revision: ident.rev_id, -            redirect: ident.redirect_id.map(|u| u.to_string()), -            editgroup_id: None, -            extra: rev.extra_json, -        }; -        Ok(entity) +        creator_row2entity(Some(ident), rev)      } -    fn creator_lookup_get_handler(&self, orcid: String) -> Result<Option<CreatorEntity>> { +    fn creator_lookup_get_handler(&self, orcid: String) -> Result<CreatorEntity> {          let conn = self.db_pool.get().expect("db_pool error"); -        //let (ident, rev): (CreatorIdentRow, CreatorRevRow) = creator_ident::table -        let res: ::std::result::Result<(CreatorIdentRow, CreatorRevRow), _> = creator_ident::table +        let (ident, rev): (CreatorIdentRow, CreatorRevRow) = creator_ident::table              .inner_join(creator_rev::table)              .filter(creator_rev::orcid.eq(&orcid))              .filter(creator_ident::is_live.eq(true))              .filter(creator_ident::redirect_id.is_null()) -            .first(&conn); - -        let (ident, rev) = match res { -            Ok(r) => r, -            Err(diesel::result::Error::NotFound) => return Ok(None), -            Err(e) => return Err(e.into()), -        }; +            .first(&conn)?; -        let entity = CreatorEntity { -            full_name: rev.full_name, -            orcid: rev.orcid, -            state: Some(ident.state().unwrap().shortname()), -            ident: Some(ident.id.to_string()), -            revision: ident.rev_id, -            redirect: ident.redirect_id.map(|u| u.to_string()), -            editgroup_id: None, -            extra: rev.extra_json, -        }; -        Ok(Some(entity)) +        creator_row2entity(Some(ident), rev)      }      fn file_id_get_handler(&self, id: String) -> Result<FileEntity> { @@ -210,69 +304,20 @@ impl Server {              .inner_join(file_rev::table)              .first(&conn)?; -        let releases: Vec<String> = file_release::table -            .filter(file_release::file_rev.eq(rev.id)) -            .get_results(&conn) -            .expect("fetch file releases") -            .iter() -            .map(|r: &FileReleaseRow| r.target_release_ident_id.to_string()) -            .collect(); - -        let entity = FileEntity { -            sha1: rev.sha1, -            md5: rev.md5, -            size: rev.size.map(|v| v as i64), -            url: rev.url, -            releases: Some(releases), -            state: Some(ident.state().unwrap().shortname()), -            ident: Some(ident.id.to_string()), -            revision: ident.rev_id.map(|v| v), -            redirect: ident.redirect_id.map(|u| u.to_string()), -            editgroup_id: None, -            extra: rev.extra_json, -        }; -        Ok(entity) +        file_row2entity(Some(ident), rev, conn)      } -    // TODO: refactor this to not be redundant with file_id_get_handler() code -    fn file_lookup_get_handler(&self, sha1: String) -> Result<Option<FileEntity>> { +    fn file_lookup_get_handler(&self, sha1: String) -> Result<FileEntity> {          let conn = self.db_pool.get().expect("db_pool error"); -        let res: ::std::result::Result<(FileIdentRow, FileRevRow), _> = file_ident::table +        let (ident, rev): (FileIdentRow, FileRevRow) = file_ident::table              .inner_join(file_rev::table)              .filter(file_rev::sha1.eq(&sha1))              .filter(file_ident::is_live.eq(true))              .filter(file_ident::redirect_id.is_null()) -            .first(&conn); - -        let (ident, rev) = match res { -            Ok(r) => r, -            Err(diesel::result::Error::NotFound) => return Ok(None), -            Err(e) => return Err(e.into()), -        }; - -        let releases: Vec<String> = file_release::table -            .filter(file_release::file_rev.eq(rev.id)) -            .get_results(&conn) -            .expect("fetch file releases") -            .iter() -            .map(|r: &FileReleaseRow| r.target_release_ident_id.to_string()) -            .collect(); +            .first(&conn)?; -        let entity = FileEntity { -            sha1: rev.sha1, -            md5: rev.md5, -            size: rev.size.map(|v| v as i64), -            url: rev.url, -            releases: Some(releases), -            state: Some(ident.state().unwrap().shortname()), -            ident: Some(ident.id.to_string()), -            revision: ident.rev_id.map(|v| v), -            redirect: ident.redirect_id.map(|u| u.to_string()), -            editgroup_id: None, -            extra: rev.extra_json, -        }; -        Ok(Some(entity)) +        file_row2entity(Some(ident), rev, conn)      }      fn release_id_get_handler(&self, id: String) -> Result<ReleaseEntity> { @@ -284,120 +329,20 @@ impl Server {              .inner_join(release_rev::table)              .first(&conn)?; -        let refs: Vec<ReleaseRef> = release_ref::table -            .filter(release_ref::release_rev.eq(rev.id)) -            .get_results(&conn) -            .expect("fetch release refs") -            .iter() -            .map(|r: &ReleaseRefRow| ReleaseRef { -                index: r.index.clone(), -                stub: r.stub.clone(), -                target_release_id: r.target_release_ident_id.map(|v| v.to_string()), -            }) -            .collect(); - -        let contribs: Vec<ReleaseContrib> = release_contrib::table -            .filter(release_contrib::release_rev.eq(rev.id)) -            .get_results(&conn) -            .expect("fetch release refs") -            .iter() -            .map(|c: &ReleaseContribRow| ReleaseContrib { -                index: c.index, -                role: c.role.clone(), -                creator_stub: c.stub.clone(), -                creator_id: c.creator_ident_id.map(|v| v.to_string()), -            }) -            .collect(); - -        let entity = ReleaseEntity { -            title: rev.title, -            release_type: rev.release_type, -            date: rev.date -                .map(|v| chrono::DateTime::from_utc(v.and_hms(0, 0, 0), chrono::Utc)), -            doi: rev.doi, -            isbn13: rev.isbn13, -            volume: rev.volume, -            pages: rev.pages, -            issue: rev.issue, -            container_id: rev.container_ident_id.map(|u| u.to_string()), -            publisher: rev.publisher, -            work_id: rev.work_ident_id.to_string(), -            refs: Some(refs), -            contribs: Some(contribs), -            state: Some(ident.state().unwrap().shortname()), -            ident: Some(ident.id.to_string()), -            revision: ident.rev_id, -            redirect: ident.redirect_id.map(|u| u.to_string()), -            editgroup_id: None, -            extra: rev.extra_json, -        }; -        Ok(entity) +        release_row2entity(Some(ident), rev, conn)      } -    fn release_lookup_get_handler(&self, doi: String) -> Result<Option<ReleaseEntity>> { +    fn release_lookup_get_handler(&self, doi: String) -> Result<ReleaseEntity> {          let conn = self.db_pool.get().expect("db_pool error"); -        let res: ::std::result::Result<(ReleaseIdentRow, ReleaseRevRow), _> = release_ident::table +        let (ident, rev): (ReleaseIdentRow, ReleaseRevRow) = release_ident::table              .inner_join(release_rev::table)              .filter(release_rev::doi.eq(&doi))              .filter(release_ident::is_live.eq(true))              .filter(release_ident::redirect_id.is_null()) -            .first(&conn); - -        let (ident, rev) = match res { -            Ok(r) => r, -            Err(diesel::result::Error::NotFound) => return Ok(None), -            Err(e) => return Err(e.into()), -        }; - -        let refs: Vec<ReleaseRef> = release_ref::table -            .filter(release_ref::release_rev.eq(rev.id)) -            .get_results(&conn) -            .expect("fetch release refs") -            .iter() -            .map(|r: &ReleaseRefRow| ReleaseRef { -                index: r.index.clone(), -                stub: r.stub.clone(), -                target_release_id: r.target_release_ident_id.map(|v| v.to_string()), -            }) -            .collect(); - -        let contribs: Vec<ReleaseContrib> = release_contrib::table -            .filter(release_contrib::release_rev.eq(rev.id)) -            .get_results(&conn) -            .expect("fetch release refs") -            .iter() -            .map(|c: &ReleaseContribRow| ReleaseContrib { -                index: c.index, -                role: c.role.clone(), -                creator_stub: c.stub.clone(), -                creator_id: c.creator_ident_id.map(|v| v.to_string()), -            }) -            .collect(); +            .first(&conn)?; -        let entity = ReleaseEntity { -            title: rev.title, -            release_type: rev.release_type, -            date: rev.date -                .map(|v| chrono::DateTime::from_utc(v.and_hms(0, 0, 0), chrono::Utc)), -            doi: rev.doi, -            isbn13: rev.isbn13, -            volume: rev.volume, -            pages: rev.pages, -            issue: rev.issue, -            container_id: rev.container_ident_id.map(|u| u.to_string()), -            publisher: rev.publisher, -            work_id: rev.work_ident_id.to_string(), -            refs: Some(refs), -            contribs: Some(contribs), -            state: Some(ident.state().unwrap().shortname()), -            ident: Some(ident.id.to_string()), -            revision: ident.rev_id, -            redirect: ident.redirect_id.map(|u| u.to_string()), -            editgroup_id: None, -            extra: rev.extra_json, -        }; -        Ok(Some(entity)) +        release_row2entity(Some(ident), rev, conn)      }      fn work_id_get_handler(&self, id: String) -> Result<WorkEntity> { @@ -409,16 +354,7 @@ impl Server {              .inner_join(work_rev::table)              .first(&conn)?; -        let entity = WorkEntity { -            work_type: rev.work_type, -            state: Some(ident.state().unwrap().shortname()), -            ident: Some(ident.id.to_string()), -            revision: ident.rev_id, -            redirect: ident.redirect_id.map(|u| u.to_string()), -            editgroup_id: None, -            extra: rev.extra_json, -        }; -        Ok(entity) +        work_row2entity(Some(ident), rev)      }      fn container_post_handler(&self, body: models::ContainerEntity) -> Result<EntityEdit> { @@ -447,16 +383,8 @@ impl Server {              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(body.extra)              .bind::<diesel::sql_types::BigInt, _>(editgroup_id)              .get_result(&conn)?; -        let edit = &edit; - -        Ok(EntityEdit { -            editgroup_id: edit.editgroup_id, -            revision: Some(edit.rev_id.unwrap()), -            redirect_ident: None, -            ident: edit.ident_id.to_string(), -            edit_id: edit.id, -            extra: edit.extra_json.clone(), -        }) + +        edit.to_model()      }      fn creator_post_handler(&self, body: models::CreatorEntity) -> Result<EntityEdit> { @@ -482,16 +410,8 @@ impl Server {              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(body.extra)              .bind::<diesel::sql_types::BigInt, _>(editgroup_id)              .get_result(&conn)?; -        let edit = &edit; - -        Ok(EntityEdit { -            editgroup_id: edit.editgroup_id, -            revision: Some(edit.rev_id.unwrap()), -            redirect_ident: None, -            ident: edit.ident_id.to_string(), -            edit_id: edit.id, -            extra: edit.extra_json.clone(), -        }) + +        edit.to_model()      }      fn file_post_handler(&self, body: models::FileEntity) -> Result<EntityEdit> { @@ -520,7 +440,6 @@ impl Server {                  .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(body.extra)                  .bind::<diesel::sql_types::BigInt, _>(editgroup_id)                  .get_result(&conn)?; -        let edit = &edit;          let _releases: Option<Vec<FileReleaseRow>> = match body.releases {              None => None, @@ -544,14 +463,7 @@ impl Server {              }          }; -        Ok(EntityEdit { -            editgroup_id: edit.editgroup_id, -            revision: Some(edit.rev_id.unwrap()), -            redirect_ident: None, -            ident: edit.ident_id.to_string(), -            edit_id: edit.id, -            extra: edit.extra_json.clone(), -        }) +        edit.to_model()      }      fn work_post_handler(&self, body: models::WorkEntity) -> Result<EntityEdit> { @@ -577,16 +489,8 @@ impl Server {                  .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(body.extra)                  .bind::<diesel::sql_types::BigInt, _>(editgroup_id)                  .get_result(&conn)?; -        let edit = &edit; - -        Ok(EntityEdit { -            editgroup_id: edit.editgroup_id, -            revision: Some(edit.rev_id.unwrap()), -            redirect_ident: None, -            ident: edit.ident_id.to_string(), -            edit_id: edit.id, -            extra: edit.extra_json.clone(), -        }) + +        edit.to_model()      }      fn release_post_handler(&self, body: models::ReleaseEntity) -> Result<EntityEdit> { @@ -628,7 +532,6 @@ impl Server {              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(body.extra)              .bind::<diesel::sql_types::BigInt, _>(editgroup_id)              .get_result(&conn)?; -        let edit = &edit;          let _refs: Option<Vec<ReleaseRefRow>> = match body.refs {              None => None, @@ -683,14 +586,7 @@ impl Server {              }          }; -        Ok(EntityEdit { -            editgroup_id: edit.editgroup_id, -            revision: Some(edit.rev_id.unwrap()), -            redirect_ident: None, -            ident: edit.ident_id.to_string(), -            edit_id: edit.id, -            extra: edit.extra_json.clone(), -        }) +        edit.to_model()      }      fn editgroup_id_get_handler(&self, id: i64) -> Result<Option<Editgroup>> { diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs index 9913b7e2..b46d6ffb 100644 --- a/rust/src/database_models.rs +++ b/rust/src/database_models.rs @@ -3,6 +3,7 @@ use database_schema::*;  use errors::*;  use serde_json;  use uuid::Uuid; +use fatcat_api::models::EntityEdit;  // Ugh. I thought the whole point was to *not* do this, but:  // https://github.com/diesel-rs/diesel/issues/1589 @@ -29,6 +30,10 @@ pub trait EntityIdentRow {      fn state(&self) -> Result<EntityState>;  } +pub trait EntityEditRow { +    fn to_model(self) -> Result<EntityEdit>; +} +  // Helper for constructing tables  macro_rules! entity_structs {      ($edit_table:expr, $edit_struct:ident, $ident_table:expr, $ident_struct:ident) => { @@ -43,6 +48,20 @@ macro_rules! entity_structs {              pub extra_json: Option<serde_json::Value>,          } +        impl EntityEditRow for $edit_struct { +            /// Go from a row (SQL model) to an API model +            fn to_model(self) -> Result<EntityEdit> { +                Ok(EntityEdit { +                    editgroup_id: self.editgroup_id, +                    revision: self.rev_id, +                    redirect_ident: self.redirect_id.map(|v| v.to_string()), +                    ident: self.ident_id.to_string(), +                    edit_id: self.id, +                    extra: self.extra_json, +                }) +            } +        } +          #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)]          #[table_name = $ident_table]          pub struct $ident_struct { | 
