Skip to content

Commit 0c48dce

Browse files
committed
Convert schema meta to GraphQL schema
1 parent a5ab971 commit 0c48dce

7 files changed

Lines changed: 545 additions & 23 deletions

File tree

.github/workflows/tests.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ jobs:
220220
- name: Additional Integration tests
221221
working-directory: ./examples/postgres
222222
run: |
223-
cargo test --test query_array_tests
224-
rm tests/query_array_tests.rs
223+
cargo test --test query_array_tests schema_meta_tests
224+
rm tests/query_array_tests.rs tests/schema_meta_tests.rs
225225
- name: Remove generated folder
226226
run: rm -rf ./examples/postgres/src
227227
- name: Generate entities

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ heck = { version = "0.4.1" }
2727
thiserror = { version = "1.0.44" }
2828
fnv = { version = "1.0.7" }
2929
lazy_static = { version = "1.5" }
30+
serde = { version = "1.0", optional = true }
3031
serde_json = { version = "1.0" }
3132
pluralizer = { version = "0.5", optional = true }
3233

3334
[features]
34-
default = ["field-camel-case"]
35+
default = ["field-camel-case", "schema-meta"]
3536
macros = ["seaography-macros"]
37+
schema-meta = ["macros", "serde/derive", "with-postgres-array"]
3638
rbac = ["sea-orm/rbac"]
3739
with-json = ["sea-orm/with-json"]
3840
with-chrono = ["sea-orm/with-chrono", "async-graphql/chrono"]

examples/postgres/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ tracing-subscriber = { version = "0.3.17" }
1515
[dependencies.seaography]
1616
path = "../../"
1717
version = "~2.0.0-rc.1" # seaography version
18-
features = ["with-decimal", "with-chrono", "with-postgres-array"]
18+
features = ["with-decimal", "with-chrono", "with-postgres-array", "schema-meta"]
1919

2020
[dev-dependencies]
2121
serde_json = { version = "1.0.103" }
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
use async_graphql::{dynamic::*, Response};
2+
use sea_orm::Database;
3+
use seaography::async_graphql;
4+
5+
pub async fn get_schema() -> Schema {
6+
let database = Database::connect("postgres://sea:sea@127.0.0.1/sakila")
7+
.await
8+
.unwrap();
9+
let schema = seaography_postgres_example::query_root::schema(database, None, None).unwrap();
10+
11+
schema
12+
}
13+
14+
pub fn assert_eq(a: Response, b: &str) {
15+
assert_eq!(
16+
a.data.into_json().unwrap(),
17+
serde_json::from_str::<serde_json::Value>(b).unwrap()
18+
)
19+
}
20+
21+
#[tokio::test]
22+
async fn test_schema_meta() {
23+
let schema = get_schema().await;
24+
25+
assert_eq(
26+
schema
27+
.execute(
28+
r#"{
29+
city: _seaography_schema_metadata(table_name: "city") {
30+
columns {
31+
name
32+
nullable
33+
type_ {
34+
primitive
35+
array {
36+
array {
37+
primitive
38+
}
39+
}
40+
enumeration {
41+
name
42+
variants
43+
}
44+
}
45+
}
46+
}
47+
film: _seaography_schema_metadata(table_name: "film") {
48+
columns {
49+
name
50+
nullable
51+
type_ {
52+
primitive
53+
array {
54+
array {
55+
primitive
56+
}
57+
}
58+
enumeration {
59+
name
60+
variants
61+
}
62+
}
63+
}
64+
}
65+
}
66+
"#,
67+
)
68+
.await,
69+
r#"
70+
{
71+
"city": {
72+
"columns": [
73+
{
74+
"name": "city_id",
75+
"nullable": false,
76+
"type_": {
77+
"primitive": "integer",
78+
"array": null,
79+
"enumeration": null
80+
}
81+
},
82+
{
83+
"name": "city",
84+
"nullable": false,
85+
"type_": {
86+
"primitive": "string",
87+
"array": null,
88+
"enumeration": null
89+
}
90+
},
91+
{
92+
"name": "country_id",
93+
"nullable": false,
94+
"type_": {
95+
"primitive": "integer",
96+
"array": null,
97+
"enumeration": null
98+
}
99+
},
100+
{
101+
"name": "last_update",
102+
"nullable": false,
103+
"type_": {
104+
"primitive": "datetime",
105+
"array": null,
106+
"enumeration": null
107+
}
108+
}
109+
]
110+
},
111+
"film": {
112+
"columns": [
113+
{
114+
"name": "film_id",
115+
"nullable": false,
116+
"type_": {
117+
"primitive": "integer",
118+
"array": null,
119+
"enumeration": null
120+
}
121+
},
122+
{
123+
"name": "title",
124+
"nullable": false,
125+
"type_": {
126+
"primitive": "string",
127+
"array": null,
128+
"enumeration": null
129+
}
130+
},
131+
{
132+
"name": "description",
133+
"nullable": true,
134+
"type_": {
135+
"primitive": "string",
136+
"array": null,
137+
"enumeration": null
138+
}
139+
},
140+
{
141+
"name": "release_year",
142+
"nullable": true,
143+
"type_": {
144+
"primitive": "integer",
145+
"array": null,
146+
"enumeration": null
147+
}
148+
},
149+
{
150+
"name": "language_id",
151+
"nullable": false,
152+
"type_": {
153+
"primitive": "integer",
154+
"array": null,
155+
"enumeration": null
156+
}
157+
},
158+
{
159+
"name": "original_language_id",
160+
"nullable": true,
161+
"type_": {
162+
"primitive": "integer",
163+
"array": null,
164+
"enumeration": null
165+
}
166+
},
167+
{
168+
"name": "rental_duration",
169+
"nullable": false,
170+
"type_": {
171+
"primitive": "integer",
172+
"array": null,
173+
"enumeration": null
174+
}
175+
},
176+
{
177+
"name": "rental_rate",
178+
"nullable": false,
179+
"type_": {
180+
"primitive": "decimal",
181+
"array": null,
182+
"enumeration": null
183+
}
184+
},
185+
{
186+
"name": "length",
187+
"nullable": true,
188+
"type_": {
189+
"primitive": "integer",
190+
"array": null,
191+
"enumeration": null
192+
}
193+
},
194+
{
195+
"name": "replacement_cost",
196+
"nullable": false,
197+
"type_": {
198+
"primitive": "decimal",
199+
"array": null,
200+
"enumeration": null
201+
}
202+
},
203+
{
204+
"name": "rating",
205+
"nullable": true,
206+
"type_": {
207+
"primitive": null,
208+
"array": null,
209+
"enumeration": {
210+
"name": "mpaa_rating",
211+
"variants": [
212+
"G",
213+
"NC-17",
214+
"PG",
215+
"PG-13",
216+
"R"
217+
]
218+
}
219+
}
220+
},
221+
{
222+
"name": "last_update",
223+
"nullable": false,
224+
"type_": {
225+
"primitive": "datetime",
226+
"array": null,
227+
"enumeration": null
228+
}
229+
},
230+
{
231+
"name": "special_features",
232+
"nullable": true,
233+
"type_": {
234+
"primitive": null,
235+
"array": {
236+
"array": {
237+
"primitive": "string"
238+
}
239+
},
240+
"enumeration": null
241+
}
242+
}
243+
]
244+
}
245+
}
246+
"#,
247+
)
248+
}

src/builder.rs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use crate::{
1717
PaginationInfoObjectBuilder, PaginationInputBuilder,
1818
};
1919

20+
type MetadataHashMap = std::collections::HashMap<String, serde_json::Value>;
21+
2022
/// The Builder is used to create the Schema for GraphQL
2123
///
2224
/// You can populate it with the entities, enumerations of your choice
@@ -45,7 +47,7 @@ pub struct Builder {
4547
pub subscriptions: Vec<SubscriptionField>,
4648

4749
/// holds all entities metadata
48-
pub metadata: std::collections::HashMap<String, serde_json::Value>,
50+
pub metadata: MetadataHashMap,
4951

5052
/// holds a copy to the database connection
5153
pub connection: sea_orm::DatabaseConnection,
@@ -325,32 +327,36 @@ impl Builder {
325327
self
326328
}
327329

328-
/// used to consume the builder context and generate a ready to be completed GraphQL schema
329-
pub fn schema_builder(self) -> SchemaBuilder {
330-
let query = self.query;
331-
let mutation = self.mutation;
332-
let subscription = self.subscription;
333-
let schema = self.schema;
334-
let have_subscription = !self.subscriptions.is_empty();
330+
#[cfg(feature = "schema-meta")]
331+
fn register_schema_meta(&mut self) {
332+
use crate::schema;
335333

336-
// register queries
337-
let query = self
338-
.queries
339-
.into_iter()
340-
.fold(query, |query, field| query.field(field));
334+
self.register_custom_output::<schema::Table>();
335+
self.register_custom_output::<schema::Column>();
336+
self.register_custom_output::<schema::ColumnType>();
337+
self.register_custom_output::<schema::Array>();
338+
self.register_custom_output::<schema::Enumeration>();
339+
340+
use crate::GqlOutputModelType;
341+
use async_graphql::dynamic::FieldValue;
341342

342343
const TABLE_NAME: &str = "table_name";
344+
let metadata_hashmap = std::mem::take(&mut self.metadata);
345+
343346
let field = Field::new(
344-
"_sea_orm_entity_metadata",
345-
TypeRef::named(TypeRef::STRING),
347+
"_seaography_schema_metadata",
348+
crate::schema::Table::gql_output_type_ref(self.context),
346349
move |ctx| {
347-
let metadata_hashmap = self.metadata.clone();
350+
let metadata_hashmap = metadata_hashmap.clone();
348351
FieldFuture::new(async move {
349352
let table_name = ctx.args.try_get(TABLE_NAME)?.string()?;
350353
if let Some(metadata) = metadata_hashmap.get(table_name) {
351-
Ok(Some(async_graphql::Value::from_json(metadata.to_owned())?))
354+
let table: crate::schema::Table = serde_json::from_value(metadata.clone())?;
355+
Ok(Some(FieldValue::owned_any(table)))
352356
} else {
353-
Ok(None)
357+
Err(async_graphql::Error::new(format!(
358+
"table not found `{table_name}`"
359+
)))
354360
}
355361
})
356362
},
@@ -359,7 +365,26 @@ impl Builder {
359365
TABLE_NAME,
360366
TypeRef::named_nn(TypeRef::STRING),
361367
));
362-
let query = query.field(field);
368+
369+
self.queries.push(field);
370+
}
371+
372+
/// used to consume the builder context and generate a ready to be completed GraphQL schema
373+
pub fn schema_builder(mut self) -> SchemaBuilder {
374+
#[cfg(feature = "schema-meta")]
375+
self.register_schema_meta();
376+
377+
let query = self.query;
378+
let mutation = self.mutation;
379+
let subscription = self.subscription;
380+
let schema = self.schema;
381+
let have_subscription = !self.subscriptions.is_empty();
382+
383+
// register queries
384+
let query = self
385+
.queries
386+
.into_iter()
387+
.fold(query, |query, field| query.field(field));
363388

364389
// register mutations
365390
let mutation = self

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ pub use error::*;
267267
pub mod rbac;
268268
pub use rbac::*;
269269

270+
#[cfg(feature = "schema-meta")]
271+
pub mod schema;
272+
270273
pub type SimpleNamingFn = Box<dyn Fn(&str) -> String + Sync + Send>;
271274
pub type ComplexNamingFn = Box<dyn Fn(&str, &str) -> String + Sync + Send>;
272275

0 commit comments

Comments
 (0)