mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
Merge match
branches to reduce duplication. (#649)
V3_GIT_ORIGIN_REV_ID: 69b2e0a370c547aed9ed798fa54168bc67f98569
This commit is contained in:
parent
c80ab33726
commit
02ee05bed4
@ -41,7 +41,6 @@ inline_always = "allow"
|
||||
manual_is_variant_and = "allow"
|
||||
manual_string_new = "allow"
|
||||
map_unwrap_or = "allow"
|
||||
match_same_arms = "allow"
|
||||
match_wildcard_for_single_variants = "allow"
|
||||
result_large_err = "allow"
|
||||
return_self_not_must_use = "allow"
|
||||
|
@ -105,24 +105,24 @@ impl Error {
|
||||
pub fn to_status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
Error::Internal(_e) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Error::ErrorDecodingAuthorizationHeader(_) => StatusCode::BAD_REQUEST,
|
||||
Error::KidHeaderNotFound => StatusCode::BAD_REQUEST,
|
||||
Error::ExpectedStringifiedJson => StatusCode::BAD_REQUEST,
|
||||
Error::DisallowedDefaultRole => StatusCode::BAD_REQUEST,
|
||||
Error::ParseClaimsMapEntryError {
|
||||
Error::ErrorDecodingAuthorizationHeader(_)
|
||||
| Error::KidHeaderNotFound
|
||||
| Error::ExpectedStringifiedJson
|
||||
| Error::DisallowedDefaultRole
|
||||
| Error::ParseClaimsMapEntryError {
|
||||
claim_name: _,
|
||||
err: _,
|
||||
} => StatusCode::BAD_REQUEST,
|
||||
Error::RequiredClaimNotFound { claim_name: _ } => StatusCode::BAD_REQUEST,
|
||||
Error::AuthorizationHeaderSourceNotFound { header_name: _ } => StatusCode::BAD_REQUEST,
|
||||
Error::CookieNotFound => StatusCode::BAD_REQUEST,
|
||||
Error::CookieNameNotFound { cookie_name: _ } => StatusCode::BAD_REQUEST,
|
||||
Error::AuthorizationHeaderParseError {
|
||||
}
|
||||
| Error::RequiredClaimNotFound { claim_name: _ }
|
||||
| Error::AuthorizationHeaderSourceNotFound { header_name: _ }
|
||||
| Error::CookieNotFound
|
||||
| Error::CookieNameNotFound { cookie_name: _ }
|
||||
| Error::AuthorizationHeaderParseError {
|
||||
err: _,
|
||||
header_name: _,
|
||||
} => StatusCode::BAD_REQUEST,
|
||||
Error::CookieParseError { err: _ } => StatusCode::BAD_REQUEST,
|
||||
Error::MissingCookieValue { cookie_name: _ } => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
| Error::CookieParseError { err: _ }
|
||||
| Error::MissingCookieValue { cookie_name: _ } => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -424,17 +424,16 @@ pub struct Claims {
|
||||
fn get_decoding_key(secret_config: &JWTKeyConfig) -> Result<DecodingKey, Error> {
|
||||
let secret_key = secret_config.key.value.as_bytes();
|
||||
match secret_config.algorithm {
|
||||
jwt::Algorithm::HS256 => Ok(jwt::DecodingKey::from_secret(secret_key)),
|
||||
jwt::Algorithm::HS384 => Ok(jwt::DecodingKey::from_secret(secret_key)),
|
||||
jwt::Algorithm::HS512 => Ok(jwt::DecodingKey::from_secret(secret_key)),
|
||||
jwt::Algorithm::ES256 => jwt::DecodingKey::from_ec_pem(secret_key),
|
||||
jwt::Algorithm::ES384 => jwt::DecodingKey::from_ec_pem(secret_key),
|
||||
jwt::Algorithm::RS256 => jwt::DecodingKey::from_rsa_pem(secret_key),
|
||||
jwt::Algorithm::RS384 => jwt::DecodingKey::from_rsa_pem(secret_key),
|
||||
jwt::Algorithm::RS512 => jwt::DecodingKey::from_rsa_pem(secret_key),
|
||||
jwt::Algorithm::PS256 => jwt::DecodingKey::from_rsa_pem(secret_key),
|
||||
jwt::Algorithm::PS384 => jwt::DecodingKey::from_rsa_pem(secret_key),
|
||||
jwt::Algorithm::PS512 => jwt::DecodingKey::from_rsa_pem(secret_key),
|
||||
jwt::Algorithm::HS256 | jwt::Algorithm::HS384 | jwt::Algorithm::HS512 => {
|
||||
Ok(jwt::DecodingKey::from_secret(secret_key))
|
||||
}
|
||||
jwt::Algorithm::ES256 | jwt::Algorithm::ES384 => jwt::DecodingKey::from_ec_pem(secret_key),
|
||||
jwt::Algorithm::RS256
|
||||
| jwt::Algorithm::RS384
|
||||
| jwt::Algorithm::RS512
|
||||
| jwt::Algorithm::PS256
|
||||
| jwt::Algorithm::PS384
|
||||
| jwt::Algorithm::PS512 => jwt::DecodingKey::from_rsa_pem(secret_key),
|
||||
jwt::Algorithm::EdDSA => jwt::DecodingKey::from_ed_pem(secret_key),
|
||||
}
|
||||
.map_err(|e| Error::Internal(InternalError::JWTDecodingKeyError(e)))
|
||||
|
@ -37,19 +37,8 @@ pub(crate) async fn explain_query_plan(
|
||||
// Here, we are assuming that all root fields are executed in parallel.
|
||||
for (alias, node) in query_plan {
|
||||
match node {
|
||||
NodeQueryPlan::NDCQueryExecution(ndc_query_execution) => {
|
||||
let sequence_steps = get_execution_steps(
|
||||
http_context,
|
||||
alias,
|
||||
&ndc_query_execution.process_response_as,
|
||||
ndc_query_execution.execution_tree.remote_executions,
|
||||
types::NDCRequest::Query(ndc_query_execution.execution_tree.root_node.query),
|
||||
ndc_query_execution.execution_tree.root_node.data_connector,
|
||||
)
|
||||
.await;
|
||||
parallel_root_steps.push(Box::new(types::Step::Sequence(sequence_steps)));
|
||||
}
|
||||
NodeQueryPlan::RelayNodeSelect(Some(ndc_query_execution)) => {
|
||||
NodeQueryPlan::NDCQueryExecution(ndc_query_execution)
|
||||
| NodeQueryPlan::RelayNodeSelect(Some(ndc_query_execution)) => {
|
||||
let sequence_steps = get_execution_steps(
|
||||
http_context,
|
||||
alias,
|
||||
@ -270,10 +259,10 @@ fn simplify_step(step: Box<types::Step>) -> Box<types::Step> {
|
||||
Box::new(types::Step::Sequence(simplified_steps))
|
||||
}
|
||||
}
|
||||
types::Step::ModelSelect(_) => step,
|
||||
types::Step::CommandSelect(_) => step,
|
||||
types::Step::HashJoin => step,
|
||||
types::Step::ForEach(_) => step,
|
||||
types::Step::ModelSelect(_)
|
||||
| types::Step::CommandSelect(_)
|
||||
| types::Step::HashJoin
|
||||
| types::Step::ForEach(_) => step,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,9 @@ pub(crate) fn get_select_filter_predicate<'s>(
|
||||
.as_ref()
|
||||
.and_then(|annotation| match annotation {
|
||||
schema::NamespaceAnnotation::Model { filter, .. } => Some(filter),
|
||||
schema::NamespaceAnnotation::NodeFieldTypeMappings(_) => None,
|
||||
schema::NamespaceAnnotation::EntityTypeMappings(_) => None,
|
||||
schema::NamespaceAnnotation::Command(_) => None,
|
||||
schema::NamespaceAnnotation::NodeFieldTypeMappings(_)
|
||||
| schema::NamespaceAnnotation::EntityTypeMappings(_)
|
||||
| schema::NamespaceAnnotation::Command(_) => None,
|
||||
})
|
||||
// If we're hitting this case, it means that the caller of this
|
||||
// function expects a filter predicate, but it was not annotated
|
||||
@ -52,8 +52,8 @@ pub(crate) fn get_argument_presets(
|
||||
match namespaced_info.as_ref() {
|
||||
None => Ok(None), // no annotation is fine...
|
||||
Some(annotation) => match annotation {
|
||||
schema::NamespaceAnnotation::Command(argument_presets) => Ok(Some(argument_presets)),
|
||||
schema::NamespaceAnnotation::Model {
|
||||
schema::NamespaceAnnotation::Command(argument_presets)
|
||||
| schema::NamespaceAnnotation::Model {
|
||||
argument_presets, ..
|
||||
} => Ok(Some(argument_presets)),
|
||||
other_namespace_annotation =>
|
||||
|
@ -52,10 +52,10 @@ pub fn get_all_usage_counts_in_query(ir: &IndexMap<Alias, RootField<'_, '_>>) ->
|
||||
for ir_field in ir.values() {
|
||||
match ir_field {
|
||||
root_field::RootField::QueryRootField(ir) => match ir {
|
||||
root_field::QueryRootField::TypeName { .. } => {}
|
||||
root_field::QueryRootField::SchemaField { .. } => {}
|
||||
root_field::QueryRootField::TypeField { .. } => {}
|
||||
root_field::QueryRootField::ApolloFederation(
|
||||
root_field::QueryRootField::TypeName { .. }
|
||||
| root_field::QueryRootField::SchemaField { .. }
|
||||
| root_field::QueryRootField::TypeField { .. }
|
||||
| root_field::QueryRootField::ApolloFederation(
|
||||
root_field::ApolloFederationRootFields::ServiceField { .. },
|
||||
) => {}
|
||||
root_field::QueryRootField::ModelSelectOne { ir, .. } => {
|
||||
|
@ -135,8 +135,8 @@ pub enum ProcessResponseAs<'ir> {
|
||||
impl<'ir> ProcessResponseAs<'ir> {
|
||||
pub fn is_nullable(&self) -> bool {
|
||||
match self {
|
||||
ProcessResponseAs::Object { is_nullable } => *is_nullable,
|
||||
ProcessResponseAs::Array { is_nullable } => *is_nullable,
|
||||
ProcessResponseAs::Object { is_nullable }
|
||||
| ProcessResponseAs::Array { is_nullable } => *is_nullable,
|
||||
ProcessResponseAs::CommandResponse { type_container, .. } => type_container.nullable,
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ pub(crate) fn collect_relationships(
|
||||
// from selection fields
|
||||
for field in ir.selection.fields.values() {
|
||||
match field {
|
||||
FieldSelection::Column { .. } => (),
|
||||
FieldSelection::ModelRelationshipLocal {
|
||||
query,
|
||||
name,
|
||||
@ -47,10 +46,11 @@ pub(crate) fn collect_relationships(
|
||||
)?;
|
||||
}
|
||||
}
|
||||
FieldSelection::Column { .. }
|
||||
// we ignore remote relationships as we are generating relationship
|
||||
// definition for one data connector
|
||||
FieldSelection::ModelRelationshipRemote { .. } => (),
|
||||
FieldSelection::CommandRelationshipRemote { .. } => (),
|
||||
| FieldSelection::ModelRelationshipRemote { .. }
|
||||
| FieldSelection::CommandRelationshipRemote { .. } => (),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -320,8 +320,8 @@ pub(crate) fn collect_relationships_from_selection(
|
||||
}
|
||||
// we ignore remote relationships as we are generating relationship
|
||||
// definition for one data connector
|
||||
FieldSelection::ModelRelationshipRemote { .. } => (),
|
||||
FieldSelection::CommandRelationshipRemote { .. } => (),
|
||||
FieldSelection::ModelRelationshipRemote { .. }
|
||||
| FieldSelection::CommandRelationshipRemote { .. } => (),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
|
@ -91,7 +91,6 @@ pub fn schema_type<'s, S: schema::SchemaContext>(
|
||||
match field_call.name.as_str() {
|
||||
"__typename" => Ok(json::to_value(type_name)?),
|
||||
// "description" => Ok(json::to_value(&scalar.description)?),
|
||||
"description" => Ok(json::Value::Null),
|
||||
"types" => {
|
||||
// we publish only those types that are reachable for the
|
||||
// namespace
|
||||
@ -173,8 +172,7 @@ fn scalar_type<'s, S: schema::SchemaContext>(
|
||||
"kind" => Ok(json::to_value("SCALAR")?),
|
||||
"name" => Ok(json::to_value(&scalar.name)?),
|
||||
"description" => Ok(json::to_value(&scalar.description)?),
|
||||
// TODO
|
||||
"specifiedByURL" => Ok(json::Value::Null),
|
||||
// TODO: "specifiedByURL"
|
||||
_ => Ok(json::Value::Null),
|
||||
}
|
||||
})
|
||||
@ -511,8 +509,7 @@ fn input_value<'s, S: schema::SchemaContext>(
|
||||
&input_value.field_type,
|
||||
&field.selection_set,
|
||||
)),
|
||||
// TODO
|
||||
"defaultValue" => Ok(json::Value::Null),
|
||||
// TODO: "defaultValue"
|
||||
"isDeprecated" => Ok(json::to_value(
|
||||
input_value.deprecation_status.is_deprecated(),
|
||||
)?),
|
||||
|
@ -237,8 +237,7 @@ impl<'a> Parser<'a> {
|
||||
|
||||
pub fn is_next_token(&self, expected: &lexer::Token) -> bool {
|
||||
match self.peek() {
|
||||
None => false,
|
||||
Some(Err(_)) => false,
|
||||
None | Some(Err(_)) => false,
|
||||
Some(Ok(token)) => token.item == *expected,
|
||||
}
|
||||
}
|
||||
|
@ -56,8 +56,7 @@ where
|
||||
);
|
||||
}
|
||||
}
|
||||
sdl::TypeSystemDefinition::Schema(_) => {}
|
||||
sdl::TypeSystemDefinition::Directive(_) => {}
|
||||
sdl::TypeSystemDefinition::Schema(_) | sdl::TypeSystemDefinition::Directive(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,13 +27,13 @@ impl<'q, 's> LocationType<'q, 's> {
|
||||
LocationType::Argument {
|
||||
type_,
|
||||
default_value: _,
|
||||
} => type_,
|
||||
LocationType::Field {
|
||||
}
|
||||
| LocationType::Field {
|
||||
type_,
|
||||
default_value: _,
|
||||
} => type_,
|
||||
LocationType::List { type_ } => type_,
|
||||
LocationType::NoLocation { type_ } => type_,
|
||||
}
|
||||
| LocationType::List { type_ }
|
||||
| LocationType::NoLocation { type_ } => type_,
|
||||
}
|
||||
}
|
||||
pub fn default_value(&self) -> Option<&'s gql::ConstValue> {
|
||||
@ -41,13 +41,12 @@ impl<'q, 's> LocationType<'q, 's> {
|
||||
LocationType::Argument {
|
||||
type_: _,
|
||||
default_value,
|
||||
} => *default_value,
|
||||
LocationType::Field {
|
||||
}
|
||||
| LocationType::Field {
|
||||
type_: _,
|
||||
default_value,
|
||||
} => *default_value,
|
||||
LocationType::List { type_: _ } => None,
|
||||
LocationType::NoLocation { type_: _ } => None,
|
||||
LocationType::List { type_: _ } | LocationType::NoLocation { type_: _ } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ enum VariableValue<'q, 's, S: schema::SchemaContext> {
|
||||
}
|
||||
|
||||
impl<'q, 's, S: schema::SchemaContext> VariableValue<'q, 's, S> {
|
||||
#[allow(clippy::match_same_arms)] // lifetimes are different
|
||||
pub fn into_json(self) -> serde_json::Value {
|
||||
match self {
|
||||
Self::Json(value, _, _) => value.clone(),
|
||||
@ -70,15 +71,8 @@ impl<'q, 's, S: schema::SchemaContext> VariableValue<'q, 's, S> {
|
||||
&LocationType::NoLocation { type_ },
|
||||
type_info,
|
||||
),
|
||||
VariableValue::Const(value, type_, type_info) => normalize(
|
||||
schema,
|
||||
namespace,
|
||||
&(),
|
||||
*value,
|
||||
&LocationType::NoLocation { type_ },
|
||||
type_info,
|
||||
),
|
||||
VariableValue::Normalized(value, type_, type_info) => normalize(
|
||||
VariableValue::Const(value, type_, type_info)
|
||||
| VariableValue::Normalized(value, type_, type_info) => normalize(
|
||||
schema,
|
||||
namespace,
|
||||
&(),
|
||||
|
@ -27,8 +27,8 @@ pub fn typecheck_value_expression(
|
||||
value_expression: &open_dds::permissions::ValueExpression,
|
||||
) -> Result<(), TypecheckError> {
|
||||
match &value_expression {
|
||||
open_dds::permissions::ValueExpression::SessionVariable(_) => Ok(()),
|
||||
open_dds::permissions::ValueExpression::BooleanExpression(_) => Ok(()),
|
||||
open_dds::permissions::ValueExpression::SessionVariable(_)
|
||||
| open_dds::permissions::ValueExpression::BooleanExpression(_) => Ok(()),
|
||||
open_dds::permissions::ValueExpression::Literal(json_value) => {
|
||||
typecheck_qualified_type_reference(ty, json_value)
|
||||
}
|
||||
@ -85,12 +85,11 @@ fn typecheck_inbuilt_type(
|
||||
value: &serde_json::Value,
|
||||
) -> Result<(), TypecheckError> {
|
||||
match (inbuilt, value) {
|
||||
(open_dds::types::InbuiltType::Int, serde_json::Value::Number(_)) => Ok(()),
|
||||
(open_dds::types::InbuiltType::Float, serde_json::Value::Number(_)) => Ok(()),
|
||||
(open_dds::types::InbuiltType::String, serde_json::Value::String(_)) => Ok(()),
|
||||
(open_dds::types::InbuiltType::ID, serde_json::Value::String(_)) => Ok(()),
|
||||
(open_dds::types::InbuiltType::Boolean, serde_json::Value::Bool(_)) => Ok(()),
|
||||
|
||||
(open_dds::types::InbuiltType::Int, serde_json::Value::Number(_))
|
||||
| (open_dds::types::InbuiltType::Float, serde_json::Value::Number(_))
|
||||
| (open_dds::types::InbuiltType::String, serde_json::Value::String(_))
|
||||
| (open_dds::types::InbuiltType::ID, serde_json::Value::String(_))
|
||||
| (open_dds::types::InbuiltType::Boolean, serde_json::Value::Bool(_)) => Ok(()),
|
||||
_ => Err(TypecheckError::ScalarTypeMismatch {
|
||||
expected: inbuilt.clone(),
|
||||
actual: value.clone(),
|
||||
|
@ -152,8 +152,8 @@ pub fn get_simple_scalar<'a>(
|
||||
ndc_models::Type::Nullable { underlying_type } => {
|
||||
get_simple_scalar(*underlying_type, scalars)
|
||||
}
|
||||
ndc_models::Type::Array { element_type: _ } => None,
|
||||
ndc_models::Type::Predicate {
|
||||
ndc_models::Type::Array { element_type: _ }
|
||||
| ndc_models::Type::Predicate {
|
||||
object_type_name: _,
|
||||
} => None,
|
||||
}
|
||||
|
@ -236,8 +236,7 @@ impl ResolvedDataConnectorUrl {
|
||||
ResolvedDataConnectorUrl::ReadWriteUrls(ResolvedReadWriteUrls { read, write }) => {
|
||||
match operation {
|
||||
OperationType::Query => &read.0,
|
||||
OperationType::Mutation => &write.0,
|
||||
OperationType::Subscription => &write.0,
|
||||
OperationType::Mutation | OperationType::Subscription => &write.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -380,31 +380,31 @@ impl Display for TypeId {
|
||||
impl TypeId {
|
||||
pub fn to_type_name(&self) -> ast::TypeName {
|
||||
match self {
|
||||
TypeId::QueryRoot { graphql_type_name } => graphql_type_name.clone(),
|
||||
TypeId::MutationRoot { graphql_type_name } => graphql_type_name.clone(),
|
||||
TypeId::OutputType {
|
||||
TypeId::QueryRoot { graphql_type_name }
|
||||
| TypeId::MutationRoot { graphql_type_name }
|
||||
| TypeId::OutputType {
|
||||
graphql_type_name, ..
|
||||
} => graphql_type_name.clone(),
|
||||
TypeId::ScalarType {
|
||||
}
|
||||
| TypeId::ScalarType {
|
||||
graphql_type_name, ..
|
||||
} => graphql_type_name.clone(),
|
||||
TypeId::InputObjectType {
|
||||
}
|
||||
| TypeId::InputObjectType {
|
||||
graphql_type_name, ..
|
||||
}
|
||||
| TypeId::InputObjectBooleanExpressionType {
|
||||
graphql_type_name, ..
|
||||
}
|
||||
| TypeId::ScalarTypeComparisonExpression {
|
||||
graphql_type_name, ..
|
||||
}
|
||||
| TypeId::ModelOrderByExpression {
|
||||
graphql_type_name, ..
|
||||
}
|
||||
| TypeId::OrderByEnumType {
|
||||
graphql_type_name, ..
|
||||
} => graphql_type_name.clone(),
|
||||
TypeId::NodeRoot => ast::TypeName(mk_name!("Node")),
|
||||
TypeId::ModelArgumentsInput { type_name, .. } => type_name.clone(),
|
||||
TypeId::InputObjectBooleanExpressionType {
|
||||
graphql_type_name, ..
|
||||
} => graphql_type_name.clone(),
|
||||
TypeId::ScalarTypeComparisonExpression {
|
||||
graphql_type_name, ..
|
||||
} => graphql_type_name.clone(),
|
||||
TypeId::ModelOrderByExpression {
|
||||
graphql_type_name, ..
|
||||
} => graphql_type_name.clone(),
|
||||
TypeId::OrderByEnumType {
|
||||
graphql_type_name, ..
|
||||
} => graphql_type_name.clone(),
|
||||
TypeId::ApolloFederationType(PossibleApolloFederationTypes::Entity) => {
|
||||
ast::TypeName(mk_name!("_Entity"))
|
||||
}
|
||||
|
@ -163,9 +163,10 @@ pub(crate) fn get_type_kind(
|
||||
.map_err(|_| Error::InternalTypeNotFound {
|
||||
type_name: type_name.to_owned(),
|
||||
})? {
|
||||
TypeRepresentation::Object(_) => Ok(super::TypeKind::Object),
|
||||
TypeRepresentation::Scalar(_) => Ok(super::TypeKind::Scalar),
|
||||
TypeRepresentation::BooleanExpression(_) => Ok(super::TypeKind::Object),
|
||||
TypeRepresentation::Object(_) | TypeRepresentation::BooleanExpression(_) => {
|
||||
Ok(super::TypeKind::Object)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -256,8 +256,9 @@ impl<'a> EnumVariant<'a> {
|
||||
// Preserve casing for kinded enums
|
||||
Tagged::KindInternal => variant_name.to_string(),
|
||||
// Use camel-casing for versioned enums
|
||||
Tagged::VersionInternal => variant_name.to_string().to_case(Case::Camel),
|
||||
Tagged::VersionWithDefinition => variant_name.to_string().to_case(Case::Camel),
|
||||
Tagged::VersionInternal | Tagged::VersionWithDefinition => {
|
||||
variant_name.to_string().to_case(Case::Camel)
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
@ -27,8 +27,9 @@ impl EnumTagType {
|
||||
|
||||
fn generate_tag(&self) -> String {
|
||||
match self {
|
||||
EnumTagType::Internal { tag } => tag.to_string(),
|
||||
EnumTagType::Adjacent { tag, content: _ } => tag.to_string(),
|
||||
EnumTagType::Internal { tag } | EnumTagType::Adjacent { tag, content: _ } => {
|
||||
tag.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user