summaryrefslogtreecommitdiffstats
path: root/rust
diff options
context:
space:
mode:
Diffstat (limited to 'rust')
-rw-r--r--rust/HACKING.md58
-rw-r--r--rust/INSTALL.md36
-rw-r--r--rust/NOTES.txt19
-rw-r--r--rust/README.md85
-rw-r--r--rust/TODO9
-rw-r--r--rust/fatcat-api/README.md2
-rw-r--r--rust/fatcat-api/api.yaml198
-rw-r--r--rust/fatcat-api/api/swagger.yaml620
-rw-r--r--rust/fatcat-api/examples/client.rs68
-rw-r--r--rust/fatcat-api/examples/server_lib/server.rs109
-rw-r--r--rust/fatcat-api/src/client.rs683
-rw-r--r--rust/fatcat-api/src/lib.rs206
-rw-r--r--rust/fatcat-api/src/mimetypes.rs186
-rw-r--r--rust/fatcat-api/src/server.rs1083
-rw-r--r--rust/src/api_helpers.rs32
-rw-r--r--rust/src/api_server.rs775
-rw-r--r--rust/src/api_wrappers.rs113
-rw-r--r--rust/src/database_entity_crud.rs900
-rw-r--r--rust/src/database_models.rs102
-rw-r--r--rust/src/lib.rs5
-rw-r--r--rust/tests/test_api_server.rs65
21 files changed, 4554 insertions, 800 deletions
diff --git a/rust/HACKING.md b/rust/HACKING.md
new file mode 100644
index 00000000..622a4b5a
--- /dev/null
+++ b/rust/HACKING.md
@@ -0,0 +1,58 @@
+
+## Code Structure
+
+Almost all of the rust code is either auto-generated or "glue" between the
+swagger API spec on one side and the SQL schema on the other.
+
+- `./migrations/*/up.sql`: SQL schema
+- `./src/database_schema.rs`: autogenerated per-table Diesel schemas
+- `./src/database_models.rs`: hand- and macro-generated Rust structs matching
+ Diesel schemas, and a small number of row-level helpers
+- `./src/database_entity_crud.rs`: "struct-relational-mapping"; trait
+ implementations of CRUD (create, read, update, delete) actions for each
+ entity model, hitting the database (and building on `database_model` structs)
+- `./src/api_server.rs`: one function for each API endpoint, with rust-style
+ arguments and return types. mostly calls in to `database_entity_crud`.
+- `./src/api_wrappers.rs`: hand- and macro-generated wrapper functions, one per
+ API endpoint, that map between API request and return types, and
+ rust-idiomatic request and return types (plus API models).
+- `./fatcat-api`: autogenerated API models and endpoint types/signatures
+- `../fatcat-openapi2.yaml`:
+
+When deciding to use this structure, it wasn't expected that either
+`api_wrappers.rs` or `database_models.rs` would need to hand-maintained; both
+are verbose and implemented in a very mechanical fashion. The return type
+mapping in `api_wrappers` might be necessary, but `database_models.rs` in
+particular feels unnecessary; other projects have attempted to completely
+automate generation of this file, but it doesn't sound reliable. In particular,
+both regular "Row" (queriable) and "NewRow" (insertable) structs need to be
+defined.
+
+## Test Structure
+
+- `./tests/test_api_server.rs`: Iron (web framework) level raw HTTP JSON
+ request/response tests of API endpoints.
+
+## Updating Schemas
+
+Regenerate API schemas after editing the fatcat-openapi2 schema. This will, as
+a side-effect, also run `cargo fmt` on the whole project, so don't run it with
+your editor open!
+
+ cargo install cargo-swagger # uses docker
+ ./codegen_openapi2.sh
+
+Update Rust database schema (after changing raw SQL schema):
+
+ diesel database reset
+ diesel print-schema > src/database_schema.rs
+
+Debug SQL schema errors (if diesel commands fail):
+
+ psql fatcat_test < migrations/2018-05-12-001226_init/up.sql
+
+## Direct API Interaction
+
+Creating entities via API:
+
+ http --json post localhost:9411/v0/container name=asdf issn=1234-5678
diff --git a/rust/INSTALL.md b/rust/INSTALL.md
new file mode 100644
index 00000000..c2b86c51
--- /dev/null
+++ b/rust/INSTALL.md
@@ -0,0 +1,36 @@
+
+Canonical IA production/QA ansible scripts are in the journal-infra repo. These
+directions are likely to end up out-of-date.
+
+## Simple Deployment
+
+To install manually, on a bare server, as root:
+
+ adduser fatcat
+ apt install postgresql-9.6 postgresql-contrib postgresql-client-9.6 \
+ nginx build-essential git pkg-config libssl-dev libpq-dev \
+ htop screen
+ mkdir -p /srv/fatcat
+ chown fatcat:fatcat /srv/fatcat
+
+ # setup new postgres user
+ su - postgres
+ createuser -P -s fatcat # strong random password
+ # DELETE: createdb fatcat
+
+ # as fatcat user
+ su - fatcat
+ ssh-keygen
+ curl https://sh.rustup.rs -sSf | sh
+ source $HOME/.cargo/env
+ cargo install diesel_cli --no-default-features --features "postgres"
+ cd /srv/fatcat
+ git clone git@git.archive.org:webgroup/fatcat
+ cd rust
+ cargo build
+ echo "DATABASE_URL=postgres://fatcat@localhost/fatcat" > .env
+ diesel database reset
+
+ # as fatcat, in a screen or something
+ cd /srv/fatcat/fatcat/rust
+ cargo run
diff --git a/rust/NOTES.txt b/rust/NOTES.txt
deleted file mode 100644
index 7e6f33eb..00000000
--- a/rust/NOTES.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-
-libs:
-- iron_slog
-- testing: keep it simple: iron-test
- => if that is annoying, shiny? mockers if needed.
-- sentry
-- start with dotenv+clap, then config-rs?
-- cadence (emits statsd)
-- frank_jwt and JWT for (simple?) auth
-
-similar:
-- https://github.com/DavidBM/templic-backend
-- https://github.com/alexanderbanks/rust-api
-- https://mgattozzi.com/diesel-powered-rocket
-- https://www.reddit.com/r/rust/comments/8j1xbs/new_to_rust_and_gitlab_ci/
-- https://crate-ci.github.io/
-
-"cool tools":
-- cargo-watch
diff --git a/rust/README.md b/rust/README.md
index a6873345..c061a1f9 100644
--- a/rust/README.md
+++ b/rust/README.md
@@ -29,87 +29,4 @@ Tests:
cargo test -- --test-threads 1
-## Simple Deployment
-
-Canonical ansible scripts are in the journal-infra repo. To install manually,
-on a bare server, as root:
-
- adduser fatcat
- apt install postgresql-9.6 postgresql-contrib postgresql-client-9.6 \
- nginx build-essential git pkg-config libssl-dev libpq-dev \
- htop screen
- mkdir -p /srv/fatcat
- chown fatcat:fatcat /srv/fatcat
-
- # setup new postgres user
- su - postgres
- createuser -P -s fatcat # strong random password
- # DELETE: createdb fatcat
-
- # as fatcat user
- su - fatcat
- ssh-keygen
- curl https://sh.rustup.rs -sSf | sh
- source $HOME/.cargo/env
- cargo install diesel_cli --no-default-features --features "postgres"
- cd /srv/fatcat
- git clone git@git.archive.org:webgroup/fatcat
- cd rust
- cargo build
- echo "DATABASE_URL=postgres://fatcat@localhost/fatcat" > .env
- diesel database reset
-
- # as fatcat, in a screen or something
- cd /srv/fatcat/fatcat/rust
- cargo run
-
-### Dumps and Backups
-
-There are a few different databaase dump formats folks might want:
-
-- raw native database backups, for disaster recovery (would include
- volatile/unsupported schema details, user API credentials, full history,
- in-process edits, comments, etc)
-- a sanitized version of the above: roughly per-table dumps of the full state
- of the database. Could use per-table SQL expressions with sub-queries to pull
- in small tables ("partial transform") and export JSON for each table; would
- be extra work to maintain, so not pursuing for now.
-- full history, full public schema exports, in a form that might be used to
- mirror or enitrely fork the project. Propose supplying the full "changelog"
- in API schema format, in a single file to capture all entity history, without
- "hydrating" any inter-entity references. Rely on separate dumps of
- non-entity, non-versioned tables (editors, abstracts, etc). Note that a
- variant of this could use the public interface, in particular to do
- incremental updates (though that wouldn't capture schema changes).
-- transformed exports of the current state of the database (aka, without
- history). Useful for data analysis, search engines, etc. Propose supplying
- just the Release table in a fully "hydrated" state to start. Unclear if
- should be on a work or release basis; will go with release for now. Harder to
- do using public interface because of the need for transaction locking.
-
-Backing up the entire database using `pg_dump`, with parallelism 1 (use more on
-larger machine with fast disks; try 4 or 8?), assuming the database name is
-'fatcat', and the current user has access:
-
- pg_dump -j1 -Fd -f test-dump fatcat
-
-### Special Tricks
-
-Regenerate API schemas (this will, as a side-effect, also run `cargo fmt` on
-the whole project, so don't run it with your editor open):
-
- cargo install cargo-swagger # uses docker
- ./codegen_openapi2.sh
-
-Regenerate SQL schema:
-
- diesel database reset
- diesel print-schema > src/database_schema.rs
-
-Debugging SQL schema errors:
-
- psql fatcat_test < migrations/2018-05-12-001226_init/up.sql
-
-Creating entities via API:
-
- http --json post localhost:9411/v0/container name=asdf issn=1234-5678
+See `HACKING` for some more advanced tips and commands.
diff --git a/rust/TODO b/rust/TODO
index a4a6dfad..ed38b915 100644
--- a/rust/TODO
+++ b/rust/TODO
@@ -1,4 +1,13 @@
+- fatcat_api -> fatcat_api_schema (or spec? models? types?)
+- fatcat -> fatcat-api-server
+- refactor rev creation (from an entity) into it's own function, across the board
+ => attach to database module structs?
+ => should make cockroachdb compatible (single use of CTE)
+ => usable from both "update" and "create"
+- editgroup param to update
+ => also for creation? for consistency
+
- editor_id vs. editor username; return editor_id (in addition to name?)
later:
diff --git a/rust/fatcat-api/README.md b/rust/fatcat-api/README.md
index 7e4a2ec8..4be4939f 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-08-22T00:54:09.323Z
+- Build date: 2018-09-01T01:21:49.948Z
This autogenerated project defines an API crate `fatcat` which contains:
* An `Api` trait defining the API in Rust.
diff --git a/rust/fatcat-api/api.yaml b/rust/fatcat-api/api.yaml
index ea17e982..a8919216 100644
--- a/rust/fatcat-api/api.yaml
+++ b/rust/fatcat-api/api.yaml
@@ -473,19 +473,47 @@ paths:
in: path
type: string
required: true
- - name: expand
- in: query
- type: string
- required: false
- description: "List of sub-entities to expand in response. For now, only 'all' accepted."
get:
operationId: "get_container"
+ parameters:
+ - name: expand
+ in: query
+ type: string
+ required: false
+ description: "List of sub-entities to expand in response. For now, only 'all' accepted."
responses:
200:
description: Found Entity
schema:
$ref: "#/definitions/container_entity"
<<: *ENTITYRESPONSES
+ put:
+ operationId: "update_container"
+ parameters:
+ - name: entity
+ in: body
+ required: true
+ schema:
+ $ref: "#/definitions/container_entity"
+ responses:
+ 200:
+ description: Updated Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
+ delete:
+ operationId: "delete_container"
+ parameters:
+ - name: editgroup
+ in: query
+ required: false
+ type: string
+ responses:
+ 200:
+ description: Deleted Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
/container/{id}/history:
parameters:
- name: id
@@ -571,19 +599,47 @@ paths:
in: path
type: string
required: true
- - name: expand
- in: query
- type: string
- required: false
- description: "List of sub-entities to expand in response. For now, only 'all' accepted."
get:
operationId: "get_creator"
+ parameters:
+ - name: expand
+ in: query
+ type: string
+ required: false
+ description: "List of sub-entities to expand in response. For now, only 'all' accepted."
responses:
200:
description: Found Entity
schema:
$ref: "#/definitions/creator_entity"
<<: *ENTITYRESPONSES
+ put:
+ operationId: "update_creator"
+ parameters:
+ - name: entity
+ in: body
+ required: true
+ schema:
+ $ref: "#/definitions/creator_entity"
+ responses:
+ 200:
+ description: Updated Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
+ delete:
+ operationId: "delete_creator"
+ parameters:
+ - name: editgroup
+ in: query
+ required: false
+ type: string
+ responses:
+ 200:
+ description: Deleted Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
/creator/{id}/history:
parameters:
- name: id
@@ -685,19 +741,47 @@ paths:
in: path
type: string
required: true
- - name: expand
- in: query
- type: string
- required: false
- description: "List of sub-entities to expand in response. For now, only 'all' accepted."
get:
operationId: "get_file"
+ parameters:
+ - name: expand
+ in: query
+ type: string
+ required: false
+ description: "List of sub-entities to expand in response. For now, only 'all' accepted."
responses:
200:
description: Found Entity
schema:
$ref: "#/definitions/file_entity"
<<: *ENTITYRESPONSES
+ put:
+ operationId: "update_file"
+ parameters:
+ - name: entity
+ in: body
+ required: true
+ schema:
+ $ref: "#/definitions/file_entity"
+ responses:
+ 200:
+ description: Updated Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
+ delete:
+ operationId: "delete_file"
+ parameters:
+ - name: editgroup
+ in: query
+ required: false
+ type: string
+ responses:
+ 200:
+ description: Deleted Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
/file/{id}/history:
parameters:
- name: id
@@ -783,19 +867,47 @@ paths:
in: path
type: string
required: true
- - name: expand
- in: query
- type: string
- required: false
- description: "List of sub-entities to expand in response. For now, only 'all' accepted."
get:
operationId: "get_release"
+ parameters:
+ - name: expand
+ in: query
+ type: string
+ required: false
+ description: "List of sub-entities to expand in response. For now, only 'all' accepted."
responses:
200:
description: Found Entity
schema:
$ref: "#/definitions/release_entity"
<<: *ENTITYRESPONSES
+ put:
+ operationId: "update_release"
+ parameters:
+ - name: entity
+ in: body
+ required: true
+ schema:
+ $ref: "#/definitions/release_entity"
+ responses:
+ 200:
+ description: Updated Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
+ delete:
+ operationId: "delete_release"
+ parameters:
+ - name: editgroup
+ in: query
+ required: false
+ type: string
+ responses:
+ 200:
+ description: Deleted Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
/release/{id}/history:
parameters:
- name: id
@@ -897,19 +1009,47 @@ paths:
in: path
type: string
required: true
- - name: expand
- in: query
- type: string
- required: false
- description: "List of sub-entities to expand in response. For now, only 'all' accepted."
get:
operationId: "get_work"
+ parameters:
+ - name: expand
+ in: query
+ type: string
+ required: false
+ description: "List of sub-entities to expand in response. For now, only 'all' accepted."
responses:
200:
description: Found Entity
schema:
$ref: "#/definitions/work_entity"
<<: *ENTITYRESPONSES
+ put:
+ operationId: "update_work"
+ parameters:
+ - name: entity
+ in: body
+ required: true
+ schema:
+ $ref: "#/definitions/work_entity"
+ responses:
+ 200:
+ description: Updated Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
+ delete:
+ operationId: "delete_work"
+ parameters:
+ - name: editgroup
+ in: query
+ required: false
+ type: string
+ responses:
+ 200:
+ description: Deleted Entity
+ schema:
+ $ref: "#/definitions/entity_edit"
+ <<: *ENTITYRESPONSES
/work/{id}/history:
parameters:
- name: id
@@ -1055,10 +1195,18 @@ paths:
description: Unmergable
schema:
$ref: "#/definitions/error_response"
+ 400:
+ description: Bad Request
+ schema:
+ $ref: "#/definitions/error_response"
404:
description: Not Found
schema:
$ref: "#/definitions/error_response"
+ 409:
+ description: Edit Conflict
+ schema:
+ $ref: "#/definitions/error_response"
500:
description: Generic Error
schema:
diff --git a/rust/fatcat-api/api/swagger.yaml b/rust/fatcat-api/api/swagger.yaml
index 3b8ed6e3..0b1ca88a 100644
--- a/rust/fatcat-api/api/swagger.yaml
+++ b/rust/fatcat-api/api/swagger.yaml
@@ -208,6 +208,127 @@ paths:
path: "/container/:id"
HttpMethod: "Get"
httpmethod: "get"
+ put:
+ operationId: "update_container"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - in: "body"
+ name: "entity"
+ required: true
+ schema:
+ $ref: "#/definitions/container_entity"
+ uppercase_data_type: "CONTAINERENTITY"
+ refName: "container_entity"
+ formatString: "{:?}"
+ example: "???"
+ model_key: "editgroup_edits"
+ uppercase_operation_id: "UPDATE_CONTAINER"
+ consumesJson: true
+ responses:
+ 200:
+ description: "Updated Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "UpdatedEntity"
+ x-uppercaseResponseId: "UPDATED_ENTITY"
+ uppercase_operation_id: "UPDATE_CONTAINER"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "UPDATE_CONTAINER"
+ 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: "UPDATE_CONTAINER"
+ 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: "UPDATE_CONTAINER"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "update_container"
+ uppercase_operation_id: "UPDATE_CONTAINER"
+ path: "/container/:id"
+ HttpMethod: "Put"
+ httpmethod: "put"
+ noClientExample: true
+ delete:
+ operationId: "delete_container"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - name: "editgroup"
+ in: "query"
+ required: false
+ type: "string"
+ formatString: "{:?}"
+ example: "Some(\"editgroup_example\".to_string())"
+ responses:
+ 200:
+ description: "Deleted Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "DeletedEntity"
+ x-uppercaseResponseId: "DELETED_ENTITY"
+ uppercase_operation_id: "DELETE_CONTAINER"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "DELETE_CONTAINER"
+ 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: "DELETE_CONTAINER"
+ 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: "DELETE_CONTAINER"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "delete_container"
+ uppercase_operation_id: "DELETE_CONTAINER"
+ path: "/container/:id"
+ HttpMethod: "Delete"
+ httpmethod: "delete"
/container/{id}/history:
get:
operationId: "get_container_history"
@@ -519,6 +640,127 @@ paths:
path: "/creator/:id"
HttpMethod: "Get"
httpmethod: "get"
+ put:
+ operationId: "update_creator"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - in: "body"
+ name: "entity"
+ required: true
+ schema:
+ $ref: "#/definitions/creator_entity"
+ uppercase_data_type: "CREATORENTITY"
+ refName: "creator_entity"
+ formatString: "{:?}"
+ example: "???"
+ model_key: "editgroup_edits"
+ uppercase_operation_id: "UPDATE_CREATOR"
+ consumesJson: true
+ responses:
+ 200:
+ description: "Updated Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "UpdatedEntity"
+ x-uppercaseResponseId: "UPDATED_ENTITY"
+ uppercase_operation_id: "UPDATE_CREATOR"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "UPDATE_CREATOR"
+ 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: "UPDATE_CREATOR"
+ 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: "UPDATE_CREATOR"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "update_creator"
+ uppercase_operation_id: "UPDATE_CREATOR"
+ path: "/creator/:id"
+ HttpMethod: "Put"
+ httpmethod: "put"
+ noClientExample: true
+ delete:
+ operationId: "delete_creator"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - name: "editgroup"
+ in: "query"
+ required: false
+ type: "string"
+ formatString: "{:?}"
+ example: "Some(\"editgroup_example\".to_string())"
+ responses:
+ 200:
+ description: "Deleted Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "DeletedEntity"
+ x-uppercaseResponseId: "DELETED_ENTITY"
+ uppercase_operation_id: "DELETE_CREATOR"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "DELETE_CREATOR"
+ 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: "DELETE_CREATOR"
+ 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: "DELETE_CREATOR"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "delete_creator"
+ uppercase_operation_id: "DELETE_CREATOR"
+ path: "/creator/:id"
+ HttpMethod: "Delete"
+ httpmethod: "delete"
/creator/{id}/history:
get:
operationId: "get_creator_history"
@@ -884,6 +1126,127 @@ paths:
path: "/file/:id"
HttpMethod: "Get"
httpmethod: "get"
+ put:
+ operationId: "update_file"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - in: "body"
+ name: "entity"
+ required: true
+ schema:
+ $ref: "#/definitions/file_entity"
+ uppercase_data_type: "FILEENTITY"
+ refName: "file_entity"
+ formatString: "{:?}"
+ example: "???"
+ model_key: "editgroup_edits"
+ uppercase_operation_id: "UPDATE_FILE"
+ consumesJson: true
+ responses:
+ 200:
+ description: "Updated Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "UpdatedEntity"
+ x-uppercaseResponseId: "UPDATED_ENTITY"
+ uppercase_operation_id: "UPDATE_FILE"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "UPDATE_FILE"
+ 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: "UPDATE_FILE"
+ 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: "UPDATE_FILE"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "update_file"
+ uppercase_operation_id: "UPDATE_FILE"
+ path: "/file/:id"
+ HttpMethod: "Put"
+ httpmethod: "put"
+ noClientExample: true
+ delete:
+ operationId: "delete_file"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - name: "editgroup"
+ in: "query"
+ required: false
+ type: "string"
+ formatString: "{:?}"
+ example: "Some(\"editgroup_example\".to_string())"
+ responses:
+ 200:
+ description: "Deleted Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "DeletedEntity"
+ x-uppercaseResponseId: "DELETED_ENTITY"
+ uppercase_operation_id: "DELETE_FILE"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "DELETE_FILE"
+ 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: "DELETE_FILE"
+ 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: "DELETE_FILE"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "delete_file"
+ uppercase_operation_id: "DELETE_FILE"
+ path: "/file/:id"
+ HttpMethod: "Delete"
+ httpmethod: "delete"
/file/{id}/history:
get:
operationId: "get_file_history"
@@ -1192,6 +1555,127 @@ paths:
path: "/release/:id"
HttpMethod: "Get"
httpmethod: "get"
+ put:
+ operationId: "update_release"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - in: "body"
+ name: "entity"
+ required: true
+ schema:
+ $ref: "#/definitions/release_entity"
+ uppercase_data_type: "RELEASEENTITY"
+ refName: "release_entity"
+ formatString: "{:?}"
+ example: "???"
+ model_key: "editgroup_edits"
+ uppercase_operation_id: "UPDATE_RELEASE"
+ consumesJson: true
+ responses:
+ 200:
+ description: "Updated Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "UpdatedEntity"
+ x-uppercaseResponseId: "UPDATED_ENTITY"
+ uppercase_operation_id: "UPDATE_RELEASE"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "UPDATE_RELEASE"
+ 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: "UPDATE_RELEASE"
+ 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: "UPDATE_RELEASE"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "update_release"
+ uppercase_operation_id: "UPDATE_RELEASE"
+ path: "/release/:id"
+ HttpMethod: "Put"
+ httpmethod: "put"
+ noClientExample: true
+ delete:
+ operationId: "delete_release"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - name: "editgroup"
+ in: "query"
+ required: false
+ type: "string"
+ formatString: "{:?}"
+ example: "Some(\"editgroup_example\".to_string())"
+ responses:
+ 200:
+ description: "Deleted Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "DeletedEntity"
+ x-uppercaseResponseId: "DELETED_ENTITY"
+ uppercase_operation_id: "DELETE_RELEASE"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "DELETE_RELEASE"
+ 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: "DELETE_RELEASE"
+ 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: "DELETE_RELEASE"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "delete_release"
+ uppercase_operation_id: "DELETE_RELEASE"
+ path: "/release/:id"
+ HttpMethod: "Delete"
+ httpmethod: "delete"
/release/{id}/history:
get:
operationId: "get_release_history"
@@ -1554,6 +2038,127 @@ paths:
path: "/work/:id"
HttpMethod: "Get"
httpmethod: "get"
+ put:
+ operationId: "update_work"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - in: "body"
+ name: "entity"
+ required: true
+ schema:
+ $ref: "#/definitions/work_entity"
+ uppercase_data_type: "WORKENTITY"
+ refName: "work_entity"
+ formatString: "{:?}"
+ example: "???"
+ model_key: "editgroup_edits"
+ uppercase_operation_id: "UPDATE_WORK"
+ consumesJson: true
+ responses:
+ 200:
+ description: "Updated Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "UpdatedEntity"
+ x-uppercaseResponseId: "UPDATED_ENTITY"
+ uppercase_operation_id: "UPDATE_WORK"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "UPDATE_WORK"
+ 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: "UPDATE_WORK"
+ 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: "UPDATE_WORK"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "update_work"
+ uppercase_operation_id: "UPDATE_WORK"
+ path: "/work/:id"
+ HttpMethod: "Put"
+ httpmethod: "put"
+ noClientExample: true
+ delete:
+ operationId: "delete_work"
+ parameters:
+ - name: "id"
+ in: "path"
+ required: true
+ type: "string"
+ formatString: "\\\"{}\\\""
+ example: "\"id_example\".to_string()"
+ - name: "editgroup"
+ in: "query"
+ required: false
+ type: "string"
+ formatString: "{:?}"
+ example: "Some(\"editgroup_example\".to_string())"
+ responses:
+ 200:
+ description: "Deleted Entity"
+ schema:
+ $ref: "#/definitions/entity_edit"
+ x-responseId: "DeletedEntity"
+ x-uppercaseResponseId: "DELETED_ENTITY"
+ uppercase_operation_id: "DELETE_WORK"
+ uppercase_data_type: "ENTITYEDIT"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "DELETE_WORK"
+ 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: "DELETE_WORK"
+ 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: "DELETE_WORK"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ operation_id: "delete_work"
+ uppercase_operation_id: "DELETE_WORK"
+ path: "/work/:id"
+ HttpMethod: "Delete"
+ httpmethod: "delete"
/work/{id}/history:
get:
operationId: "get_work_history"
@@ -1888,11 +2493,11 @@ paths:
uppercase_data_type: "SUCCESS"
producesJson: true
400:
- description: "Unmergable"
+ description: "Bad Request"
schema:
$ref: "#/definitions/error_response"
- x-responseId: "Unmergable"
- x-uppercaseResponseId: "UNMERGABLE"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
uppercase_operation_id: "ACCEPT_EDITGROUP"
uppercase_data_type: "ERRORRESPONSE"
producesJson: true
@@ -1905,6 +2510,15 @@ paths:
uppercase_operation_id: "ACCEPT_EDITGROUP"
uppercase_data_type: "ERRORRESPONSE"
producesJson: true
+ 409:
+ description: "Edit Conflict"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "EditConflict"
+ x-uppercaseResponseId: "EDIT_CONFLICT"
+ uppercase_operation_id: "ACCEPT_EDITGROUP"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
500:
description: "Generic Error"
schema:
diff --git a/rust/fatcat-api/examples/client.rs b/rust/fatcat-api/examples/client.rs
index 34653196..cc94af11 100644
--- a/rust/fatcat-api/examples/client.rs
+++ b/rust/fatcat-api/examples/client.rs
@@ -13,10 +13,11 @@ use clap::{App, Arg};
#[allow(unused_imports)]
use fatcat::{
AcceptEditgroupResponse, ApiError, ApiNoContext, ContextWrapperExt, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse,
- CreateEditgroupResponse, 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,
+ CreateEditgroupResponse, CreateFileBatchResponse, CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerResponse,
+ DeleteCreatorResponse, DeleteFileResponse, DeleteReleaseResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerHistoryResponse, GetContainerResponse,
+ GetCreatorHistoryResponse, GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, GetFileResponse,
+ GetReleaseFilesResponse, GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse,
+ LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse, UpdateContainerResponse, UpdateCreatorResponse, UpdateFileResponse, UpdateReleaseResponse, UpdateWorkResponse,
};
#[allow(unused_imports)]
use futures::{future, stream, Future, Stream};
@@ -33,6 +34,11 @@ fn main() {
"CreateFileBatch",
"CreateReleaseBatch",
"CreateWorkBatch",
+ "DeleteContainer",
+ "DeleteCreator",
+ "DeleteFile",
+ "DeleteRelease",
+ "DeleteWork",
"GetChangelog",
"GetChangelogEntry",
"GetContainer",
@@ -145,6 +151,31 @@ fn main() {
println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
}
+ Some("DeleteContainer") => {
+ let result = client.delete_container("id_example".to_string(), Some("editgroup_example".to_string())).wait();
+ println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ }
+
+ Some("DeleteCreator") => {
+ let result = client.delete_creator("id_example".to_string(), Some("editgroup_example".to_string())).wait();
+ println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ }
+
+ Some("DeleteFile") => {
+ let result = client.delete_file("id_example".to_string(), Some("editgroup_example".to_string())).wait();
+ println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ }
+
+ Some("DeleteRelease") => {
+ let result = client.delete_release("id_example".to_string(), Some("editgroup_example".to_string())).wait();
+ println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ }
+
+ Some("DeleteWork") => {
+ let result = client.delete_work("id_example".to_string(), Some("editgroup_example".to_string())).wait();
+ 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>")));
@@ -260,6 +291,35 @@ fn main() {
println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
}
+ // Disabled because there's no example.
+ // Some("UpdateContainer") => {
+ // let result = client.update_container("id_example".to_string(), ???).wait();
+ // println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ // },
+
+ // Disabled because there's no example.
+ // Some("UpdateCreator") => {
+ // let result = client.update_creator("id_example".to_string(), ???).wait();
+ // println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ // },
+
+ // Disabled because there's no example.
+ // Some("UpdateFile") => {
+ // let result = client.update_file("id_example".to_string(), ???).wait();
+ // println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ // },
+
+ // Disabled because there's no example.
+ // Some("UpdateRelease") => {
+ // let result = client.update_release("id_example".to_string(), ???).wait();
+ // println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ // },
+
+ // Disabled because there's no example.
+ // Some("UpdateWork") => {
+ // let result = client.update_work("id_example".to_string(), ???).wait();
+ // println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ // },
_ => panic!("Invalid operation provided"),
}
}
diff --git a/rust/fatcat-api/examples/server_lib/server.rs b/rust/fatcat-api/examples/server_lib/server.rs
index 60e19847..ab08f594 100644
--- a/rust/fatcat-api/examples/server_lib/server.rs
+++ b/rust/fatcat-api/examples/server_lib/server.rs
@@ -12,10 +12,11 @@ use swagger;
use fatcat::models;
use fatcat::{
AcceptEditgroupResponse, Api, ApiError, Context, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse,
- 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,
+ CreateFileBatchResponse, CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerResponse, DeleteCreatorResponse,
+ DeleteFileResponse, DeleteReleaseResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerHistoryResponse, GetContainerResponse, GetCreatorHistoryResponse,
+ GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse,
+ GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse,
+ LookupFileResponse, LookupReleaseResponse, UpdateContainerResponse, UpdateCreatorResponse, UpdateFileResponse, UpdateReleaseResponse, UpdateWorkResponse,
};
#[derive(Copy, Clone)]
@@ -154,6 +155,61 @@ impl Api for Server {
Box::new(futures::failed("Generic failure".into()))
}
+ fn delete_container(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteContainerResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "delete_container(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ editgroup,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
+ fn delete_creator(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteCreatorResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "delete_creator(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ editgroup,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
+ fn delete_file(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteFileResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "delete_file(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ editgroup,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
+ fn delete_release(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteReleaseResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "delete_release(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ editgroup,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
+ fn delete_work(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteWorkResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "delete_work(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ editgroup,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ 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());
@@ -316,4 +372,49 @@ impl Api for Server {
println!("lookup_release(\"{}\") - X-Span-ID: {:?}", doi, context.x_span_id.unwrap_or(String::from("<none>")).clone());
Box::new(futures::failed("Generic failure".into()))
}
+
+ fn update_container(&self, id: String, entity: models::ContainerEntity, context: &Context) -> Box<Future<Item = UpdateContainerResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "update_container(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ entity,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
+ fn update_creator(&self, id: String, entity: models::CreatorEntity, context: &Context) -> Box<Future<Item = UpdateCreatorResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "update_creator(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ entity,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
+ fn update_file(&self, id: String, entity: models::FileEntity, context: &Context) -> Box<Future<Item = UpdateFileResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!("update_file(\"{}\", {:?}) - X-Span-ID: {:?}", id, entity, context.x_span_id.unwrap_or(String::from("<none>")).clone());
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
+ fn update_release(&self, id: String, entity: models::ReleaseEntity, context: &Context) -> Box<Future<Item = UpdateReleaseResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!(
+ "update_release(\"{}\", {:?}) - X-Span-ID: {:?}",
+ id,
+ entity,
+ context.x_span_id.unwrap_or(String::from("<none>")).clone()
+ );
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
+ fn update_work(&self, id: String, entity: models::WorkEntity, context: &Context) -> Box<Future<Item = UpdateWorkResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!("update_work(\"{}\", {:?}) - X-Span-ID: {:?}", id, entity, context.x_span_id.unwrap_or(String::from("<none>")).clone());
+ Box::new(futures::failed("Generic failure".into()))
+ }
}
diff --git a/rust/fatcat-api/src/client.rs b/rust/fatcat-api/src/client.rs
index 628d8894..6f61f773 100644
--- a/rust/fatcat-api/src/client.rs
+++ b/rust/fatcat-api/src/client.rs
@@ -36,10 +36,11 @@ use swagger::{ApiError, Context, XSpanId};
use models;
use {
AcceptEditgroupResponse, Api, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, 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,
+ CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerResponse, DeleteCreatorResponse, DeleteFileResponse,
+ DeleteReleaseResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerHistoryResponse, GetContainerResponse, GetCreatorHistoryResponse,
+ GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse,
+ GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse,
+ LookupFileResponse, LookupReleaseResponse, UpdateContainerResponse, UpdateCreatorResponse, UpdateFileResponse, UpdateReleaseResponse, UpdateWorkResponse,
};
/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
@@ -191,7 +192,7 @@ impl Api for Client {
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(AcceptEditgroupResponse::Unmergable(body))
+ Ok(AcceptEditgroupResponse::BadRequest(body))
}
404 => {
let mut buf = String::new();
@@ -200,6 +201,13 @@ impl Api for Client {
Ok(AcceptEditgroupResponse::NotFound(body))
}
+ 409 => {
+ 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(AcceptEditgroupResponse::EditConflict(body))
+ }
500 => {
let mut buf = String::new();
response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
@@ -1008,6 +1016,346 @@ impl Api for Client {
Box::new(futures::done(result))
}
+ fn delete_container(&self, param_id: String, param_editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteContainerResponse, Error = ApiError> + Send> {
+ // Query parameters
+ let query_editgroup = param_editgroup.map_or_else(String::new, |query| format!("editgroup={editgroup}&", editgroup = query.to_string()));
+
+ let url = format!(
+ "{}/v0/container/{id}?{editgroup}",
+ self.base_path,
+ id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET),
+ editgroup = utf8_percent_encode(&query_editgroup, QUERY_ENCODE_SET)
+ );
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Delete, &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<DeleteContainerResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(DeleteContainerResponse::DeletedEntity(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(DeleteContainerResponse::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(DeleteContainerResponse::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(DeleteContainerResponse::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 delete_creator(&self, param_id: String, param_editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteCreatorResponse, Error = ApiError> + Send> {
+ // Query parameters
+ let query_editgroup = param_editgroup.map_or_else(String::new, |query| format!("editgroup={editgroup}&", editgroup = query.to_string()));
+
+ let url = format!(
+ "{}/v0/creator/{id}?{editgroup}",
+ self.base_path,
+ id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET),
+ editgroup = utf8_percent_encode(&query_editgroup, QUERY_ENCODE_SET)
+ );
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Delete, &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<DeleteCreatorResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(DeleteCreatorResponse::DeletedEntity(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(DeleteCreatorResponse::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(DeleteCreatorResponse::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(DeleteCreatorResponse::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 delete_file(&self, param_id: String, param_editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteFileResponse, Error = ApiError> + Send> {
+ // Query parameters
+ let query_editgroup = param_editgroup.map_or_else(String::new, |query| format!("editgroup={editgroup}&", editgroup = query.to_string()));
+
+ let url = format!(
+ "{}/v0/file/{id}?{editgroup}",
+ self.base_path,
+ id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET),
+ editgroup = utf8_percent_encode(&query_editgroup, QUERY_ENCODE_SET)
+ );
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Delete, &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<DeleteFileResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(DeleteFileResponse::DeletedEntity(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(DeleteFileResponse::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(DeleteFileResponse::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(DeleteFileResponse::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 delete_release(&self, param_id: String, param_editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteReleaseResponse, Error = ApiError> + Send> {
+ // Query parameters
+ let query_editgroup = param_editgroup.map_or_else(String::new, |query| format!("editgroup={editgroup}&", editgroup = query.to_string()));
+
+ let url = format!(
+ "{}/v0/release/{id}?{editgroup}",
+ self.base_path,
+ id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET),
+ editgroup = utf8_percent_encode(&query_editgroup, QUERY_ENCODE_SET)
+ );
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Delete, &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<DeleteReleaseResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(DeleteReleaseResponse::DeletedEntity(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(DeleteReleaseResponse::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(DeleteReleaseResponse::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(DeleteReleaseResponse::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 delete_work(&self, param_id: String, param_editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteWorkResponse, Error = ApiError> + Send> {
+ // Query parameters
+ let query_editgroup = param_editgroup.map_or_else(String::new, |query| format!("editgroup={editgroup}&", editgroup = query.to_string()));
+
+ let url = format!(
+ "{}/v0/work/{id}?{editgroup}",
+ self.base_path,
+ id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET),
+ editgroup = utf8_percent_encode(&query_editgroup, QUERY_ENCODE_SET)
+ );
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Delete, &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<DeleteWorkResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(DeleteWorkResponse::DeletedEntity(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(DeleteWorkResponse::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(DeleteWorkResponse::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(DeleteWorkResponse::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(&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()));
@@ -2436,6 +2784,331 @@ impl Api for Client {
let result = request.send().map_err(|e| ApiError(format!("No response received: {}", e))).and_then(parse_response);
Box::new(futures::done(result))
}
+
+ fn update_container(&self, param_id: String, param_entity: models::ContainerEntity, context: &Context) -> Box<Future<Item = UpdateContainerResponse, Error = ApiError> + Send> {
+ let url = format!("{}/v0/container/{id}", self.base_path, id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET));
+
+ let body = serde_json::to_string(&param_entity).expect("impossible to fail to serialize");
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Put, &url);
+ let mut custom_headers = hyper::header::Headers::new();
+
+ let request = request.body(&body);
+
+ custom_headers.set(ContentType(mimetypes::requests::UPDATE_CONTAINER.clone()));
+ 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<UpdateContainerResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(UpdateContainerResponse::UpdatedEntity(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(UpdateContainerResponse::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(UpdateContainerResponse::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(UpdateContainerResponse::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 update_creator(&self, param_id: String, param_entity: models::CreatorEntity, context: &Context) -> Box<Future<Item = UpdateCreatorResponse, Error = ApiError> + Send> {
+ let url = format!("{}/v0/creator/{id}", self.base_path, id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET));
+
+ let body = serde_json::to_string(&param_entity).expect("impossible to fail to serialize");
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Put, &url);
+ let mut custom_headers = hyper::header::Headers::new();
+
+ let request = request.body(&body);
+
+ custom_headers.set(ContentType(mimetypes::requests::UPDATE_CREATOR.clone()));
+ 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<UpdateCreatorResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(UpdateCreatorResponse::UpdatedEntity(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(UpdateCreatorResponse::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(UpdateCreatorResponse::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(UpdateCreatorResponse::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 update_file(&self, param_id: String, param_entity: models::FileEntity, context: &Context) -> Box<Future<Item = UpdateFileResponse, Error = ApiError> + Send> {
+ let url = format!("{}/v0/file/{id}", self.base_path, id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET));
+
+ let body = serde_json::to_string(&param_entity).expect("impossible to fail to serialize");
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Put, &url);
+ let mut custom_headers = hyper::header::Headers::new();
+
+ let request = request.body(&body);
+
+ custom_headers.set(ContentType(mimetypes::requests::UPDATE_FILE.clone()));
+ 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<UpdateFileResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(UpdateFileResponse::UpdatedEntity(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(UpdateFileResponse::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(UpdateFileResponse::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(UpdateFileResponse::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 update_release(&self, param_id: String, param_entity: models::ReleaseEntity, context: &Context) -> Box<Future<Item = UpdateReleaseResponse, Error = ApiError> + Send> {
+ let url = format!("{}/v0/release/{id}", self.base_path, id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET));
+
+ let body = serde_json::to_string(&param_entity).expect("impossible to fail to serialize");
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Put, &url);
+ let mut custom_headers = hyper::header::Headers::new();
+
+ let request = request.body(&body);
+
+ custom_headers.set(ContentType(mimetypes::requests::UPDATE_RELEASE.clone()));
+ 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<UpdateReleaseResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(UpdateReleaseResponse::UpdatedEntity(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(UpdateReleaseResponse::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(UpdateReleaseResponse::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(UpdateReleaseResponse::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 update_work(&self, param_id: String, param_entity: models::WorkEntity, context: &Context) -> Box<Future<Item = UpdateWorkResponse, Error = ApiError> + Send> {
+ let url = format!("{}/v0/work/{id}", self.base_path, id = utf8_percent_encode(&param_id.to_string(), PATH_SEGMENT_ENCODE_SET));
+
+ let body = serde_json::to_string(&param_entity).expect("impossible to fail to serialize");
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Put, &url);
+ let mut custom_headers = hyper::header::Headers::new();
+
+ let request = request.body(&body);
+
+ custom_headers.set(ContentType(mimetypes::requests::UPDATE_WORK.clone()));
+ 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<UpdateWorkResponse, 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::EntityEdit>(&buf)?;
+
+ Ok(UpdateWorkResponse::UpdatedEntity(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(UpdateWorkResponse::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(UpdateWorkResponse::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(UpdateWorkResponse::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))
+ }
}
#[derive(Debug)]
diff --git a/rust/fatcat-api/src/lib.rs b/rust/fatcat-api/src/lib.rs
index 5de3647b..fc1ae2a1 100644
--- a/rust/fatcat-api/src/lib.rs
+++ b/rust/fatcat-api/src/lib.rs
@@ -36,10 +36,12 @@ pub use swagger::{ApiError, Context, ContextWrapper};
pub enum AcceptEditgroupResponse {
/// Merged Successfully
MergedSuccessfully(models::Success),
- /// Unmergable
- Unmergable(models::ErrorResponse),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
/// Not Found
NotFound(models::ErrorResponse),
+ /// Edit Conflict
+ EditConflict(models::ErrorResponse),
/// Generic Error
GenericError(models::ErrorResponse),
}
@@ -175,6 +177,66 @@ pub enum CreateWorkBatchResponse {
}
#[derive(Debug, PartialEq)]
+pub enum DeleteContainerResponse {
+ /// Deleted Entity
+ DeletedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum DeleteCreatorResponse {
+ /// Deleted Entity
+ DeletedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum DeleteFileResponse {
+ /// Deleted Entity
+ DeletedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum DeleteReleaseResponse {
+ /// Deleted Entity
+ DeletedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum DeleteWorkResponse {
+ /// Deleted Entity
+ DeletedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
pub enum GetChangelogResponse {
/// Success
Success(Vec<models::ChangelogEntry>),
@@ -436,6 +498,66 @@ pub enum LookupReleaseResponse {
GenericError(models::ErrorResponse),
}
+#[derive(Debug, PartialEq)]
+pub enum UpdateContainerResponse {
+ /// Updated Entity
+ UpdatedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum UpdateCreatorResponse {
+ /// Updated Entity
+ UpdatedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum UpdateFileResponse {
+ /// Updated Entity
+ UpdatedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum UpdateReleaseResponse {
+ /// Updated Entity
+ UpdatedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum UpdateWorkResponse {
+ /// Updated Entity
+ UpdatedEntity(models::EntityEdit),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Found
+ NotFound(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
/// API
pub trait Api {
fn accept_editgroup(&self, id: String, context: &Context) -> Box<Future<Item = AcceptEditgroupResponse, Error = ApiError> + Send>;
@@ -492,6 +614,16 @@ pub trait Api {
context: &Context,
) -> Box<Future<Item = CreateWorkBatchResponse, Error = ApiError> + Send>;
+ fn delete_container(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteContainerResponse, Error = ApiError> + Send>;
+
+ fn delete_creator(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteCreatorResponse, Error = ApiError> + Send>;
+
+ fn delete_file(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteFileResponse, Error = ApiError> + Send>;
+
+ fn delete_release(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteReleaseResponse, Error = ApiError> + Send>;
+
+ fn delete_work(&self, id: String, editgroup: Option<String>, context: &Context) -> Box<Future<Item = DeleteWorkResponse, 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>;
@@ -537,6 +669,16 @@ pub trait Api {
fn lookup_file(&self, sha1: String, context: &Context) -> Box<Future<Item = LookupFileResponse, Error = ApiError> + Send>;
fn lookup_release(&self, doi: String, context: &Context) -> Box<Future<Item = LookupReleaseResponse, Error = ApiError> + Send>;
+
+ fn update_container(&self, id: String, entity: models::ContainerEntity, context: &Context) -> Box<Future<Item = UpdateContainerResponse, Error = ApiError> + Send>;
+
+ fn update_creator(&self, id: String, entity: models::CreatorEntity, context: &Context) -> Box<Future<Item = UpdateCreatorResponse, Error = ApiError> + Send>;
+
+ fn update_file(&self, id: String, entity: models::FileEntity, context: &Context) -> Box<Future<Item = UpdateFileResponse, Error = ApiError> + Send>;
+
+ fn update_release(&self, id: String, entity: models::ReleaseEntity, context: &Context) -> Box<Future<Item = UpdateReleaseResponse, Error = ApiError> + Send>;
+
+ fn update_work(&self, id: String, entity: models::WorkEntity, context: &Context) -> Box<Future<Item = UpdateWorkResponse, Error = ApiError> + Send>;
}
/// API without a `Context`
@@ -580,6 +722,16 @@ pub trait ApiNoContext {
fn create_work_batch(&self, entity_list: &Vec<models::WorkEntity>, autoaccept: Option<bool>, editgroup: Option<String>) -> Box<Future<Item = CreateWorkBatchResponse, Error = ApiError> + Send>;
+ fn delete_container(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteContainerResponse, Error = ApiError> + Send>;
+
+ fn delete_creator(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteCreatorResponse, Error = ApiError> + Send>;
+
+ fn delete_file(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteFileResponse, Error = ApiError> + Send>;
+
+ fn delete_release(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteReleaseResponse, Error = ApiError> + Send>;
+
+ fn delete_work(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteWorkResponse, 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>;
@@ -625,6 +777,16 @@ pub trait ApiNoContext {
fn lookup_file(&self, sha1: String) -> Box<Future<Item = LookupFileResponse, Error = ApiError> + Send>;
fn lookup_release(&self, doi: String) -> Box<Future<Item = LookupReleaseResponse, Error = ApiError> + Send>;
+
+ fn update_container(&self, id: String, entity: models::ContainerEntity) -> Box<Future<Item = UpdateContainerResponse, Error = ApiError> + Send>;
+
+ fn update_creator(&self, id: String, entity: models::CreatorEntity) -> Box<Future<Item = UpdateCreatorResponse, Error = ApiError> + Send>;
+
+ fn update_file(&self, id: String, entity: models::FileEntity) -> Box<Future<Item = UpdateFileResponse, Error = ApiError> + Send>;
+
+ fn update_release(&self, id: String, entity: models::ReleaseEntity) -> Box<Future<Item = UpdateReleaseResponse, Error = ApiError> + Send>;
+
+ fn update_work(&self, id: String, entity: models::WorkEntity) -> Box<Future<Item = UpdateWorkResponse, Error = ApiError> + Send>;
}
/// Trait to extend an API to make it easy to bind it to a context.
@@ -706,6 +868,26 @@ impl<'a, T: Api> ApiNoContext for ContextWrapper<'a, T> {
self.api().create_work_batch(entity_list, autoaccept, editgroup, &self.context())
}
+ fn delete_container(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteContainerResponse, Error = ApiError> + Send> {
+ self.api().delete_container(id, editgroup, &self.context())
+ }
+
+ fn delete_creator(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteCreatorResponse, Error = ApiError> + Send> {
+ self.api().delete_creator(id, editgroup, &self.context())
+ }
+
+ fn delete_file(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteFileResponse, Error = ApiError> + Send> {
+ self.api().delete_file(id, editgroup, &self.context())
+ }
+
+ fn delete_release(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteReleaseResponse, Error = ApiError> + Send> {
+ self.api().delete_release(id, editgroup, &self.context())
+ }
+
+ fn delete_work(&self, id: String, editgroup: Option<String>) -> Box<Future<Item = DeleteWorkResponse, Error = ApiError> + Send> {
+ self.api().delete_work(id, editgroup, &self.context())
+ }
+
fn get_changelog(&self, limit: Option<i64>) -> Box<Future<Item = GetChangelogResponse, Error = ApiError> + Send> {
self.api().get_changelog(limit, &self.context())
}
@@ -797,6 +979,26 @@ impl<'a, T: Api> ApiNoContext for ContextWrapper<'a, T> {
fn lookup_release(&self, doi: String) -> Box<Future<Item = LookupReleaseResponse, Error = ApiError> + Send> {
self.api().lookup_release(doi, &self.context())
}
+
+ fn update_container(&self, id: String, entity: models::ContainerEntity) -> Box<Future<Item = UpdateContainerResponse, Error = ApiError> + Send> {
+ self.api().update_container(id, entity, &self.context())
+ }
+
+ fn update_creator(&self, id: String, entity: models::CreatorEntity) -> Box<Future<Item = UpdateCreatorResponse, Error = ApiError> + Send> {
+ self.api().update_creator(id, entity, &self.context())
+ }
+
+ fn update_file(&self, id: String, entity: models::FileEntity) -> Box<Future<Item = UpdateFileResponse, Error = ApiError> + Send> {
+ self.api().update_file(id, entity, &self.context())
+ }
+
+ fn update_release(&self, id: String, entity: models::ReleaseEntity) -> Box<Future<Item = UpdateReleaseResponse, Error = ApiError> + Send> {
+ self.api().update_release(id, entity, &self.context())
+ }
+
+ fn update_work(&self, id: String, entity: models::WorkEntity) -> Box<Future<Item = UpdateWorkResponse, Error = ApiError> + Send> {
+ self.api().update_work(id, entity, &self.context())
+ }
}
#[cfg(feature = "client")]
diff --git a/rust/fatcat-api/src/mimetypes.rs b/rust/fatcat-api/src/mimetypes.rs
index 53b582dc..2c54a313 100644
--- a/rust/fatcat-api/src/mimetypes.rs
+++ b/rust/fatcat-api/src/mimetypes.rs
@@ -10,7 +10,7 @@ pub mod responses {
}
/// Create Mime objects for the response content types for AcceptEditgroup
lazy_static! {
- pub static ref ACCEPT_EDITGROUP_UNMERGABLE: Mime = mime!(Application / Json);
+ pub static ref ACCEPT_EDITGROUP_BAD_REQUEST: Mime = mime!(Application / Json);
}
/// Create Mime objects for the response content types for AcceptEditgroup
lazy_static! {
@@ -18,6 +18,10 @@ pub mod responses {
}
/// Create Mime objects for the response content types for AcceptEditgroup
lazy_static! {
+ pub static ref ACCEPT_EDITGROUP_EDIT_CONFLICT: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for AcceptEditgroup
+ lazy_static! {
pub static ref ACCEPT_EDITGROUP_GENERIC_ERROR: Mime = mime!(Application / Json);
}
/// Create Mime objects for the response content types for CreateContainer
@@ -192,6 +196,86 @@ 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 DeleteContainer
+ lazy_static! {
+ pub static ref DELETE_CONTAINER_DELETED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteContainer
+ lazy_static! {
+ pub static ref DELETE_CONTAINER_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteContainer
+ lazy_static! {
+ pub static ref DELETE_CONTAINER_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteContainer
+ lazy_static! {
+ pub static ref DELETE_CONTAINER_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteCreator
+ lazy_static! {
+ pub static ref DELETE_CREATOR_DELETED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteCreator
+ lazy_static! {
+ pub static ref DELETE_CREATOR_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteCreator
+ lazy_static! {
+ pub static ref DELETE_CREATOR_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteCreator
+ lazy_static! {
+ pub static ref DELETE_CREATOR_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteFile
+ lazy_static! {
+ pub static ref DELETE_FILE_DELETED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteFile
+ lazy_static! {
+ pub static ref DELETE_FILE_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteFile
+ lazy_static! {
+ pub static ref DELETE_FILE_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteFile
+ lazy_static! {
+ pub static ref DELETE_FILE_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteRelease
+ lazy_static! {
+ pub static ref DELETE_RELEASE_DELETED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteRelease
+ lazy_static! {
+ pub static ref DELETE_RELEASE_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteRelease
+ lazy_static! {
+ pub static ref DELETE_RELEASE_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteRelease
+ lazy_static! {
+ pub static ref DELETE_RELEASE_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteWork
+ lazy_static! {
+ pub static ref DELETE_WORK_DELETED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteWork
+ lazy_static! {
+ pub static ref DELETE_WORK_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteWork
+ lazy_static! {
+ pub static ref DELETE_WORK_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for DeleteWork
+ lazy_static! {
+ pub static ref DELETE_WORK_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);
@@ -532,6 +616,86 @@ pub mod responses {
lazy_static! {
pub static ref LOOKUP_RELEASE_GENERIC_ERROR: Mime = mime!(Application / Json);
}
+ /// Create Mime objects for the response content types for UpdateContainer
+ lazy_static! {
+ pub static ref UPDATE_CONTAINER_UPDATED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateContainer
+ lazy_static! {
+ pub static ref UPDATE_CONTAINER_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateContainer
+ lazy_static! {
+ pub static ref UPDATE_CONTAINER_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateContainer
+ lazy_static! {
+ pub static ref UPDATE_CONTAINER_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateCreator
+ lazy_static! {
+ pub static ref UPDATE_CREATOR_UPDATED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateCreator
+ lazy_static! {
+ pub static ref UPDATE_CREATOR_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateCreator
+ lazy_static! {
+ pub static ref UPDATE_CREATOR_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateCreator
+ lazy_static! {
+ pub static ref UPDATE_CREATOR_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateFile
+ lazy_static! {
+ pub static ref UPDATE_FILE_UPDATED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateFile
+ lazy_static! {
+ pub static ref UPDATE_FILE_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateFile
+ lazy_static! {
+ pub static ref UPDATE_FILE_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateFile
+ lazy_static! {
+ pub static ref UPDATE_FILE_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateRelease
+ lazy_static! {
+ pub static ref UPDATE_RELEASE_UPDATED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateRelease
+ lazy_static! {
+ pub static ref UPDATE_RELEASE_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateRelease
+ lazy_static! {
+ pub static ref UPDATE_RELEASE_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateRelease
+ lazy_static! {
+ pub static ref UPDATE_RELEASE_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateWork
+ lazy_static! {
+ pub static ref UPDATE_WORK_UPDATED_ENTITY: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateWork
+ lazy_static! {
+ pub static ref UPDATE_WORK_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateWork
+ lazy_static! {
+ pub static ref UPDATE_WORK_NOT_FOUND: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for UpdateWork
+ lazy_static! {
+ pub static ref UPDATE_WORK_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
}
@@ -581,5 +745,25 @@ pub mod requests {
lazy_static! {
pub static ref CREATE_WORK_BATCH: Mime = mime!(Application / Json);
}
+ /// Create Mime objects for the request content types for UpdateContainer
+ lazy_static! {
+ pub static ref UPDATE_CONTAINER: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the request content types for UpdateCreator
+ lazy_static! {
+ pub static ref UPDATE_CREATOR: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the request content types for UpdateFile
+ lazy_static! {
+ pub static ref UPDATE_FILE: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the request content types for UpdateRelease
+ lazy_static! {
+ pub static ref UPDATE_RELEASE: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the request content types for UpdateWork
+ lazy_static! {
+ pub static ref UPDATE_WORK: Mime = mime!(Application / Json);
+ }
}
diff --git a/rust/fatcat-api/src/server.rs b/rust/fatcat-api/src/server.rs
index 1ba9a218..04d10e14 100644
--- a/rust/fatcat-api/src/server.rs
+++ b/rust/fatcat-api/src/server.rs
@@ -38,10 +38,11 @@ use swagger::{ApiError, Context, XSpanId};
use models;
use {
AcceptEditgroupResponse, Api, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse, 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,
+ CreateFileResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerResponse, DeleteCreatorResponse, DeleteFileResponse,
+ DeleteReleaseResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerHistoryResponse, GetContainerResponse, GetCreatorHistoryResponse,
+ GetCreatorReleasesResponse, GetCreatorResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileHistoryResponse, GetFileResponse, GetReleaseFilesResponse,
+ GetReleaseHistoryResponse, GetReleaseResponse, GetStatsResponse, GetWorkHistoryResponse, GetWorkReleasesResponse, GetWorkResponse, LookupContainerResponse, LookupCreatorResponse,
+ LookupFileResponse, LookupReleaseResponse, UpdateContainerResponse, UpdateCreatorResponse, UpdateFileResponse, UpdateReleaseResponse, UpdateWorkResponse,
};
header! { (Warning, "Warning") => [String] }
@@ -130,11 +131,11 @@ where
Ok(response)
}
- AcceptEditgroupResponse::Unmergable(body) => {
+ AcceptEditgroupResponse::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::ACCEPT_EDITGROUP_UNMERGABLE.clone()));
+ response.headers.set(ContentType(mimetypes::responses::ACCEPT_EDITGROUP_BAD_REQUEST.clone()));
context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
@@ -150,6 +151,16 @@ where
Ok(response)
}
+ AcceptEditgroupResponse::EditConflict(body) => {
+ let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");
+
+ let mut response = Response::with((status::Status::from_u16(409), body_string));
+ response.headers.set(ContentType(mimetypes::responses::ACCEPT_EDITGROUP_EDIT_CONFLICT.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
AcceptEditgroupResponse::GenericError(body) => {
let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");
@@ -1356,6 +1367,466 @@ where
);
let api_clone = api.clone();
+ router.delete(
+ "/v0/container/: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))))?
+ };
+
+ // 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_editgroup = query_params.get("editgroup").and_then(|list| list.first()).and_then(|x| x.parse::<String>().ok());
+
+ match api.delete_container(param_id, param_editgroup, context).wait() {
+ Ok(rsp) => match rsp {
+ DeleteContainerResponse::DeletedEntity(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::DELETE_CONTAINER_DELETED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteContainerResponse::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::DELETE_CONTAINER_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteContainerResponse::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::DELETE_CONTAINER_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteContainerResponse::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::DELETE_CONTAINER_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)
+ })
+ },
+ "DeleteContainer",
+ );
+
+ let api_clone = api.clone();
+ router.delete(
+ "/v0/creator/: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))))?
+ };
+
+ // 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_editgroup = query_params.get("editgroup").and_then(|list| list.first()).and_then(|x| x.parse::<String>().ok());
+
+ match api.delete_creator(param_id, param_editgroup, context).wait() {
+ Ok(rsp) => match rsp {
+ DeleteCreatorResponse::DeletedEntity(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::DELETE_CREATOR_DELETED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteCreatorResponse::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::DELETE_CREATOR_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteCreatorResponse::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::DELETE_CREATOR_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteCreatorResponse::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::DELETE_CREATOR_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)
+ })
+ },
+ "DeleteCreator",
+ );
+
+ let api_clone = api.clone();
+ router.delete(
+ "/v0/file/: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))))?
+ };
+
+ // 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_editgroup = query_params.get("editgroup").and_then(|list| list.first()).and_then(|x| x.parse::<String>().ok());
+
+ match api.delete_file(param_id, param_editgroup, context).wait() {
+ Ok(rsp) => match rsp {
+ DeleteFileResponse::DeletedEntity(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::DELETE_FILE_DELETED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteFileResponse::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::DELETE_FILE_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteFileResponse::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::DELETE_FILE_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteFileResponse::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::DELETE_FILE_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)
+ })
+ },
+ "DeleteFile",
+ );
+
+ let api_clone = api.clone();
+ router.delete(
+ "/v0/release/: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))))?
+ };
+
+ // 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_editgroup = query_params.get("editgroup").and_then(|list| list.first()).and_then(|x| x.parse::<String>().ok());
+
+ match api.delete_release(param_id, param_editgroup, context).wait() {
+ Ok(rsp) => match rsp {
+ DeleteReleaseResponse::DeletedEntity(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::DELETE_RELEASE_DELETED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteReleaseResponse::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::DELETE_RELEASE_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteReleaseResponse::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::DELETE_RELEASE_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteReleaseResponse::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::DELETE_RELEASE_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)
+ })
+ },
+ "DeleteRelease",
+ );
+
+ let api_clone = api.clone();
+ router.delete(
+ "/v0/work/: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))))?
+ };
+
+ // 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_editgroup = query_params.get("editgroup").and_then(|list| list.first()).and_then(|x| x.parse::<String>().ok());
+
+ match api.delete_work(param_id, param_editgroup, context).wait() {
+ Ok(rsp) => match rsp {
+ DeleteWorkResponse::DeletedEntity(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::DELETE_WORK_DELETED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteWorkResponse::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::DELETE_WORK_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteWorkResponse::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::DELETE_WORK_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ DeleteWorkResponse::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::DELETE_WORK_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)
+ })
+ },
+ "DeleteWork",
+ );
+
+ let api_clone = api.clone();
router.get(
"/v0/changelog",
move |req: &mut Request| {
@@ -3312,6 +3783,606 @@ where
},
"LookupRelease",
);
+
+ let api_clone = api.clone();
+ router.put(
+ "/v0/container/: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))))?
+ };
+
+ // Body parameters (note that non-required body parameters will ignore garbage
+ // values, rather than causing a 400 response). Produce warning header and logs for
+ // any unused fields.
+
+ let param_entity = req.get::<bodyparser::Raw>()
+ .map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - not valid UTF-8: {}", e))))?;
+
+ let mut unused_elements = Vec::new();
+
+ let param_entity = if let Some(param_entity_raw) = param_entity {
+ let deserializer = &mut serde_json::Deserializer::from_str(&param_entity_raw);
+
+ let param_entity: Option<models::ContainerEntity> =
+ serde_ignored::deserialize(deserializer, |path| {
+ warn!("Ignoring unknown field in body: {}", path);
+ unused_elements.push(path.to_string());
+ }).map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - doesn't match schema: {}", e))))?;
+
+ param_entity
+ } else {
+ None
+ };
+ let param_entity = param_entity.ok_or_else(|| Response::with((status::BadRequest, "Missing required body parameter entity".to_string())))?;
+
+ match api.update_container(param_id, param_entity, context).wait() {
+ Ok(rsp) => match rsp {
+ UpdateContainerResponse::UpdatedEntity(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::UPDATE_CONTAINER_UPDATED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateContainerResponse::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::UPDATE_CONTAINER_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateContainerResponse::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::UPDATE_CONTAINER_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateContainerResponse::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::UPDATE_CONTAINER_GENERIC_ERROR.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ 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)
+ })
+ },
+ "UpdateContainer",
+ );
+
+ let api_clone = api.clone();
+ router.put(
+ "/v0/creator/: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))))?
+ };
+
+ // Body parameters (note that non-required body parameters will ignore garbage
+ // values, rather than causing a 400 response). Produce warning header and logs for
+ // any unused fields.
+
+ let param_entity = req.get::<bodyparser::Raw>()
+ .map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - not valid UTF-8: {}", e))))?;
+
+ let mut unused_elements = Vec::new();
+
+ let param_entity = if let Some(param_entity_raw) = param_entity {
+ let deserializer = &mut serde_json::Deserializer::from_str(&param_entity_raw);
+
+ let param_entity: Option<models::CreatorEntity> =
+ serde_ignored::deserialize(deserializer, |path| {
+ warn!("Ignoring unknown field in body: {}", path);
+ unused_elements.push(path.to_string());
+ }).map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - doesn't match schema: {}", e))))?;
+
+ param_entity
+ } else {
+ None
+ };
+ let param_entity = param_entity.ok_or_else(|| Response::with((status::BadRequest, "Missing required body parameter entity".to_string())))?;
+
+ match api.update_creator(param_id, param_entity, context).wait() {
+ Ok(rsp) => match rsp {
+ UpdateCreatorResponse::UpdatedEntity(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::UPDATE_CREATOR_UPDATED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateCreatorResponse::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::UPDATE_CREATOR_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateCreatorResponse::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::UPDATE_CREATOR_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateCreatorResponse::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::UPDATE_CREATOR_GENERIC_ERROR.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ 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)
+ })
+ },
+ "UpdateCreator",
+ );
+
+ let api_clone = api.clone();
+ router.put(
+ "/v0/file/: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))))?
+ };
+
+ // Body parameters (note that non-required body parameters will ignore garbage
+ // values, rather than causing a 400 response). Produce warning header and logs for
+ // any unused fields.
+
+ let param_entity = req.get::<bodyparser::Raw>()
+ .map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - not valid UTF-8: {}", e))))?;
+
+ let mut unused_elements = Vec::new();
+
+ let param_entity = if let Some(param_entity_raw) = param_entity {
+ let deserializer = &mut serde_json::Deserializer::from_str(&param_entity_raw);
+
+ let param_entity: Option<models::FileEntity> =
+ serde_ignored::deserialize(deserializer, |path| {
+ warn!("Ignoring unknown field in body: {}", path);
+ unused_elements.push(path.to_string());
+ }).map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - doesn't match schema: {}", e))))?;
+
+ param_entity
+ } else {
+ None
+ };
+ let param_entity = param_entity.ok_or_else(|| Response::with((status::BadRequest, "Missing required body parameter entity".to_string())))?;
+
+ match api.update_file(param_id, param_entity, context).wait() {
+ Ok(rsp) => match rsp {
+ UpdateFileResponse::UpdatedEntity(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::UPDATE_FILE_UPDATED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateFileResponse::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::UPDATE_FILE_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateFileResponse::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::UPDATE_FILE_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateFileResponse::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::UPDATE_FILE_GENERIC_ERROR.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ 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)
+ })
+ },
+ "UpdateFile",
+ );
+
+ let api_clone = api.clone();
+ router.put(
+ "/v0/release/: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))))?
+ };
+
+ // Body parameters (note that non-required body parameters will ignore garbage
+ // values, rather than causing a 400 response). Produce warning header and logs for
+ // any unused fields.
+
+ let param_entity = req.get::<bodyparser::Raw>()
+ .map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - not valid UTF-8: {}", e))))?;
+
+ let mut unused_elements = Vec::new();
+
+ let param_entity = if let Some(param_entity_raw) = param_entity {
+ let deserializer = &mut serde_json::Deserializer::from_str(&param_entity_raw);
+
+ let param_entity: Option<models::ReleaseEntity> =
+ serde_ignored::deserialize(deserializer, |path| {
+ warn!("Ignoring unknown field in body: {}", path);
+ unused_elements.push(path.to_string());
+ }).map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - doesn't match schema: {}", e))))?;
+
+ param_entity
+ } else {
+ None
+ };
+ let param_entity = param_entity.ok_or_else(|| Response::with((status::BadRequest, "Missing required body parameter entity".to_string())))?;
+
+ match api.update_release(param_id, param_entity, context).wait() {
+ Ok(rsp) => match rsp {
+ UpdateReleaseResponse::UpdatedEntity(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::UPDATE_RELEASE_UPDATED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateReleaseResponse::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::UPDATE_RELEASE_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateReleaseResponse::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::UPDATE_RELEASE_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateReleaseResponse::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::UPDATE_RELEASE_GENERIC_ERROR.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ 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)
+ })
+ },
+ "UpdateRelease",
+ );
+
+ let api_clone = api.clone();
+ router.put(
+ "/v0/work/: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))))?
+ };
+
+ // Body parameters (note that non-required body parameters will ignore garbage
+ // values, rather than causing a 400 response). Produce warning header and logs for
+ // any unused fields.
+
+ let param_entity = req.get::<bodyparser::Raw>()
+ .map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - not valid UTF-8: {}", e))))?;
+
+ let mut unused_elements = Vec::new();
+
+ let param_entity = if let Some(param_entity_raw) = param_entity {
+ let deserializer = &mut serde_json::Deserializer::from_str(&param_entity_raw);
+
+ let param_entity: Option<models::WorkEntity> =
+ serde_ignored::deserialize(deserializer, |path| {
+ warn!("Ignoring unknown field in body: {}", path);
+ unused_elements.push(path.to_string());
+ }).map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter entity - doesn't match schema: {}", e))))?;
+
+ param_entity
+ } else {
+ None
+ };
+ let param_entity = param_entity.ok_or_else(|| Response::with((status::BadRequest, "Missing required body parameter entity".to_string())))?;
+
+ match api.update_work(param_id, param_entity, context).wait() {
+ Ok(rsp) => match rsp {
+ UpdateWorkResponse::UpdatedEntity(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::UPDATE_WORK_UPDATED_ENTITY.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateWorkResponse::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::UPDATE_WORK_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateWorkResponse::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::UPDATE_WORK_NOT_FOUND.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ Ok(response)
+ }
+ UpdateWorkResponse::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::UPDATE_WORK_GENERIC_ERROR.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ if !unused_elements.is_empty() {
+ response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
+ }
+ 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)
+ })
+ },
+ "UpdateWork",
+ );
}
/// Middleware to extract authentication data from request
diff --git a/rust/src/api_helpers.rs b/rust/src/api_helpers.rs
index 020aad76..6c9a4e5f 100644
--- a/rust/src/api_helpers.rs
+++ b/rust/src/api_helpers.rs
@@ -6,6 +6,9 @@ use diesel::prelude::*;
use errors::*;
use regex::Regex;
use uuid::Uuid;
+use std::str::FromStr;
+
+pub type DbConn = diesel::r2d2::PooledConnection<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
/// This function should always be run within a transaction
pub fn get_or_create_editgroup(editor_id: Uuid, conn: &PgConnection) -> Result<Uuid> {
@@ -34,10 +37,7 @@ pub fn accept_editgroup(editgroup_id: Uuid, conn: &PgConnection) -> Result<Chang
.count()
.get_result(conn)?;
if count > 0 {
- bail!(
- "editgroup {} has already been accepted",
- editgroup_id.to_string()
- );
+ return Err(ErrorKind::EditgroupAlreadyAccepted(uuid2fcid(&editgroup_id)).into());
}
// for each entity type...
@@ -90,6 +90,30 @@ pub fn accept_editgroup(editgroup_id: Uuid, conn: &PgConnection) -> Result<Chang
Ok(entry)
}
+pub struct FatCatId(Uuid);
+
+impl ToString for FatCatId {
+ fn to_string(&self) -> String {
+ uuid2fcid(&self.to_uuid())
+ }
+}
+
+impl FromStr for FatCatId {
+ type Err = Error;
+ fn from_str(s: &str) -> Result<FatCatId> {
+ fcid2uuid(s).map(|u| FatCatId(u))
+ }
+}
+
+impl FatCatId {
+ pub fn to_uuid(&self) -> Uuid {
+ self.0
+ }
+ pub fn from_uuid(u: &Uuid) -> FatCatId {
+ FatCatId(u.clone())
+ }
+}
+
/// Convert fatcat IDs (base32 strings) to UUID
pub fn fcid2uuid(fcid: &str) -> Result<Uuid> {
if fcid.len() != 26 {
diff --git a/rust/src/api_server.rs b/rust/src/api_server.rs
index 88fd7063..602fbfb7 100644
--- a/rust/src/api_server.rs
+++ b/rust/src/api_server.rs
@@ -17,11 +17,11 @@ use fatcat_api::models::*;
use sha1::Sha1;
use uuid::Uuid;
use ConnectionPool;
-
-type DbConn = diesel::r2d2::PooledConnection<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
+use database_entity_crud::{EntityCrud, EditContext};
+use std::str::FromStr;
macro_rules! entity_batch_handler {
- ($post_handler:ident, $post_batch_handler:ident, $model:ident) => {
+ ($post_batch_handler:ident, $model:ident) => {
pub fn $post_batch_handler(
&self,
entity_list: &[models::$model],
@@ -29,8 +29,6 @@ macro_rules! entity_batch_handler {
editgroup: Option<String>,
conn: &DbConn,
) -> Result<Vec<EntityEdit>> {
- let mut ret: Vec<EntityEdit> = vec![];
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
// editgroup override logic based on parameters
let eg_id: Option<Uuid> = match (editgroup, autoaccept) {
(Some(eg_string), _) => Some(fcid2uuid(&eg_string)?),
@@ -51,43 +49,17 @@ macro_rules! entity_batch_handler {
// actual wrapped function call here
ret.push(self.$post_handler(e, autoaccept, conn)?);
}
+ let mut edit_context = make_edit_context(conn, eg_id)?;
+ edit_context.autoaccept = autoaccept;
+ let model_list: Vec<&models::$model> = entity_list.iter().map(|e| e).collect();
+ let edits = $model::db_create_batch(conn, &edit_context, model_list.as_slice())?;
if autoaccept {
// if autoaccept, eg_id is always Some
let _clr: ChangelogRow = diesel::insert_into(changelog::table)
.values((changelog::editgroup_id.eq(eg_id.unwrap()),))
.get_result(conn)?;
}
- Ok(ret)
- }
- }
-}
-
-macro_rules! entity_history_handler {
- ($history_handler:ident, $edit_row_type:ident, $edit_table:ident) => {
- pub fn $history_handler(
- &self,
- id: &Uuid,
- limit: Option<i64>,
- conn: &DbConn,
- ) -> Result<Vec<EntityHistoryEntry>> {
- let limit = limit.unwrap_or(50);
-
- let rows: Vec<(EditgroupRow, ChangelogRow, $edit_row_type)> = editgroup::table
- .inner_join(changelog::table)
- .inner_join($edit_table::table)
- .filter($edit_table::ident_id.eq(id))
- .order(changelog::id.desc())
- .limit(limit)
- .get_results(conn)?;
-
- let history: Vec<EntityHistoryEntry> = rows.into_iter()
- .map(|(eg_row, cl_row, e_row)| EntityHistoryEntry {
- edit: e_row.into_model().expect("edit row to model"),
- editgroup: eg_row.into_model_partial(),
- changelog_entry: cl_row.into_model(),
- })
- .collect();
- Ok(history)
+ edits.into_iter().map(|e| e.into_model()).collect()
}
}
}
@@ -103,227 +75,23 @@ macro_rules! count_entity {
}};
}
-#[derive(Clone)]
-pub struct Server {
- pub db_pool: ConnectionPool,
-}
-
-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(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
- Ok(ContainerEntity {
- issnl: rev.issnl,
- wikidata_qid: rev.wikidata_qid,
- publisher: rev.publisher,
- name: rev.name,
- abbrev: rev.abbrev,
- coden: rev.coden,
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- 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(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
- Ok(CreatorEntity {
- display_name: rev.display_name,
- given_name: rev.given_name,
- surname: rev.surname,
- orcid: rev.orcid,
- wikidata_qid: rev.wikidata_qid,
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- 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(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
+fn make_edit_context(conn: &DbConn, editgroup_id: Option<FatCatId>) -> Result<EditContext> {
+ let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
+ let editgroup_id = match editgroup_id {
+ None => FatCatId::from_uuid(&get_or_create_editgroup(editor_id, conn)?),
+ Some(param) => param,
};
-
- let releases: Vec<String> = file_release::table
- .filter(file_release::file_rev.eq(rev.id))
- .get_results(conn)?
- .into_iter()
- .map(|r: FileReleaseRow| uuid2fcid(&r.target_release_ident_id))
- .collect();
-
- let urls: Vec<FileEntityUrls> = file_rev_url::table
- .filter(file_rev_url::file_rev.eq(rev.id))
- .get_results(conn)?
- .into_iter()
- .map(|r: FileRevUrlRow| FileEntityUrls {
- rel: r.rel,
- url: r.url,
- })
- .collect();
-
- Ok(FileEntity {
- sha1: rev.sha1,
- sha256: rev.sha256,
- md5: rev.md5,
- size: rev.size.map(|v| v as i64),
- urls: Some(urls),
- mimetype: rev.mimetype,
- releases: Some(releases),
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- redirect: redirect_id,
- editgroup_id: None,
- extra: rev.extra_json,
+ Ok(EditContext {
+ editor_id: FatCatId::from_uuid(&editor_id),
+ editgroup_id: editgroup_id,
+ extra_json: None,
+ autoapprove: false,
})
}
-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(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
-
- let refs: Vec<ReleaseRef> = release_ref::table
- .filter(release_ref::release_rev.eq(rev.id))
- .order(release_ref::index_val.asc())
- .get_results(conn)
- .expect("fetch release refs")
- .into_iter()
- .map(|r: ReleaseRefRow| ReleaseRef {
- index: r.index_val,
- key: r.key,
- extra: r.extra_json,
- container_title: r.container_title,
- year: r.year,
- title: r.title,
- locator: r.locator,
- target_release_id: r.target_release_ident_id.map(|v| uuid2fcid(&v)),
- })
- .collect();
-
- let contribs: Vec<ReleaseContrib> = release_contrib::table
- .filter(release_contrib::release_rev.eq(rev.id))
- .order((
- release_contrib::role.asc(),
- release_contrib::index_val.asc(),
- ))
- .get_results(conn)
- .expect("fetch release refs")
- .into_iter()
- .map(|c: ReleaseContribRow| ReleaseContrib {
- index: c.index_val,
- raw_name: c.raw_name,
- role: c.role,
- extra: c.extra_json,
- creator_id: c.creator_ident_id.map(|v| uuid2fcid(&v)),
- creator: None,
- })
- .collect();
-
- let abstracts: Vec<ReleaseEntityAbstracts> = release_rev_abstract::table
- .inner_join(abstracts::table)
- .filter(release_rev_abstract::release_rev.eq(rev.id))
- .get_results(conn)?
- .into_iter()
- .map(
- |r: (ReleaseRevAbstractRow, AbstractsRow)| ReleaseEntityAbstracts {
- sha1: Some(r.0.abstract_sha1),
- mimetype: r.0.mimetype,
- lang: r.0.lang,
- content: Some(r.1.content),
- },
- )
- .collect();
-
- Ok(ReleaseEntity {
- title: rev.title,
- release_type: rev.release_type,
- release_status: rev.release_status,
- release_date: rev.release_date
- .map(|v| chrono::DateTime::from_utc(v.and_hms(0, 0, 0), chrono::Utc)),
- doi: rev.doi,
- pmid: rev.pmid,
- pmcid: rev.pmcid,
- isbn13: rev.isbn13,
- core_id: rev.core_id,
- wikidata_qid: rev.wikidata_qid,
- volume: rev.volume,
- issue: rev.issue,
- pages: rev.pages,
- files: None,
- container: None,
- container_id: rev.container_ident_id.map(|u| uuid2fcid(&u)),
- publisher: rev.publisher,
- language: rev.language,
- work_id: Some(uuid2fcid(&rev.work_ident_id)),
- refs: Some(refs),
- contribs: Some(contribs),
- abstracts: Some(abstracts),
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- 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(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
- Ok(WorkEntity {
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- redirect: redirect_id,
- editgroup_id: None,
- extra: rev.extra_json,
- })
+#[derive(Clone)]
+pub struct Server {
+ pub db_pool: ConnectionPool,
}
impl Server {
@@ -333,13 +101,7 @@ impl Server {
_expand: Option<String>,
conn: &DbConn,
) -> Result<ContainerEntity> {
- // TODO: handle Deletions
- let (ident, rev): (ContainerIdentRow, ContainerRevRow) = container_ident::table
- .find(id)
- .inner_join(container_rev::table)
- .first(conn)?;
-
- container_row2entity(Some(ident), rev)
+ ContainerEntity::db_get(conn, FatCatId::from_uuid(id))
}
pub fn lookup_container_handler(&self, issnl: &str, conn: &DbConn) -> Result<ContainerEntity> {
@@ -354,7 +116,7 @@ impl Server {
.filter(container_ident::redirect_id.is_null())
.first(conn)?;
- container_row2entity(Some(ident), rev)
+ ContainerEntity::db_from_row(conn, rev, Some(ident))
}
pub fn get_creator_handler(
@@ -363,12 +125,8 @@ impl Server {
_expand: Option<String>,
conn: &DbConn,
) -> Result<CreatorEntity> {
- let (ident, rev): (CreatorIdentRow, CreatorRevRow) = creator_ident::table
- .find(id)
- .inner_join(creator_rev::table)
- .first(conn)?;
- creator_row2entity(Some(ident), rev)
+ CreatorEntity::db_get(conn, FatCatId::from_uuid(id))
}
pub fn lookup_creator_handler(&self, orcid: &str, conn: &DbConn) -> Result<CreatorEntity> {
@@ -383,7 +141,7 @@ impl Server {
.filter(creator_ident::redirect_id.is_null())
.first(conn)?;
- creator_row2entity(Some(ident), rev)
+ CreatorEntity::db_from_row(conn, rev, Some(ident))
}
pub fn get_creator_releases_handler(
@@ -402,8 +160,9 @@ impl Server {
.filter(release_ident::redirect_id.is_null())
.load(conn)?;
+ // TODO: from_rows, not from_row?
rows.into_iter()
- .map(|(rev, ident, _)| release_row2entity(Some(ident), rev, conn))
+ .map(|(rev, ident, _)| ReleaseEntity::db_from_row(conn, rev, Some(ident)))
.collect()
}
@@ -413,12 +172,7 @@ impl Server {
_expand: Option<String>,
conn: &DbConn,
) -> Result<FileEntity> {
- let (ident, rev): (FileIdentRow, FileRevRow) = file_ident::table
- .find(id)
- .inner_join(file_rev::table)
- .first(conn)?;
-
- file_row2entity(Some(ident), rev, conn)
+ FileEntity::db_get(conn, FatCatId::from_uuid(id))
}
pub fn lookup_file_handler(&self, sha1: &str, conn: &DbConn) -> Result<FileEntity> {
@@ -432,7 +186,7 @@ impl Server {
.filter(file_ident::redirect_id.is_null())
.first(conn)?;
- file_row2entity(Some(ident), rev, conn)
+ FileEntity::db_from_row(conn, rev, Some(ident))
}
pub fn get_release_handler(
@@ -441,12 +195,8 @@ impl Server {
expand: Option<String>,
conn: &DbConn,
) -> Result<ReleaseEntity> {
- let (ident, rev): (ReleaseIdentRow, ReleaseRevRow) = release_ident::table
- .find(id)
- .inner_join(release_rev::table)
- .first(conn)?;
- let mut release = release_row2entity(Some(ident), rev, conn)?;
+ let mut release = ReleaseEntity::db_get(conn, FatCatId::from_uuid(id))?;
// For now, if there is any expand param we do them all
if expand.is_some() {
@@ -457,7 +207,6 @@ impl Server {
Some(self.get_container_handler(&fcid2uuid(&cid)?, None, conn)?);
}
}
-
Ok(release)
}
@@ -473,22 +222,23 @@ impl Server {
.filter(release_ident::redirect_id.is_null())
.first(conn)?;
- release_row2entity(Some(ident), rev, conn)
+ ReleaseEntity::db_from_row(conn, rev, Some(ident))
}
pub fn get_release_files_handler(&self, id: &str, conn: &DbConn) -> Result<Vec<FileEntity>> {
- let id = fcid2uuid(&id)?;
+
+ let ident = FatCatId::from_str(id)?;
let rows: Vec<(FileRevRow, FileIdentRow, FileReleaseRow)> = file_rev::table
.inner_join(file_ident::table)
.inner_join(file_release::table)
- .filter(file_release::target_release_ident_id.eq(&id))
+ .filter(file_release::target_release_ident_id.eq(&ident.to_uuid()))
.filter(file_ident::is_live.eq(true))
.filter(file_ident::redirect_id.is_null())
.load(conn)?;
rows.into_iter()
- .map(|(rev, ident, _)| file_row2entity(Some(ident), rev, conn))
+ .map(|(rev, ident, _)| FileEntity::db_from_row(conn, rev, Some(ident)))
.collect()
}
@@ -498,12 +248,7 @@ impl Server {
_expand: Option<String>,
conn: &DbConn,
) -> Result<WorkEntity> {
- let (ident, rev): (WorkIdentRow, WorkRevRow) = work_ident::table
- .find(id)
- .inner_join(work_rev::table)
- .first(conn)?;
-
- work_row2entity(Some(ident), rev)
+ WorkEntity::db_get(conn, FatCatId::from_uuid(id))
}
pub fn get_work_releases_handler(&self, id: &str, conn: &DbConn) -> Result<Vec<ReleaseEntity>> {
@@ -517,379 +262,139 @@ impl Server {
.load(conn)?;
rows.into_iter()
- .map(|(rev, ident)| release_row2entity(Some(ident), rev, conn))
+ .map(|(rev, ident)| ReleaseEntity::db_from_row(conn, rev, Some(ident)))
.collect()
}
pub fn create_container_handler(
&self,
entity: models::ContainerEntity,
- autoaccept: bool,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id: Uuid = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn)?,
- Some(param) => fcid2uuid(&param)?,
- };
- if let Some(ref extid) = entity.wikidata_qid {
- check_wikidata_qid(extid)?;
- }
- if let Some(ref extid) = entity.issnl {
- check_issn(extid)?;
- }
-
- let edit: ContainerEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO container_rev (name, publisher, issnl, wikidata_qid, abbrev, coden, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6, $7)
- RETURNING id ),
- ident AS ( INSERT INTO container_ident (is_live, rev_id)
- VALUES ($8, (SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO container_edit (editgroup_id, ident_id, rev_id) VALUES
- ($9, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Text, _>(entity.name)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.publisher)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.issnl)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.abbrev)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.coden)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Bool, _>(autoaccept)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
+ edit.into_model()
+ }
+ pub fn update_container_handler(
+ &self,
+ id: &Uuid,
+ entity: models::ContainerEntity,
+ conn: &DbConn,
+ ) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
+ }
+ pub fn delete_container_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = ContainerEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
edit.into_model()
}
pub fn create_creator_handler(
&self,
entity: models::CreatorEntity,
- autoaccept: bool,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn).expect("current editgroup"),
- Some(param) => fcid2uuid(&param)?,
- };
- if let Some(ref extid) = entity.orcid {
- check_orcid(extid)?;
- }
- if let Some(ref extid) = entity.wikidata_qid {
- check_wikidata_qid(extid)?;
- }
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
+ edit.into_model()
- let edit: CreatorEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO creator_rev (display_name, given_name, surname, orcid, wikidata_qid, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6)
- RETURNING id ),
- ident AS ( INSERT INTO creator_ident (is_live, rev_id)
- VALUES ($7, (SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO creator_edit (editgroup_id, ident_id, rev_id) VALUES
- ($8, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Text, _>(entity.display_name)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.given_name)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.surname)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.orcid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Bool, _>(autoaccept)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
+ }
+ pub fn update_creator_handler(
+ &self,
+ id: &Uuid,
+ entity: models::CreatorEntity,
+ conn: &DbConn,
+ ) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
+ }
+ pub fn delete_creator_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = CreatorEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
edit.into_model()
}
pub fn create_file_handler(
&self,
entity: models::FileEntity,
- autoaccept: bool,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn).expect("current editgroup"),
- Some(param) => fcid2uuid(&param)?,
- };
-
- let edit: FileEditRow =
- diesel::sql_query(
- "WITH rev AS ( INSERT INTO file_rev (size, sha1, sha256, md5, mimetype, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6)
- RETURNING id ),
- ident AS ( INSERT INTO file_ident (is_live, rev_id)
- VALUES ($7, (SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO file_edit (editgroup_id, ident_id, rev_id) VALUES
- ($8, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Nullable<diesel::sql_types::Int8>, _>(entity.size)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.sha1)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.sha256)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.md5)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.mimetype)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Bool, _>(autoaccept)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
-
- let _releases: Option<Vec<FileReleaseRow>> = match entity.releases {
- None => None,
- Some(release_list) => {
- if release_list.is_empty() {
- Some(vec![])
- } else {
- let release_rows: Vec<FileReleaseRow> = release_list
- .iter()
- .map(|r| FileReleaseRow {
- file_rev: edit.rev_id.unwrap(),
- target_release_ident_id: fcid2uuid(r)
- .expect("invalid fatcat identifier"),
- })
- .collect();
- let release_rows: Vec<FileReleaseRow> = insert_into(file_release::table)
- .values(release_rows)
- .get_results(conn)
- .expect("error inserting file_releases");
- Some(release_rows)
- }
- }
- };
-
- let _urls: Option<Vec<FileRevUrlRow>> = match entity.urls {
- None => None,
- Some(url_list) => {
- if url_list.is_empty() {
- Some(vec![])
- } else {
- let url_rows: Vec<FileRevUrlNewRow> = url_list
- .into_iter()
- .map(|u| FileRevUrlNewRow {
- file_rev: edit.rev_id.unwrap(),
- rel: u.rel,
- url: u.url,
- })
- .collect();
- let url_rows: Vec<FileRevUrlRow> = insert_into(file_rev_url::table)
- .values(url_rows)
- .get_results(conn)
- .expect("error inserting file_rev_url");
- Some(url_rows)
- }
- }
- };
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
+ edit.into_model()
+ }
+ pub fn update_file_handler(
+ &self,
+ id: &Uuid,
+ entity: models::FileEntity,
+ conn: &DbConn,
+ ) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
+ }
+ pub fn delete_file_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = FileEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
edit.into_model()
}
pub fn create_release_handler(
&self,
entity: models::ReleaseEntity,
- autoaccept: bool,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn).expect("current editgroup"),
- Some(param) => fcid2uuid(&param)?,
- };
- if let Some(ref extid) = entity.doi {
- check_doi(extid)?;
- }
- if let Some(ref extid) = entity.pmid {
- check_pmid(extid)?;
- }
- if let Some(ref extid) = entity.pmcid {
- check_pmcid(extid)?;
- }
- if let Some(ref extid) = entity.wikidata_qid {
- check_wikidata_qid(extid)?;
- }
-
- let work_id = match entity.work_id {
- Some(work_id) => fcid2uuid(&work_id)?,
- None => {
- // If a work_id wasn't passed, create a new work under the current editgroup
- let work_model = models::WorkEntity {
- ident: None,
- revision: None,
- redirect: None,
- state: None,
- editgroup_id: Some(uuid2fcid(&editgroup_id)),
- extra: None,
- };
- let new_entity = self.create_work_handler(work_model, autoaccept, conn)?;
- fcid2uuid(&new_entity.ident)?
- }
- };
-
- let container_id: Option<Uuid> = match entity.container_id {
- Some(id) => Some(fcid2uuid(&id)?),
- None => None,
- };
-
- let edit: ReleaseEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO release_rev (title, release_type, release_status, release_date, doi, pmid, pmcid, wikidata_qid, isbn13, core_id, volume, issue, pages, work_ident_id, container_ident_id, publisher, language, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
- RETURNING id ),
- ident AS ( INSERT INTO release_ident (is_live, rev_id)
- VALUES ($19, (SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO release_edit (editgroup_id, ident_id, rev_id) VALUES
- ($20, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Text, _>(entity.title)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.release_type)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.release_status)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Date>, _>(
- entity.release_date.map(|v| v.naive_utc().date()))
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.doi)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pmid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pmcid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.isbn13)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.core_id)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.volume)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.issue)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pages)
- .bind::<diesel::sql_types::Uuid, _>(work_id)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Uuid>, _>(container_id)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.publisher)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.language)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Bool, _>(autoaccept)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
-
- let _refs: Option<Vec<ReleaseRefRow>> = match entity.refs {
- None => None,
- Some(ref_list) => {
- if ref_list.is_empty() {
- Some(vec![])
- } else {
- let ref_rows: Vec<ReleaseRefNewRow> = ref_list
- .iter()
- .map(|r| ReleaseRefNewRow {
- release_rev: edit.rev_id.unwrap(),
- target_release_ident_id: r.target_release_id
- .clone()
- .map(|v| fcid2uuid(&v).expect("valid fatcat identifier")),
- index_val: r.index,
- key: r.key.clone(),
- container_title: r.container_title.clone(),
- year: r.year,
- title: r.title.clone(),
- locator: r.locator.clone(),
- extra_json: r.extra.clone(),
- })
- .collect();
- let ref_rows: Vec<ReleaseRefRow> = insert_into(release_ref::table)
- .values(ref_rows)
- .get_results(conn)
- .expect("error inserting release_refs");
- Some(ref_rows)
- }
- }
- };
-
- let _contribs: Option<Vec<ReleaseContribRow>> = match entity.contribs {
- None => None,
- Some(contrib_list) => {
- if contrib_list.is_empty() {
- Some(vec![])
- } else {
- let contrib_rows: Vec<ReleaseContribNewRow> = contrib_list
- .iter()
- .map(|c| ReleaseContribNewRow {
- release_rev: edit.rev_id.unwrap(),
- creator_ident_id: c.creator_id
- .clone()
- .map(|v| fcid2uuid(&v).expect("valid fatcat identifier")),
- raw_name: c.raw_name.clone(),
- index_val: c.index,
- role: c.role.clone(),
- extra_json: c.extra.clone(),
- })
- .collect();
- let contrib_rows: Vec<ReleaseContribRow> = insert_into(release_contrib::table)
- .values(contrib_rows)
- .get_results(conn)
- .expect("error inserting release_contribs");
- Some(contrib_rows)
- }
- }
- };
-
- if let Some(abstract_list) = entity.abstracts {
- // For rows that specify content, we need to insert the abstract if it doesn't exist
- // already
- let new_abstracts: Vec<AbstractsRow> = abstract_list
- .iter()
- .filter(|ea| ea.content.is_some())
- .map(|c| AbstractsRow {
- sha1: Sha1::from(c.content.clone().unwrap()).hexdigest(),
- content: c.content.clone().unwrap(),
- })
- .collect();
- if !new_abstracts.is_empty() {
- // Sort of an "upsert"; only inserts new abstract rows if they don't already exist
- insert_into(abstracts::table)
- .values(&new_abstracts)
- .on_conflict(abstracts::sha1)
- .do_nothing()
- .execute(conn)?;
- }
- let release_abstract_rows: Vec<ReleaseRevAbstractNewRow> = abstract_list
- .into_iter()
- .map(|c| ReleaseRevAbstractNewRow {
- release_rev: edit.rev_id.unwrap(),
- abstract_sha1: match c.content {
- Some(ref content) => Sha1::from(content).hexdigest(),
- None => c.sha1.expect("either abstract_sha1 or content is required"),
- },
- lang: c.lang,
- mimetype: c.mimetype,
- })
- .collect();
- insert_into(release_rev_abstract::table)
- .values(release_abstract_rows)
- .execute(conn)?;
- }
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
+ edit.into_model()
+ }
+ pub fn update_release_handler(
+ &self,
+ id: &Uuid,
+ entity: models::ReleaseEntity,
+ conn: &DbConn,
+ ) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
+ }
+ pub fn delete_release_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = ReleaseEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
edit.into_model()
}
pub fn create_work_handler(
&self,
entity: models::WorkEntity,
- autoaccept: bool,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn).expect("current editgroup"),
- Some(param) => fcid2uuid(&param)?,
- };
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
+ edit.into_model()
+ }
+
+ pub fn update_work_handler(
+ &self,
+ id: &Uuid,
+ entity: models::WorkEntity,
+ conn: &DbConn,
+ ) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
+ }
- let edit: WorkEditRow =
- diesel::sql_query(
- "WITH rev AS ( INSERT INTO work_rev (extra_json)
- VALUES ($1)
- RETURNING id ),
- ident AS ( INSERT INTO work_ident (is_live, rev_id)
- VALUES ($2, (SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO work_edit (editgroup_id, ident_id, rev_id) VALUES
- ($3, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Bool, _>(autoaccept)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
+ pub fn delete_work_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = WorkEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
edit.into_model()
}
@@ -910,8 +415,7 @@ impl Server {
editgroup::description.eq(entity.description),
editgroup::extra_json.eq(entity.extra),
))
- .get_result(conn)
- .expect("error creating edit group");
+ .get_result(conn)?;
Ok(Editgroup {
id: Some(uuid2fcid(&row.id)),
@@ -1124,30 +628,33 @@ impl Server {
}
entity_batch_handler!(
- create_container_handler,
create_container_batch_handler,
ContainerEntity
);
entity_batch_handler!(
- create_creator_handler,
create_creator_batch_handler,
CreatorEntity
);
- entity_batch_handler!(create_file_handler, create_file_batch_handler, FileEntity);
+ entity_batch_handler!(create_file_batch_handler, FileEntity);
entity_batch_handler!(
- create_release_handler,
create_release_batch_handler,
ReleaseEntity
);
- entity_batch_handler!(create_work_handler, create_work_batch_handler, WorkEntity);
+ entity_batch_handler!(create_work_batch_handler, WorkEntity);
- entity_history_handler!(
- get_container_history_handler,
- ContainerEditRow,
- container_edit
- );
- entity_history_handler!(get_creator_history_handler, CreatorEditRow, creator_edit);
- entity_history_handler!(get_file_history_handler, FileEditRow, file_edit);
- entity_history_handler!(get_release_history_handler, ReleaseEditRow, release_edit);
- entity_history_handler!(get_work_history_handler, WorkEditRow, work_edit);
+ pub fn get_container_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ ContainerEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
+ pub fn get_creator_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ CreatorEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
+ pub fn get_file_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ FileEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
+ pub fn get_release_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ ReleaseEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
+ pub fn get_work_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ WorkEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
}
diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs
index da6139d2..6272814e 100644
--- a/rust/src/api_wrappers.rs
+++ b/rust/src/api_wrappers.rs
@@ -19,8 +19,9 @@ macro_rules! wrap_entity_handlers {
// The only stable approach I know of would be: https://github.com/dtolnay/mashup
($get_fn:ident, $get_handler:ident, $get_resp:ident, $post_fn:ident, $post_handler:ident,
$post_resp:ident, $post_batch_fn:ident, $post_batch_handler:ident,
- $post_batch_resp:ident, $get_history_fn:ident, $get_history_handler:ident,
- $get_history_resp:ident, $model:ident) => {
+ $post_batch_resp:ident, $update_fn:ident, $update_handler:ident, $update_resp:ident,
+ $delete_fn:ident, $delete_handler:ident, $delete_resp:ident, $get_history_fn:ident,
+ $get_history_handler:ident, $get_history_resp:ident, $model:ident) => {
fn $get_fn(
&self,
@@ -108,6 +109,79 @@ macro_rules! wrap_entity_handlers {
Box::new(futures::done(Ok(ret)))
}
+ fn $update_fn(
+ &self,
+ id: String,
+ entity: models::$model,
+ _context: &Context,
+ ) -> Box<Future<Item = $update_resp, Error = ApiError> + Send> {
+ let id = if let Ok(parsed) = fcid2uuid(&id) { parsed } else {
+ return Box::new(futures::done(Ok($update_resp::BadRequest(ErrorResponse {
+ message: ErrorKind::InvalidFatcatId(id).to_string() }))));
+ };
+ let conn = self.db_pool.get().expect("db_pool error");
+ let ret = match conn.transaction(|| self.$update_handler(&id, entity, &conn)) {
+ Ok(edit) =>
+ $update_resp::UpdatedEntity(edit),
+ Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) =>
+ $update_resp::NotFound(ErrorResponse { message: format!("No such entity {}: {}", stringify!($model), id) }),
+ Err(Error(ErrorKind::Diesel(e), _)) =>
+ $update_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::Uuid(e), _)) =>
+ $update_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InvalidFatcatId(e), _)) =>
+ $update_resp::BadRequest(ErrorResponse {
+ message: ErrorKind::InvalidFatcatId(e).to_string() }),
+ Err(Error(ErrorKind::MalformedExternalId(e), _)) =>
+ $update_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(e) => {
+ error!("{}", e);
+ $update_resp::GenericError(ErrorResponse { message: e.to_string() })
+ },
+ };
+ Box::new(futures::done(Ok(ret)))
+ }
+
+ fn $delete_fn(
+ &self,
+ id: String,
+ editgroup_id: Option<String>,
+ _context: &Context,
+ ) -> Box<Future<Item = $delete_resp, Error = ApiError> + Send> {
+ let id = if let Ok(parsed) = fcid2uuid(&id) { parsed } else {
+ return Box::new(futures::done(Ok($delete_resp::BadRequest(ErrorResponse {
+ message: ErrorKind::InvalidFatcatId(id).to_string() }))));
+ };
+ let editgroup_id = match editgroup_id {
+ Some(raw) => if let Ok(parsed) = fcid2uuid(&raw) { Some(parsed) } else {
+ return Box::new(futures::done(Ok($delete_resp::BadRequest(ErrorResponse {
+ message: ErrorKind::InvalidFatcatId(raw).to_string() }))))
+ }
+ None => None
+ };
+ let conn = self.db_pool.get().expect("db_pool error");
+ let ret = match conn.transaction(|| self.$delete_handler(&id, editgroup_id, &conn)) {
+ Ok(edit) =>
+ $delete_resp::DeletedEntity(edit),
+ Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) =>
+ $delete_resp::NotFound(ErrorResponse { message: format!("No such entity {}: {}", stringify!($model), id) }),
+ Err(Error(ErrorKind::Diesel(e), _)) =>
+ $delete_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::Uuid(e), _)) =>
+ $delete_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InvalidFatcatId(e), _)) =>
+ $delete_resp::BadRequest(ErrorResponse {
+ message: ErrorKind::InvalidFatcatId(e).to_string() }),
+ Err(Error(ErrorKind::MalformedExternalId(e), _)) =>
+ $delete_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(e) => {
+ error!("{}", e);
+ $delete_resp::GenericError(ErrorResponse { message: e.to_string() })
+ },
+ };
+ Box::new(futures::done(Ok(ret)))
+ }
+
fn $get_history_fn(
&self,
id: String,
@@ -177,6 +251,12 @@ impl Api for Server {
create_container_batch,
create_container_batch_handler,
CreateContainerBatchResponse,
+ update_container,
+ update_container_handler,
+ UpdateContainerResponse,
+ delete_container,
+ delete_container_handler,
+ DeleteContainerResponse,
get_container_history,
get_container_history_handler,
GetContainerHistoryResponse,
@@ -193,6 +273,12 @@ impl Api for Server {
create_creator_batch,
create_creator_batch_handler,
CreateCreatorBatchResponse,
+ update_creator,
+ update_creator_handler,
+ UpdateCreatorResponse,
+ delete_creator,
+ delete_creator_handler,
+ DeleteCreatorResponse,
get_creator_history,
get_creator_history_handler,
GetCreatorHistoryResponse,
@@ -208,6 +294,12 @@ impl Api for Server {
create_file_batch,
create_file_batch_handler,
CreateFileBatchResponse,
+ update_file,
+ update_file_handler,
+ UpdateFileResponse,
+ delete_file,
+ delete_file_handler,
+ DeleteFileResponse,
get_file_history,
get_file_history_handler,
GetFileHistoryResponse,
@@ -223,6 +315,12 @@ impl Api for Server {
create_release_batch,
create_release_batch_handler,
CreateReleaseBatchResponse,
+ update_release,
+ update_release_handler,
+ UpdateReleaseResponse,
+ delete_release,
+ delete_release_handler,
+ DeleteReleaseResponse,
get_release_history,
get_release_history_handler,
GetReleaseHistoryResponse,
@@ -238,6 +336,12 @@ impl Api for Server {
create_work_batch,
create_work_batch_handler,
CreateWorkBatchResponse,
+ update_work,
+ update_work_handler,
+ UpdateWorkResponse,
+ delete_work,
+ delete_work_handler,
+ DeleteWorkResponse,
get_work_history,
get_work_history_handler,
GetWorkHistoryResponse,
@@ -310,6 +414,11 @@ impl Api for Server {
message: format!("No such editgroup: {}", id),
})
}
+ Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => {
+ AcceptEditgroupResponse::BadRequest(ErrorResponse {
+ message: ErrorKind::EditgroupAlreadyAccepted(e).to_string(),
+ })
+ }
Err(e) => AcceptEditgroupResponse::GenericError(ErrorResponse {
message: e.to_string(),
}),
diff --git a/rust/src/database_entity_crud.rs b/rust/src/database_entity_crud.rs
new file mode 100644
index 00000000..0f5c5f9d
--- /dev/null
+++ b/rust/src/database_entity_crud.rs
@@ -0,0 +1,900 @@
+
+use sha1::Sha1;
+use chrono;
+use diesel::prelude::*;
+use diesel::{self, insert_into};
+use database_schema::*;
+use database_models::*;
+use errors::*;
+use fatcat_api::models::*;
+use api_helpers::*;
+use uuid::Uuid;
+use std::marker::Sized;
+use std::str::FromStr;
+use serde_json;
+
+pub struct EditContext {
+ pub editor_id: FatCatId,
+ pub editgroup_id: FatCatId,
+ pub extra_json: Option<serde_json::Value>,
+ pub autoapprove: bool,
+}
+
+/* One goal here is to abstract the non-entity-specific bits into generic traits or functions,
+ * instead of macros.
+ *
+ * Notably:
+ *
+ * db_get
+ * db_get_rev
+ * db_create
+ * db_create_batch
+ * db_update
+ * db_delete
+ * db_get_history
+ *
+ * For now, these will probably be macros, until we can level up our trait/generics foo.
+ */
+
+// Associated Type, not parametric
+pub trait EntityCrud where Self: Sized {
+ // TODO: could these be generic structs? Or do they need to be bound to a specific table?
+ type EditRow; // EntityEditRow
+ type EditNewRow;
+ type IdentRow; // EntityIdentRow
+ type IdentNewRow;
+ type RevRow;
+
+ fn parse_editgroup_id(&self) -> Result<Option<FatCatId>>;
+
+ // Generic Methods
+ fn db_get(conn: &DbConn, ident: FatCatId) -> Result<Self>;
+ fn db_get_rev(conn: &DbConn, rev_id: Uuid) -> Result<Self>;
+ fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow>;
+ fn db_create_batch(conn: &DbConn, edit_context: &EditContext, models: &[&Self]) -> Result<Vec<Self::EditRow>>;
+ fn db_update(&self, conn: &DbConn, edit_context: &EditContext, ident: FatCatId) -> Result<Self::EditRow>;
+ fn db_delete(conn: &DbConn, edit_context: &EditContext, ident: FatCatId) -> Result<Self::EditRow>;
+ fn db_get_history(conn: &DbConn, ident: FatCatId, limit: Option<i64>) -> Result<Vec<EntityHistoryEntry>>;
+
+ // Entity-specific Methods
+ fn db_from_row(conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self>;
+ fn db_insert_rev(&self, conn: &DbConn) -> Result<Uuid>;
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>>;
+}
+
+// TODO: this could be a separate trait on all entities?
+macro_rules! generic_parse_editgroup_id{
+ () => {
+ fn parse_editgroup_id(&self) -> Result<Option<FatCatId>> {
+ match &self.editgroup_id {
+ Some(s) => Ok(Some(FatCatId::from_str(&s)?)),
+ None => Ok(None),
+ }
+ }
+ }
+}
+
+macro_rules! generic_db_get {
+ ($ident_table: ident, $rev_table: ident) => {
+ fn db_get(conn: &DbConn, ident: FatCatId) -> Result<Self> {
+ let (ident, rev): (Self::IdentRow, Self::RevRow) = $ident_table::table
+ .find(ident.to_uuid())
+ .inner_join($rev_table::table)
+ .first(conn)?;
+
+ Self::db_from_row(conn, rev, Some(ident))
+ }
+ }
+}
+
+macro_rules! generic_db_get_rev {
+ ($rev_table: ident) => {
+ fn db_get_rev(conn: &DbConn, rev_id: Uuid) -> Result<Self> {
+ let rev = $rev_table::table
+ .find(rev_id)
+ .first(conn)?;
+
+ Self::db_from_row(conn, rev, None)
+ }
+ }
+}
+
+macro_rules! generic_db_create {
+ // TODO: this path should call generic_db_create_batch
+ ($ident_table: ident, $edit_table: ident) => {
+ fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow> {
+ let rev_id = self.db_insert_rev(conn)?;
+ let ident: Uuid = insert_into($ident_table::table)
+ .values($ident_table::rev_id.eq(&rev_id))
+ .returning($ident_table::id)
+ .get_result(conn)?;
+ let edit: Self::EditRow = insert_into($edit_table::table)
+ .values((
+ $edit_table::editgroup_id.eq(edit_context.editgroup_id.to_uuid()),
+ $edit_table::rev_id.eq(&rev_id),
+ $edit_table::ident_id.eq(&ident),
+ ))
+ .get_result(conn)?;
+ Ok(edit)
+ }
+ }
+}
+
+macro_rules! generic_db_create_batch {
+ ($ident_table: ident, $edit_table: ident) => {
+ fn db_create_batch(conn: &DbConn, edit_context: &EditContext, models: &[&Self]) -> Result<Vec<Self::EditRow>> {
+ let rev_ids: Vec<Uuid> = Self::db_insert_revs(conn, models)?;
+ let ident_ids: Vec<Uuid> = insert_into($ident_table::table)
+ .values(rev_ids.iter()
+ .map(|rev_id| Self::IdentNewRow {
+ rev_id: Some(rev_id.clone()),
+ is_live: edit_context.autoapprove,
+ redirect_id: None,
+ })
+ .collect::<Vec<Self::IdentNewRow>>())
+ .returning($ident_table::id)
+ .get_results(conn)?;
+ let edits: Vec<Self::EditRow> = insert_into($edit_table::table)
+ .values(rev_ids.into_iter().zip(ident_ids.into_iter())
+ .map(|(rev_id, ident_id)| Self::EditNewRow {
+ editgroup_id: edit_context.editgroup_id.to_uuid(),
+ rev_id: Some(rev_id),
+ ident_id: ident_id,
+ redirect_id: None,
+ prev_rev: None,
+ extra_json: edit_context.extra_json.clone(),
+ })
+ .collect::<Vec<Self::EditNewRow>>())
+ .get_results(conn)?;
+ Ok(edits)
+ }
+ }
+}
+
+macro_rules! generic_db_update {
+ ($ident_table: ident, $edit_table: ident) => {
+ fn db_update(&self, conn: &DbConn, edit_context: &EditContext, ident: FatCatId) -> Result<Self::EditRow> {
+ let current: Self::IdentRow = $ident_table::table.find(ident.to_uuid()).first(conn)?;
+ if current.is_live != true {
+ // TODO: what if isn't live? 4xx not 5xx
+ bail!("can't delete an entity that doesn't exist yet");
+ }
+ if current.rev_id.is_none() {
+ // TODO: what if it's already deleted? 4xx not 5xx
+ bail!("entity was already deleted");
+ }
+
+ let rev_id = self.db_insert_rev(conn)?;
+ let edit: Self::EditRow = insert_into($edit_table::table)
+ .values((
+ $edit_table::editgroup_id.eq(edit_context.editgroup_id.to_uuid()),
+ $edit_table::ident_id.eq(&ident.to_uuid()),
+ $edit_table::rev_id.eq(&rev_id),
+ $edit_table::prev_rev.eq(current.rev_id.unwrap()),
+ $edit_table::extra_json.eq(&self.extra),
+ ))
+ .get_result(conn)?;
+
+ Ok(edit)
+ }
+ }
+}
+
+macro_rules! generic_db_delete {
+ ($ident_table: ident, $edit_table:ident) => {
+ fn db_delete(conn: &DbConn, edit_context: &EditContext, ident: FatCatId) -> Result<Self::EditRow> {
+
+ let current: Self::IdentRow = $ident_table::table.find(ident.to_uuid()).first(conn)?;
+ if current.is_live != true {
+ // TODO: what if isn't live? 4xx not 5xx
+ bail!("can't delete an entity that doesn't exist yet");
+ }
+ if current.rev_id.is_none() {
+ // TODO: what if it's already deleted? 4xx not 5xx
+ bail!("entity was already deleted");
+ }
+ let edit: Self::EditRow = insert_into($edit_table::table)
+ .values((
+ $edit_table::editgroup_id.eq(edit_context.editgroup_id.to_uuid()),
+ $edit_table::ident_id.eq(ident.to_uuid()),
+ $edit_table::rev_id.eq(None::<Uuid>),
+ $edit_table::redirect_id.eq(None::<Uuid>),
+ $edit_table::prev_rev.eq(current.rev_id),
+ $edit_table::extra_json.eq(&edit_context.extra_json),
+ ))
+ .get_result(conn)?;
+
+ Ok(edit)
+ }
+ }
+}
+
+macro_rules! generic_db_get_history {
+ ($edit_table:ident) => {
+ fn db_get_history(conn: &DbConn, ident: FatCatId, limit: Option<i64>) -> Result<Vec<EntityHistoryEntry>> {
+ let limit = limit.unwrap_or(50); // TODO: make a static
+
+ let rows: Vec<(EditgroupRow, ChangelogRow, Self::EditRow)> = editgroup::table
+ .inner_join(changelog::table)
+ .inner_join($edit_table::table)
+ .filter($edit_table::ident_id.eq(ident.to_uuid()))
+ .order(changelog::id.desc())
+ .limit(limit)
+ .get_results(conn)?;
+
+ let history: Result<Vec<EntityHistoryEntry>> = rows.into_iter()
+ .map(|(eg_row, cl_row, e_row)| Ok(EntityHistoryEntry {
+ edit: e_row.into_model()?,
+ editgroup: eg_row.into_model_partial(),
+ changelog_entry: cl_row.into_model(),
+ }))
+ .collect();
+ history
+ }
+ }
+}
+
+macro_rules! generic_db_insert_rev {
+ () => {
+ fn db_insert_rev(&self, conn: &DbConn) -> Result<Uuid> {
+ Self::db_insert_revs(conn, &vec![self]).map(|id_list| id_list[0])
+ }
+ }
+}
+
+impl EntityCrud for ContainerEntity {
+ type EditRow = ContainerEditRow;
+ type EditNewRow = ContainerEditNewRow;
+ type IdentRow = ContainerIdentRow;
+ type IdentNewRow = ContainerIdentNewRow;
+ type RevRow = ContainerRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(container_ident, container_rev);
+ generic_db_get_rev!(container_rev);
+ generic_db_create!(container_ident, container_edit);
+ generic_db_create_batch!(container_ident, container_edit);
+ generic_db_update!(container_ident, container_edit);
+ generic_db_delete!(container_ident, container_edit);
+ generic_db_get_history!(container_edit);
+ generic_db_insert_rev!();
+
+ fn db_from_row(_conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+
+ Ok(ContainerEntity {
+ issnl: rev_row.issnl,
+ wikidata_qid: rev_row.wikidata_qid,
+ publisher: rev_row.publisher,
+ name: rev_row.name,
+ abbrev: rev_row.abbrev,
+ coden: rev_row.coden,
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ extra: rev_row.extra_json,
+ editgroup_id: None,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+
+ // first verify external identifier syntax
+ for entity in models {
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
+ if let Some(ref extid) = entity.issnl {
+ check_issn(extid)?;
+ }
+ }
+
+ let rev_ids: Vec<Uuid> = insert_into(container_rev::table)
+ .values(models.iter()
+ .map(|model| ContainerRevNewRow {
+ name: model.name.clone(),
+ publisher: model.publisher.clone(),
+ issnl: model.issnl.clone(),
+ wikidata_qid: model.wikidata_qid.clone(),
+ abbrev: model.abbrev.clone(),
+ coden: model.coden.clone(),
+ extra_json: model.extra.clone()
+ })
+ .collect::<Vec<ContainerRevNewRow>>())
+ .returning(container_rev::id)
+ .get_results(conn)?;
+ Ok(rev_ids)
+ }
+}
+
+impl EntityCrud for CreatorEntity {
+ type EditRow = CreatorEditRow;
+ type EditNewRow = CreatorEditNewRow;
+ type IdentRow = CreatorIdentRow;
+ type IdentNewRow = CreatorIdentNewRow;
+ type RevRow = CreatorRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(creator_ident, creator_rev);
+ generic_db_get_rev!(creator_rev);
+ generic_db_create!(creator_ident, creator_edit);
+ generic_db_create_batch!(creator_ident, creator_edit);
+ generic_db_update!(creator_ident, creator_edit);
+ generic_db_delete!(creator_ident, creator_edit);
+ generic_db_get_history!(creator_edit);
+ generic_db_insert_rev!();
+
+ fn db_from_row(_conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+ Ok(CreatorEntity {
+ display_name: rev_row.display_name,
+ given_name: rev_row.given_name,
+ surname: rev_row.surname,
+ orcid: rev_row.orcid,
+ wikidata_qid: rev_row.wikidata_qid,
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ editgroup_id: None,
+ extra: rev_row.extra_json,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+
+ // first verify external identifier syntax
+ for entity in models {
+ if let Some(ref extid) = entity.orcid {
+ check_orcid(extid)?;
+ }
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
+ }
+
+ let rev_ids: Vec<Uuid> = insert_into(creator_rev::table)
+ .values(models.iter()
+ .map(|model| CreatorRevNewRow {
+ display_name: model.display_name.clone(),
+ given_name: model.given_name.clone(),
+ surname: model.surname.clone(),
+ orcid: model.orcid.clone(),
+ wikidata_qid: model.wikidata_qid.clone(),
+ extra_json: model.extra.clone()
+ })
+ .collect::<Vec<CreatorRevNewRow>>())
+ .returning(creator_rev::id)
+ .get_results(conn)?;
+ Ok(rev_ids)
+ }
+}
+
+impl EntityCrud for FileEntity {
+ type EditRow = FileEditRow;
+ type EditNewRow = FileEditNewRow;
+ type IdentRow = FileIdentRow;
+ type IdentNewRow = FileIdentNewRow;
+ type RevRow = FileRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(file_ident, file_rev);
+ generic_db_get_rev!(file_rev);
+ generic_db_create!(file_ident, file_edit);
+ generic_db_create_batch!(file_ident, file_edit);
+ generic_db_update!(file_ident, file_edit);
+ generic_db_delete!(file_ident, file_edit);
+ generic_db_get_history!(file_edit);
+ generic_db_insert_rev!();
+
+ fn db_from_row(conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+
+ let releases: Vec<FatCatId> = file_release::table
+ .filter(file_release::file_rev.eq(rev_row.id))
+ .get_results(conn)?
+ .into_iter()
+ .map(|r: FileReleaseRow| FatCatId::from_uuid(&r.target_release_ident_id))
+ .collect();
+
+ let urls: Vec<FileEntityUrls> = file_rev_url::table
+ .filter(file_rev_url::file_rev.eq(rev_row.id))
+ .get_results(conn)?
+ .into_iter()
+ .map(|r: FileRevUrlRow| FileEntityUrls {
+ rel: r.rel,
+ url: r.url,
+ })
+ .collect();
+
+ Ok(FileEntity {
+ sha1: rev_row.sha1,
+ sha256: rev_row.sha256,
+ md5: rev_row.md5,
+ size: rev_row.size.map(|v| v as i64),
+ urls: Some(urls),
+ mimetype: rev_row.mimetype,
+ releases: Some(releases.iter().map(|fcid| fcid.to_string()).collect()),
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ editgroup_id: None,
+ extra: rev_row.extra_json,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+
+ let rev_ids: Vec<Uuid> = insert_into(file_rev::table)
+ .values(models.iter()
+ .map(|model| FileRevNewRow {
+ size: model.size,
+ sha1: model.sha1.clone(),
+ sha256: model.sha256.clone(),
+ md5: model.md5.clone(),
+ mimetype: model.mimetype.clone(),
+ extra_json: model.extra.clone()
+ })
+ .collect::<Vec<FileRevNewRow>>())
+ .returning(file_rev::id)
+ .get_results(conn)?;
+
+ let mut file_release_rows: Vec<FileReleaseRow> = vec![];
+ let mut file_url_rows: Vec<FileRevUrlNewRow> = vec![];
+
+ for (model, rev_id) in models.iter().zip(rev_ids.iter()) {
+ match &model.releases {
+ None => (),
+ Some(release_list) => {
+ let these_release_rows: Result<Vec<FileReleaseRow>> = release_list
+ .iter()
+ .map(|r| Ok(FileReleaseRow {
+ file_rev: rev_id.clone(),
+ target_release_ident_id: FatCatId::from_str(r)?.to_uuid(),
+ }))
+ .collect();
+ file_release_rows.extend(these_release_rows?);
+ }
+ };
+
+ match &model.urls {
+ None => (),
+ Some(url_list) => {
+ let these_url_rows: Vec<FileRevUrlNewRow> = url_list
+ .into_iter()
+ .map(|u| FileRevUrlNewRow {
+ file_rev: rev_id.clone(),
+ rel: u.rel.clone(),
+ url: u.url.clone(),
+ })
+ .collect();
+ file_url_rows.extend(these_url_rows);
+ }
+ };
+ }
+
+ if !file_release_rows.is_empty() {
+ // TODO: shouldn't it be "file_rev_release"?
+ insert_into(file_release::table)
+ .values(file_release_rows)
+ .execute(conn)?;
+ }
+
+ if !file_url_rows.is_empty() {
+ insert_into(file_rev_url::table)
+ .values(file_url_rows)
+ .execute(conn)?;
+ }
+
+ Ok(rev_ids)
+ }
+}
+
+impl EntityCrud for ReleaseEntity {
+ type EditRow = ReleaseEditRow;
+ type EditNewRow = ReleaseEditNewRow;
+ type IdentRow = ReleaseIdentRow;
+ type IdentNewRow = ReleaseIdentNewRow;
+ type RevRow = ReleaseRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(release_ident, release_rev);
+ generic_db_get_rev!(release_rev);
+ //generic_db_create!(release_ident, release_edit);
+ //generic_db_create_batch!(release_ident, release_edit);
+ generic_db_update!(release_ident, release_edit);
+ generic_db_delete!(release_ident, release_edit);
+ generic_db_get_history!(release_edit);
+ generic_db_insert_rev!();
+
+ fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow> {
+ let mut edits = Self::db_create_batch(conn, edit_context, &vec![self])?;
+ // probably a more elegant way to destroy the vec and take first element
+ Ok(edits.pop().unwrap())
+ }
+
+ fn db_create_batch(conn: &DbConn, edit_context: &EditContext, models: &[&Self]) -> Result<Vec<Self::EditRow>> {
+ // This isn't the generic implementation because we need to create Work entities for each
+ // of the release entities passed (at least in the common case)
+
+ // Generate the set of new work entities to insert (usually one for each release, but some
+ // releases might be pointed to a work already)
+ let mut new_work_models: Vec<&WorkEntity> = vec![];
+ for entity in models {
+ if entity.work_id.is_none() {
+ new_work_models.push(&WorkEntity {
+ ident: None,
+ revision: None,
+ redirect: None,
+ state: None,
+ editgroup_id: None,
+ extra: None,
+ });
+ };
+ }
+
+ // create the works, then pluck the list of idents from the result
+ let new_work_edits = WorkEntity::db_create_batch(conn, edit_context, new_work_models.as_slice())?;
+ let mut new_work_ids: Vec<Uuid> = new_work_edits.iter().map(|edit| edit.ident_id).collect();
+
+ // Copy all the release models, and ensure that each has work_id set, using the new work
+ // idents. There should be one new work ident for each release missing one.
+ let models_with_work_ids: Vec<Self> = models.iter().map(|model| {
+ let mut model = (*model).clone();
+ if model.work_id.is_none() {
+ model.work_id = Some(FatCatId::from_uuid(&new_work_ids.pop().unwrap()).to_string())
+ }
+ model
+ }).collect();
+ let model_refs: Vec<&Self> = models_with_work_ids.iter().map(|s| s).collect();
+ let models = model_refs.as_slice();
+
+ // The rest here is copy/pasta from the generic (how to avoid copypasta?)
+ let rev_ids: Vec<Uuid> = Self::db_insert_revs(conn, models)?;
+ let ident_ids: Vec<Uuid> = insert_into(release_ident::table)
+ .values(rev_ids.iter()
+ .map(|rev_id| Self::IdentNewRow {
+ rev_id: Some(rev_id.clone()),
+ is_live: edit_context.autoapprove,
+ redirect_id: None,
+ })
+ .collect::<Vec<Self::IdentNewRow>>())
+ .returning(release_ident::id)
+ .get_results(conn)?;
+ let edits: Vec<Self::EditRow> = insert_into(release_edit::table)
+ .values(rev_ids.into_iter().zip(ident_ids.into_iter())
+ .map(|(rev_id, ident_id)| Self::EditNewRow {
+ editgroup_id: edit_context.editgroup_id.to_uuid(),
+ rev_id: Some(rev_id),
+ ident_id: ident_id,
+ redirect_id: None,
+ prev_rev: None,
+ extra_json: edit_context.extra_json.clone(),
+ })
+ .collect::<Vec<Self::EditNewRow>>())
+ .get_results(conn)?;
+ Ok(edits)
+ }
+
+ fn db_from_row(conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+
+ let refs: Vec<ReleaseRef> = release_ref::table
+ .filter(release_ref::release_rev.eq(rev_row.id))
+ .order(release_ref::index_val.asc())
+ .get_results(conn)?
+ .into_iter()
+ .map(|r: ReleaseRefRow| ReleaseRef {
+ index: r.index_val,
+ key: r.key,
+ extra: r.extra_json,
+ container_title: r.container_title,
+ year: r.year,
+ title: r.title,
+ locator: r.locator,
+ target_release_id: r.target_release_ident_id.map(|v| FatCatId::from_uuid(&v).to_string()),
+ })
+ .collect();
+
+ let contribs: Vec<ReleaseContrib> = release_contrib::table
+ .filter(release_contrib::release_rev.eq(rev_row.id))
+ .order((
+ release_contrib::role.asc(),
+ release_contrib::index_val.asc(),
+ ))
+ .get_results(conn)?
+ .into_iter()
+ .map(|c: ReleaseContribRow| ReleaseContrib {
+ index: c.index_val,
+ raw_name: c.raw_name,
+ role: c.role,
+ extra: c.extra_json,
+ creator_id: c.creator_ident_id.map(|v| FatCatId::from_uuid(&v).to_string()),
+ creator: None,
+ })
+ .collect();
+
+ let abstracts: Vec<ReleaseEntityAbstracts> = release_rev_abstract::table
+ .inner_join(abstracts::table)
+ .filter(release_rev_abstract::release_rev.eq(rev_row.id))
+ .get_results(conn)?
+ .into_iter()
+ .map(
+ |r: (ReleaseRevAbstractRow, AbstractsRow)| ReleaseEntityAbstracts {
+ sha1: Some(r.0.abstract_sha1),
+ mimetype: r.0.mimetype,
+ lang: r.0.lang,
+ content: Some(r.1.content),
+ },
+ )
+ .collect();
+
+ Ok(ReleaseEntity {
+ title: rev_row.title,
+ release_type: rev_row.release_type,
+ release_status: rev_row.release_status,
+ release_date: rev_row.release_date
+ .map(|v| chrono::DateTime::from_utc(v.and_hms(0, 0, 0), chrono::Utc)),
+ doi: rev_row.doi,
+ pmid: rev_row.pmid,
+ pmcid: rev_row.pmcid,
+ isbn13: rev_row.isbn13,
+ core_id: rev_row.core_id,
+ wikidata_qid: rev_row.wikidata_qid,
+ volume: rev_row.volume,
+ issue: rev_row.issue,
+ pages: rev_row.pages,
+ files: None,
+ container: None,
+ container_id: rev_row.container_ident_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ publisher: rev_row.publisher,
+ language: rev_row.language,
+ work_id: Some(FatCatId::from_uuid(&rev_row.work_ident_id).to_string()),
+ refs: Some(refs),
+ contribs: Some(contribs),
+ abstracts: Some(abstracts),
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ editgroup_id: None,
+ extra: rev_row.extra_json,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+
+ // first verify external identifier syntax
+ for entity in models {
+ if let Some(ref extid) = entity.doi {
+ check_doi(extid)?;
+ }
+ if let Some(ref extid) = entity.pmid {
+ check_pmid(extid)?;
+ }
+ if let Some(ref extid) = entity.pmcid {
+ check_pmcid(extid)?;
+ }
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
+ }
+
+ let rev_ids: Vec<Uuid> = insert_into(release_rev::table)
+ .values(models.iter()
+ .map(|model| Ok(ReleaseRevNewRow {
+ title: model.title.clone(),
+ release_type: model.release_type.clone(),
+ release_status: model.release_status.clone(),
+ release_date: model.release_date.map(|v| v.naive_utc().date()),
+ doi: model.doi.clone(),
+ pmid: model.pmid.clone(),
+ pmcid: model.pmcid.clone(),
+ wikidata_qid: model.wikidata_qid.clone(),
+ isbn13: model.isbn13.clone(),
+ core_id: model.core_id.clone(),
+ volume: model.volume.clone(),
+ issue: model.issue.clone(),
+ pages: model.pages.clone(),
+ work_ident_id: match model.work_id.clone() {
+ None => bail!("release_revs must have a work_id by the time they are inserted; this is an internal soundness error"),
+ Some(s) => FatCatId::from_str(&s)?.to_uuid(),
+ },
+ container_ident_id: match model.container_id.clone() {
+ None => None,
+ Some(s) => Some(FatCatId::from_str(&s)?.to_uuid()),
+ },
+ publisher: model.publisher.clone(),
+ language: model.language.clone(),
+ extra_json: model.extra.clone()
+ }))
+ .collect::<Result<Vec<ReleaseRevNewRow>>>()?)
+ .returning(release_rev::id)
+ .get_results(conn)?;
+
+ let mut release_ref_rows: Vec<ReleaseRefNewRow> = vec![];
+ let mut release_contrib_rows: Vec<ReleaseContribNewRow> = vec![];
+
+ for (model, rev_id) in models.iter().zip(rev_ids.iter()) {
+ match &model.refs {
+ None => (),
+ Some(ref_list) => {
+ let these_ref_rows: Vec<ReleaseRefNewRow> = ref_list
+ .iter()
+ .map(|r| Ok(ReleaseRefNewRow {
+ release_rev: rev_id.clone(),
+ target_release_ident_id: match r.target_release_id.clone() {
+ None => None,
+ Some(v) => Some(FatCatId::from_str(&v)?.to_uuid()),
+ },
+ index_val: r.index,
+ key: r.key.clone(),
+ container_title: r.container_title.clone(),
+ year: r.year,
+ title: r.title.clone(),
+ locator: r.locator.clone(),
+ extra_json: r.extra.clone(),
+ }))
+ .collect::<Result<Vec<ReleaseRefNewRow>>>()?;
+ release_ref_rows.extend(these_ref_rows);
+ }
+ };
+
+ match &model.contribs {
+ None => (),
+ Some(contrib_list) => {
+ let these_contrib_rows: Vec<ReleaseContribNewRow> = contrib_list
+ .iter()
+ .map(|c| Ok(ReleaseContribNewRow {
+ release_rev: rev_id.clone(),
+ creator_ident_id: match c.creator_id.clone() {
+ None => None,
+ Some(v) => Some(FatCatId::from_str(&v)?.to_uuid()),
+ },
+ raw_name: c.raw_name.clone(),
+ index_val: c.index,
+ role: c.role.clone(),
+ extra_json: c.extra.clone(),
+ }))
+ .collect::<Result<Vec<ReleaseContribNewRow>>>()?;
+ release_contrib_rows.extend(these_contrib_rows);
+ }
+ };
+
+ // TODO: this part still isn't parallelized
+ if let Some(abstract_list) = &model.abstracts {
+ // For rows that specify content, we need to insert the abstract if it doesn't exist
+ // already
+ let new_abstracts: Vec<AbstractsRow> = abstract_list
+ .iter()
+ .filter(|ea| ea.content.is_some())
+ .map(|c| AbstractsRow {
+ sha1: Sha1::from(c.content.clone().unwrap()).hexdigest(),
+ content: c.content.clone().unwrap(),
+ })
+ .collect();
+ if !new_abstracts.is_empty() {
+ // Sort of an "upsert"; only inserts new abstract rows if they don't already exist
+ insert_into(abstracts::table)
+ .values(&new_abstracts)
+ .on_conflict(abstracts::sha1)
+ .do_nothing()
+ .execute(conn)?;
+ }
+ let release_abstract_rows: Vec<ReleaseRevAbstractNewRow> = abstract_list
+ .into_iter()
+ .map(|c| Ok(ReleaseRevAbstractNewRow {
+ release_rev: rev_id.clone(),
+ abstract_sha1: match c.content {
+ Some(ref content) => Sha1::from(content).hexdigest(),
+ None => match c.sha1.clone() {
+ Some(v) => v,
+ None => { bail!("either abstract_sha1 or content is required") }
+ },
+ },
+ lang: c.lang.clone(),
+ mimetype: c.mimetype.clone(),
+ }))
+ .collect::<Result<Vec<ReleaseRevAbstractNewRow>>>()?;
+ insert_into(release_rev_abstract::table)
+ .values(release_abstract_rows)
+ .execute(conn)?;
+ }
+ }
+
+ if !release_ref_rows.is_empty() {
+ insert_into(release_ref::table)
+ .values(release_ref_rows)
+ .execute(conn)?;
+ }
+
+ if !release_contrib_rows.is_empty() {
+ insert_into(release_contrib::table)
+ .values(release_contrib_rows)
+ .execute(conn)?;
+ }
+
+ Ok(rev_ids)
+ }
+}
+
+impl EntityCrud for WorkEntity {
+ type EditRow = WorkEditRow;
+ type EditNewRow = WorkEditNewRow;
+ type IdentRow = WorkIdentRow;
+ type IdentNewRow = WorkIdentNewRow;
+ type RevRow = WorkRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(work_ident, work_rev);
+ generic_db_get_rev!(work_rev);
+ generic_db_create!(work_ident, work_edit);
+ generic_db_create_batch!(work_ident, work_edit);
+ generic_db_update!(work_ident, work_edit);
+ generic_db_delete!(work_ident, work_edit);
+ generic_db_get_history!(work_edit);
+ generic_db_insert_rev!();
+
+ fn db_from_row(_conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+
+ Ok(WorkEntity {
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ editgroup_id: None,
+ extra: rev_row.extra_json,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+ let rev_ids: Vec<Uuid> = insert_into(work_rev::table)
+ .values(models.iter()
+ .map(|model| WorkRevNewRow { extra_json: model.extra.clone() } )
+ .collect::<Vec<WorkRevNewRow>>())
+ .returning(work_rev::id)
+ .get_results(conn)?;
+ Ok(rev_ids)
+ }
+}
+
diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs
index 47e00bcf..2d6788eb 100644
--- a/rust/src/database_models.rs
+++ b/rust/src/database_models.rs
@@ -37,7 +37,9 @@ pub trait EntityEditRow {
// Helper for constructing tables
macro_rules! entity_structs {
- ($edit_table:expr, $edit_struct:ident, $ident_table:expr, $ident_struct:ident) => {
+ ($edit_table:expr, $edit_struct:ident, $edit_new_struct:ident, $ident_table:expr,
+ $ident_struct:ident, $ident_new_struct:ident) => {
+
#[derive(Debug, Queryable, Identifiable, Associations, AsChangeset, QueryableByName)]
#[table_name = $edit_table]
pub struct $edit_struct {
@@ -51,6 +53,17 @@ macro_rules! entity_structs {
pub extra_json: Option<serde_json::Value>,
}
+ #[derive(Debug, Associations, AsChangeset, QueryableByName, Insertable)]
+ #[table_name = $edit_table]
+ pub struct $edit_new_struct {
+ pub editgroup_id: Uuid,
+ pub ident_id: Uuid,
+ pub rev_id: Option<Uuid>,
+ pub redirect_id: Option<Uuid>,
+ pub prev_rev: Option<Uuid>,
+ pub extra_json: Option<serde_json::Value>,
+ }
+
impl EntityEditRow for $edit_struct {
/// Go from a row (SQL model) to an API model
fn into_model(self) -> Result<EntityEdit> {
@@ -75,6 +88,14 @@ macro_rules! entity_structs {
pub redirect_id: Option<Uuid>,
}
+ #[derive(Debug, Associations, AsChangeset, Insertable)]
+ #[table_name = $ident_table]
+ pub struct $ident_new_struct {
+ pub is_live: bool,
+ pub rev_id: Option<Uuid>,
+ pub redirect_id: Option<Uuid>,
+ }
+
impl EntityIdentRow for $ident_struct {
fn state(&self) -> Result<EntityState> {
if !self.is_live {
@@ -104,11 +125,25 @@ pub struct ContainerRevRow {
pub coden: Option<String>,
}
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "container_rev"]
+pub struct ContainerRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+ pub name: String,
+ pub publisher: Option<String>,
+ pub issnl: Option<String>,
+ pub wikidata_qid: Option<String>,
+ pub abbrev: Option<String>,
+ pub coden: Option<String>,
+}
+
entity_structs!(
"container_edit",
ContainerEditRow,
+ ContainerEditNewRow,
"container_ident",
- ContainerIdentRow
+ ContainerIdentRow,
+ ContainerIdentNewRow
);
#[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)]
@@ -123,11 +158,24 @@ pub struct CreatorRevRow {
pub wikidata_qid: Option<String>,
}
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "creator_rev"]
+pub struct CreatorRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+ pub display_name: String,
+ pub given_name: Option<String>,
+ pub surname: Option<String>,
+ pub orcid: Option<String>,
+ pub wikidata_qid: Option<String>,
+}
+
entity_structs!(
"creator_edit",
CreatorEditRow,
+ CreatorEditNewRow,
"creator_ident",
- CreatorIdentRow
+ CreatorIdentRow,
+ CreatorIdentNewRow
);
#[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)]
@@ -159,7 +207,18 @@ pub struct FileRevRow {
pub mimetype: Option<String>,
}
-entity_structs!("file_edit", FileEditRow, "file_ident", FileIdentRow);
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "file_rev"]
+pub struct FileRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+ pub size: Option<i64>,
+ pub sha1: Option<String>,
+ pub sha256: Option<String>,
+ pub md5: Option<String>,
+ pub mimetype: Option<String>,
+}
+
+entity_structs!("file_edit", FileEditRow, FileEditNewRow, "file_ident", FileIdentRow, FileIdentNewRow);
#[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)]
#[table_name = "release_rev"]
@@ -185,11 +244,36 @@ pub struct ReleaseRevRow {
pub language: Option<String>,
}
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "release_rev"]
+pub struct ReleaseRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+ pub work_ident_id: Uuid,
+ pub container_ident_id: Option<Uuid>,
+ pub title: String,
+ pub release_type: Option<String>,
+ pub release_status: Option<String>,
+ pub release_date: Option<chrono::NaiveDate>,
+ pub doi: Option<String>,
+ pub pmid: Option<String>,
+ pub pmcid: Option<String>,
+ pub wikidata_qid: Option<String>,
+ pub isbn13: Option<String>,
+ pub core_id: Option<String>,
+ pub volume: Option<String>,
+ pub issue: Option<String>,
+ pub pages: Option<String>,
+ pub publisher: Option<String>,
+ pub language: Option<String>,
+}
+
entity_structs!(
"release_edit",
ReleaseEditRow,
+ ReleaseEditNewRow,
"release_ident",
- ReleaseIdentRow
+ ReleaseIdentRow,
+ ReleaseIdentNewRow
);
#[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)]
@@ -199,7 +283,13 @@ pub struct WorkRevRow {
pub extra_json: Option<serde_json::Value>,
}
-entity_structs!("work_edit", WorkEditRow, "work_ident", WorkIdentRow);
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "work_rev"]
+pub struct WorkRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+}
+
+entity_structs!("work_edit", WorkEditRow, WorkEditNewRow, "work_ident", WorkIdentRow, WorkIdentNewRow);
#[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)]
#[table_name = "release_rev_abstract"]
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 50a7d410..a938486b 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -27,6 +27,7 @@ pub mod api_server;
pub mod api_wrappers;
pub mod database_models;
pub mod database_schema;
+pub mod database_entity_crud;
mod errors {
// Create the Error, ErrorKind, ResultExt, and Result types
@@ -47,6 +48,10 @@ mod errors {
description("external identifier doesn't match required pattern")
display("external identifier doesn't match required pattern")
}
+ EditgroupAlreadyAccepted(id: String) {
+ description("editgroup was already accepted")
+ display("attempted to accept an editgroup which was already accepted: {}", id)
+ }
}
}
}
diff --git a/rust/tests/test_api_server.rs b/rust/tests/test_api_server.rs
index 340fb996..54639228 100644
--- a/rust/tests/test_api_server.rs
+++ b/rust/tests/test_api_server.rs
@@ -469,6 +469,71 @@ fn test_post_work() {
}
#[test]
+fn test_update_work() {
+ let (headers, router, conn) = setup();
+
+ check_response(
+ request::post(
+ "http://localhost:9411/v0/work",
+ headers.clone(),
+ r#"{
+ "extra": { "source": "other speculation" }
+ }"#,
+ &router,
+ ),
+ status::Created,
+ None,
+ );
+
+ let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001").unwrap();
+ let editgroup_id = get_or_create_editgroup(editor_id, &conn).unwrap();
+ check_response(
+ request::post(
+ &format!(
+ "http://localhost:9411/v0/editgroup/{}/accept",
+ uuid2fcid(&editgroup_id)
+ ),
+ headers.clone(),
+ "",
+ &router,
+ ),
+ status::Ok,
+ None,
+ );
+}
+
+#[test]
+fn test_delete_work() {
+ let (headers, router, conn) = setup();
+
+ check_response(
+ request::delete(
+ "http://localhost:9411/v0/work/aaaaaaaaaaaaavkvaaaaaaaaai",
+ headers.clone(),
+ &router,
+ ),
+ status::Ok,
+ None,
+ );
+
+ let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001").unwrap();
+ let editgroup_id = get_or_create_editgroup(editor_id, &conn).unwrap();
+ check_response(
+ request::post(
+ &format!(
+ "http://localhost:9411/v0/editgroup/{}/accept",
+ uuid2fcid(&editgroup_id)
+ ),
+ headers.clone(),
+ "",
+ &router,
+ ),
+ status::Ok,
+ None,
+ );
+}
+
+#[test]
fn test_accept_editgroup() {
let (headers, router, conn) = setup();