diff --git a/Cargo.toml b/Cargo.toml index 39247e6c..af71e237 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pg_graphql" -version = "1.4.4" +version = "1.5.0" edition = "2021" [lib] diff --git a/docs/changelog.md b/docs/changelog.md index 227e7bbe..3c2461dc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -64,4 +64,8 @@ ## 1.4.4 - bugfix: function returning a noncompliant view's type no longer breaks introspection +## 1.5.0 +- feature: `first`/`offset` based pagination +- feature: improved descriptions for all internal error states + ## master diff --git a/src/builder.rs b/src/builder.rs index 580dc93c..39e86ec2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -122,14 +122,20 @@ fn parse_node_id(encoded: gson::Value) -> Result { } let mut x_arr_iter = x_arr.into_iter(); - let schema_name = match x_arr_iter.next().unwrap() { + let schema_name = match x_arr_iter + .next() + .expect("failed to get schema name from nodeId argument") + { serde_json::Value::String(s) => s, _ => { return Err("Invalid value passed to nodeId argument. Error 6".to_string()); } }; - let table_name = match x_arr_iter.next().unwrap() { + let table_name = match x_arr_iter + .next() + .expect("failed to get table name from nodeId argument") + { serde_json::Value::String(s) => s, _ => { return Err("Invalid value passed to nodeId argument. Error 7".to_string()); @@ -188,11 +194,15 @@ where )?; // [OrderBy!] - let insert_type: InsertInputType = - match field.get_arg("objects").unwrap().type_().unmodified_type() { - __Type::InsertInput(insert_type) => insert_type, - _ => return Err("Could not locate Insert Entity type".to_string()), - }; + let insert_type: InsertInputType = match field + .get_arg("objects") + .expect("failed to get `objects` argument") + .type_() + .unmodified_type() + { + __Type::InsertInput(insert_type) => insert_type, + _ => return Err("Could not locate Insert Entity type".to_string()), + }; let mut objects: Vec = vec![]; @@ -300,7 +310,9 @@ where } "__typename" => InsertSelection::Typename { alias: alias_or_name(&selection_field), - typename: xtype.name().unwrap(), + typename: xtype + .name() + .expect("insert response type should have a name"), }, _ => return Err("unexpected field type on insert response".to_string()), }), @@ -362,7 +374,11 @@ where let validated: gson::Value = read_argument("set", field, query_field, variables, variable_definitions)?; - let update_type: UpdateInputType = match field.get_arg("set").unwrap().type_().unmodified_type() + let update_type: UpdateInputType = match field + .get_arg("set") + .expect("failed to get `set` argument") + .type_() + .unmodified_type() { __Type::UpdateInput(type_) => type_, _ => return Err("Could not locate update entity type".to_string()), @@ -464,7 +480,9 @@ where } "__typename" => UpdateSelection::Typename { alias: alias_or_name(&selection_field), - typename: xtype.name().unwrap(), + typename: xtype + .name() + .expect("update response type should have a name"), }, _ => return Err("unexpected field type on update response".to_string()), }), @@ -566,7 +584,9 @@ where } "__typename" => DeleteSelection::Typename { alias: alias_or_name(&selection_field), - typename: xtype.name().unwrap(), + typename: xtype + .name() + .expect("delete response type should have a name"), }, _ => return Err("unexpected field type on delete response".to_string()), }), @@ -1038,7 +1058,11 @@ where variable_definitions, )?; - let filter_type = field.get_arg("filter").unwrap().type_().unmodified_type(); + let filter_type = field + .get_arg("filter") + .expect("failed to get filter argument") + .type_() + .unmodified_type(); if !matches!(filter_type, __Type::FilterEntity(_)) { return Err("Could not locate Filter Entity type".to_string()); } @@ -1190,11 +1214,15 @@ where )?; // [
OrderBy!] - let order_type: OrderByEntityType = - match field.get_arg("orderBy").unwrap().type_().unmodified_type() { - __Type::OrderByEntity(order_entity) => order_entity, - _ => return Err("Could not locate OrderBy Entity type".to_string()), - }; + let order_type: OrderByEntityType = match field + .get_arg("orderBy") + .expect("failed to get orderBy argument") + .type_() + .unmodified_type() + { + __Type::OrderByEntity(order_entity) => order_entity, + _ => return Err("Could not locate OrderBy Entity type".to_string()), + }; let mut orders = vec![]; @@ -1279,7 +1307,12 @@ where variables, variable_definitions, )?; - let _: Scalar = match field.get_arg(arg_name).unwrap().type_().unmodified_type() { + let _: Scalar = match field + .get_arg(arg_name) + .unwrap_or_else(|| panic!("failed to get {arg_name} argument")) + .type_() + .unmodified_type() + { __Type::Scalar(x) => x, _ => return Err(format!("Could not argument {}", arg_name)), }; @@ -1442,7 +1475,7 @@ where }, "__typename" => ConnectionSelection::Typename { alias: alias_or_name(&selection_field), - typename: xtype.name().unwrap(), + typename: xtype.name().expect("connection type should have a name"), }, _ => return Err("unexpected field type on connection".to_string()), }, @@ -1520,7 +1553,7 @@ where }, "__typename" => PageInfoSelection::Typename { alias: alias_or_name(&selection_field), - typename: xtype.name().unwrap(), + typename: xtype.name().expect("page info type should have a name"), }, _ => return Err("unexpected field type on pageInfo".to_string()), }), @@ -1586,7 +1619,7 @@ where }, "__typename" => EdgeSelection::Typename { alias: alias_or_name(&selection_field), - typename: xtype.name().unwrap(), + typename: xtype.name().expect("edge type should have a name"), }, _ => return Err("unexpected field type on edge".to_string()), }, @@ -1752,7 +1785,7 @@ where _ => match f.name().as_ref() { "__typename" => NodeSelection::Typename { alias: alias_or_name(&selection_field), - typename: xtype.name().unwrap(), + typename: xtype.name().expect("node type should have a name"), }, _ => match f.type_().unmodified_type() { __Type::Connection(_) => { @@ -2177,10 +2210,7 @@ impl __Schema { if name_arg.is_some() { type_name = name_arg; } - if type_name.is_none() { - return Err("no name found for __type".to_string()); - } - let type_name = type_name.unwrap(); + let type_name = type_name.ok_or("no name found for __type".to_string())?; let type_map = type_map(self); let requested_type: Option<&__Type> = type_map.get(&type_name); @@ -2504,83 +2534,91 @@ impl __Schema { match field_map.get(field_name) { None => return Err(format!("unknown field in __Schema: {}", field_name)), - Some(f) => builder_fields.push(__SchemaSelection { - alias: alias_or_name(&selection_field), - selection: match f.name().as_str() { - "types" => { - let builders = self - .types() - .iter() - // Filter out intropsection meta-types - //.filter(|x| { - // !x.name().unwrap_or("".to_string()).starts_with("__") - //}) - .map(|t| { - self.to_type_builder( - f, - &selection_field, - fragment_definitions, - t.name(), - variables, - variable_definitions, - ) - .map(|x| x.unwrap()) - }) - // from Vec to Result - .collect::, _>>()?; - __SchemaField::Types(builders) - } - "queryType" => { - let builder = self.to_type_builder( - f, - &selection_field, - fragment_definitions, - Some("Query".to_string()), - variables, - variable_definitions, - )?; - __SchemaField::QueryType(builder.unwrap()) - } - "mutationType" => { - let builder = self.to_type_builder( - f, - &selection_field, - fragment_definitions, - Some("Mutation".to_string()), - variables, - variable_definitions, - )?; - __SchemaField::MutationType(builder) - } - "subscriptionType" => __SchemaField::SubscriptionType(None), - "directives" => { - let builders = self - .directives() - .iter() - .map(|directive| { - self.to_directive_builder( - directive, - &selection_field, - fragment_definitions, - variables, - variable_definitions, - ) - }) - .collect::, _>>()?; - __SchemaField::Directives(builders) - } - "__typename" => __SchemaField::Typename { - alias: alias_or_name(&selection_field), - typename: field.name(), + Some(f) => { + builder_fields.push(__SchemaSelection { + alias: alias_or_name(&selection_field), + selection: match f.name().as_str() { + "types" => { + let builders = self + .types() + .iter() + // Filter out intropsection meta-types + //.filter(|x| { + // !x.name().unwrap_or("".to_string()).starts_with("__") + //}) + .map(|t| { + self.to_type_builder( + f, + &selection_field, + fragment_definitions, + t.name(), + variables, + variable_definitions, + ) + .map(|x| { + x.expect( + "type builder should exist for types field", + ) + }) + }) + // from Vec to Result + .collect::, _>>()?; + __SchemaField::Types(builders) + } + "queryType" => { + let builder = self.to_type_builder( + f, + &selection_field, + fragment_definitions, + Some("Query".to_string()), + variables, + variable_definitions, + )?; + __SchemaField::QueryType(builder.expect( + "type builder should exist for queryType field", + )) + } + "mutationType" => { + let builder = self.to_type_builder( + f, + &selection_field, + fragment_definitions, + Some("Mutation".to_string()), + variables, + variable_definitions, + )?; + __SchemaField::MutationType(builder) + } + "subscriptionType" => __SchemaField::SubscriptionType(None), + "directives" => { + let builders = self + .directives() + .iter() + .map(|directive| { + self.to_directive_builder( + directive, + &selection_field, + fragment_definitions, + variables, + variable_definitions, + ) + }) + .collect::, _>>()?; + __SchemaField::Directives(builders) + } + "__typename" => __SchemaField::Typename { + alias: alias_or_name(&selection_field), + typename: field.name(), + }, + _ => { + return Err(format!( + "unexpected field {} type on __Schema", + field_name + )) + } }, - _ => { - return Err(format!( - "unexpected field {} type on __Schema", - field_name - )) - } - }, - }), + }) + } } } diff --git a/src/graphql.rs b/src/graphql.rs index b16783ff..f82281fd 100644 --- a/src/graphql.rs +++ b/src/graphql.rs @@ -133,7 +133,10 @@ impl __Schema { column_names = &fkey.referenced_table_meta.column_names; } - let table: &Arc
= self.context.get_table_by_oid(table_ref.oid).unwrap(); + let table: &Arc
= self + .context + .get_table_by_oid(table_ref.oid) + .expect("failed to get table by oid"); let is_inflection_on = self.inflect_names(table.schema_oid); @@ -1123,6 +1126,15 @@ pub struct FilterTypeType { pub schema: Arc<__Schema>, } +impl FilterTypeType { + fn entity_name(&self) -> String { + match &self.entity { + FilterableType::Scalar(s) => s.name().expect("scalar name should exist"), + FilterableType::Enum(e) => e.name().expect("enum type name should exist"), + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct FilterEntityType { pub table: Arc
, @@ -1340,7 +1352,11 @@ fn function_args(schema: &Arc<__Schema>, func: &Arc) -> Vec<__InputVal if matches!(t.category, TypeCategory::Pseudo) { None } else { - Some((t, arg_name.unwrap(), arg_default)) + Some(( + t, + arg_name.expect("function arg name should exist"), + arg_default, + )) } } None => None, @@ -1713,7 +1729,7 @@ impl ___Type for NodeInterfaceType { } fn possible_types(&self) -> Option> { - let node_interface_name = self.name().unwrap(); + let node_interface_name = self.name().expect("node interface type name should exist"); let mut possible_types = vec![]; @@ -1723,8 +1739,10 @@ impl ___Type for NodeInterfaceType { .sorted_by(|a, b| a.name().cmp(&b.name())) { let type_interfaces: Vec<__Type> = type_.interfaces().unwrap_or(vec![]); - let interface_names: Vec = - type_interfaces.iter().map(|x| x.name().unwrap()).collect(); + let interface_names: Vec = type_interfaces + .iter() + .map(|x| x.name().expect("type interface name should exist")) + .collect(); if interface_names.contains(&node_interface_name) { possible_types.push(type_) } @@ -2076,7 +2094,7 @@ impl ___Type for NodeType { if foreign_table.is_none() { continue; } - let foreign_table = foreign_table.unwrap(); + let foreign_table = foreign_table.expect("foreign table should exist"); if !self .schema .graphql_table_select_types_are_valid(foreign_table) @@ -2125,7 +2143,7 @@ impl ___Type for NodeType { if foreign_table.is_none() { continue; } - let foreign_table = foreign_table.unwrap(); + let foreign_table = foreign_table.expect("foreign table should exist"); if !self .schema .graphql_table_select_types_are_valid(foreign_table) @@ -3354,10 +3372,7 @@ impl ___Type for FilterTypeType { } fn name(&self) -> Option { - match &self.entity { - FilterableType::Scalar(s) => Some(format!("{}Filter", s.name().unwrap())), - FilterableType::Enum(e) => Some(format!("{}Filter", e.name().unwrap())), - } + Some(format!("{}Filter", self.entity_name())) } fn fields(&self, _include_deprecated: bool) -> Option> { @@ -3367,10 +3382,7 @@ impl ___Type for FilterTypeType { fn description(&self) -> Option { Some(format!( "Boolean expression comparing fields on type \"{}\"", - match &self.entity { - FilterableType::Scalar(s) => s.name().unwrap(), - FilterableType::Enum(e) => e.name().unwrap(), - } + self.entity_name() )) } @@ -3853,7 +3865,7 @@ pub struct __Schema { #[cached( type = "SizedCache>", create = "{ SizedCache::with_size(200) }", - convert = r#"{ serde_json::ser::to_string(&schema.context.config).unwrap() }"#, + convert = r#"{ serde_json::ser::to_string(&schema.context.config).expect("schema config should be a string") }"#, sync_writes = true )] pub fn type_map(schema: &__Schema) -> HashMap { @@ -3861,7 +3873,7 @@ pub fn type_map(schema: &__Schema) -> HashMap { .types() .into_iter() .filter(|x| x.name().is_some()) - .map(|x| (x.name().unwrap(), x)) + .map(|x| (x.name().expect("type should have a name"), x)) .collect(); tmap } diff --git a/src/lib.rs b/src/lib.rs index ede8379c..9338cc00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,8 @@ fn resolve( } }; - let value: serde_json::Value = serde_json::to_value(response).unwrap(); + let value: serde_json::Value = + serde_json::to_value(response).expect("failed to convert response into json"); pgrx::JsonB(value) } diff --git a/src/parser_util.rs b/src/parser_util.rs index b413912c..b80ba970 100644 --- a/src/parser_util.rs +++ b/src/parser_util.rs @@ -577,54 +577,56 @@ pub fn validate_arg_from_type(type_: &__Type, value: &gson::Value) -> Result value.clone(), } } - __Type::Enum(enum_) => match value { - GsonValue::Absent => value.clone(), - GsonValue::Null => value.clone(), - GsonValue::String(user_input_string) => { - let matches_enum_value = enum_ - .enum_values(true) - .into_iter() - .flatten() - .find(|x| x.name().as_str() == user_input_string); - match matches_enum_value { - Some(_) => { - match &enum_.enum_ { - EnumSource::Enum(e) => e - .directives - .mappings - .as_ref() - // Use mappings if available and mapped - .and_then(|mappings| mappings.get_by_right(user_input_string)) - .map(|val| GsonValue::String(val.clone())) - .unwrap_or_else(|| value.clone()), - EnumSource::FilterIs => value.clone(), + __Type::Enum(enum_) => { + let enum_name = enum_.name().expect("enum type should have a name"); + match value { + GsonValue::Absent => value.clone(), + GsonValue::Null => value.clone(), + GsonValue::String(user_input_string) => { + let matches_enum_value = enum_ + .enum_values(true) + .into_iter() + .flatten() + .find(|x| x.name().as_str() == user_input_string); + match matches_enum_value { + Some(_) => { + match &enum_.enum_ { + EnumSource::Enum(e) => e + .directives + .mappings + .as_ref() + // Use mappings if available and mapped + .and_then(|mappings| mappings.get_by_right(user_input_string)) + .map(|val| GsonValue::String(val.clone())) + .unwrap_or_else(|| value.clone()), + EnumSource::FilterIs => value.clone(), + } } - } - None => { - return Err(format!("Invalid input for {} type", enum_.name().unwrap())) + None => return Err(format!("Invalid input for {} type", enum_name)), } } + _ => return Err(format!("Invalid input for {} type", enum_name)), } - _ => return Err(format!("Invalid input for {} type", enum_.name().unwrap())), - }, - __Type::OrderBy(enum_) => match value { - GsonValue::Absent => value.clone(), - GsonValue::Null => value.clone(), - GsonValue::String(user_input_string) => { - let matches_enum_value = enum_ - .enum_values(true) - .into_iter() - .flatten() - .find(|x| x.name().as_str() == user_input_string); - match matches_enum_value { - Some(_) => value.clone(), - None => { - return Err(format!("Invalid input for {} type", enum_.name().unwrap())) + } + __Type::OrderBy(enum_) => { + let enum_name = enum_.name().expect("order by type should have a name"); + match value { + GsonValue::Absent => value.clone(), + GsonValue::Null => value.clone(), + GsonValue::String(user_input_string) => { + let matches_enum_value = enum_ + .enum_values(true) + .into_iter() + .flatten() + .find(|x| x.name().as_str() == user_input_string); + match matches_enum_value { + Some(_) => value.clone(), + None => return Err(format!("Invalid input for {} type", enum_name)), } } + _ => return Err(format!("Invalid input for {} type", enum_name)), } - _ => return Err(format!("Invalid input for {} type", enum_.name().unwrap())), - }, + } __Type::List(list_type) => { let inner_type: __Type = *list_type.type_.clone(); match value { diff --git a/src/resolve.rs b/src/resolve.rs index ced04848..ee77dd3d 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -157,10 +157,11 @@ where let query_type = schema_type.query_type(); let map = field_map(&query_type); + let query_type_name = query_type.name().expect("query type should have a name"); let selections = match normalize_selection_set( &selection_set, &fragment_definitions, - &query_type.name().unwrap(), + &query_type_name, variables, &query_type, ) { @@ -196,8 +197,7 @@ where res_errors.push(ErrorMessage { message: format!( "Unknown field {:?} on type {}", - selection.name, - query_type.name().unwrap() + selection.name, query_type_name ), }); } @@ -376,10 +376,13 @@ where let map = field_map(&mutation_type); + let mutation_type_name = mutation_type + .name() + .expect("mutation type should have a name"); let selections = match normalize_selection_set( &selection_set, &fragment_definitions, - &mutation_type.name().unwrap(), + &mutation_type_name, variables, &mutation_type, ) { @@ -409,8 +412,7 @@ where conn = match maybe_field_def { None => Err(format!( "Unknown field {:?} on type {}", - selection.name, - mutation_type.name().unwrap() + selection.name, mutation_type_name ))?, Some(field_def) => match field_def.type_.unmodified_type() { __Type::InsertResponse(_) => { diff --git a/src/sql_types.rs b/src/sql_types.rs index c18b1d7d..045e8e64 100644 --- a/src/sql_types.rs +++ b/src/sql_types.rs @@ -756,8 +756,12 @@ pub(crate) fn get_one_readonly( pub fn load_sql_config() -> Config { let query = include_str!("../sql/load_sql_config.sql"); - let sql_result: serde_json::Value = get_one_readonly::(query).unwrap().unwrap().0; - let config: Config = serde_json::from_value(sql_result).unwrap(); + let sql_result: serde_json::Value = get_one_readonly::(query) + .expect("failed to read sql config") + .expect("sql config is missing") + .0; + let config: Config = + serde_json::from_value(sql_result).expect("failed to convert sql config into json"); config } @@ -776,7 +780,10 @@ pub fn calculate_hash(t: &T) -> u64 { pub fn load_sql_context(_config: &Config) -> Result, String> { // cache value for next query let query = include_str!("../sql/load_sql_context.sql"); - let sql_result: serde_json::Value = get_one_readonly::(query).unwrap().unwrap().0; + let sql_result: serde_json::Value = get_one_readonly::(query) + .expect("failed to read sql context") + .expect("sql context is missing") + .0; let context: Result = serde_json::from_value(sql_result); /// This pass cross-reference types with its details diff --git a/src/transpile.rs b/src/transpile.rs index 2c52d3ec..c16415d5 100644 --- a/src/transpile.rs +++ b/src/transpile.rs @@ -12,11 +12,17 @@ use std::collections::HashSet; use std::sync::Arc; pub fn quote_ident(ident: &str) -> String { - unsafe { direct_function_call::(pg_sys::quote_ident, &[ident.into_datum()]).unwrap() } + unsafe { + direct_function_call::(pg_sys::quote_ident, &[ident.into_datum()]) + .expect("failed to quote ident") + } } pub fn quote_literal(ident: &str) -> String { - unsafe { direct_function_call::(pg_sys::quote_literal, &[ident.into_datum()]).unwrap() } + unsafe { + direct_function_call::(pg_sys::quote_literal, &[ident.into_datum()]) + .expect("failed to quote literal") + } } pub fn rand_block_name() -> String { @@ -1305,10 +1311,10 @@ impl QueryEntrypoint for NodeBuilder { let quoted_table = quote_ident(&self.table.name); let object_clause = self.to_sql("ed_block_name, param_context)?; - if self.node_id.is_none() { - return Err("Expected nodeId argument missing".to_string()); - } - let node_id = self.node_id.as_ref().unwrap(); + let node_id = self + .node_id + .as_ref() + .ok_or("Expected nodeId argument missing")?; let node_id_clause = node_id.to_sql("ed_block_name, &self.table, param_context)?;