use crate::common::helpers::{Context, ValidateWithContext};
use crate::common::reference::RefOr;
use crate::v3_1::operation::Operation;
use crate::v3_1::parameter::Parameter;
use crate::v3_1::server::Server;
use crate::v3_1::spec::Spec;
use serde::de::{Error, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Default)]
pub struct PathItem {
pub operations: Option<BTreeMap<String, Operation>>,
pub servers: Option<Vec<Server>>,
pub parameters: Option<Vec<RefOr<Parameter>>>,
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}
impl Serialize for PathItem {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
if let Some(o) = &self.operations {
for (k, v) in o {
map.serialize_entry(&k, &v)?;
}
}
if let Some(parameters) = &self.parameters {
map.serialize_entry("parameters", parameters)?;
}
if let Some(servers) = &self.servers {
map.serialize_entry("servers", servers)?;
}
if let Some(ref ext) = self.extensions {
for (k, v) in ext {
if k.starts_with("x-") {
map.serialize_entry(&k, &v)?;
}
}
}
map.end()
}
}
impl<'de> Deserialize<'de> for PathItem {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
const FIELDS: &[&str] = &[
"parameters",
"servers",
"get",
"head",
"post",
"put",
"patch",
"delete",
"options",
"trace",
"<custom method>",
"x-<ext name>",
];
struct PathItemVisitor;
impl<'de> Visitor<'de> for PathItemVisitor {
type Value = PathItem;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct PathItem")
}
fn visit_map<V>(self, mut map: V) -> Result<PathItem, V::Error>
where
V: MapAccess<'de>,
{
let mut res = PathItem::default();
let mut operations: BTreeMap<String, Operation> = BTreeMap::new();
let mut extensions: BTreeMap<String, serde_json::Value> = BTreeMap::new();
while let Some(key) = map.next_key::<String>()? {
if key == "parameters" {
if res.parameters.is_some() {
return Err(Error::duplicate_field("parameters"));
}
res.parameters = Some(map.next_value()?);
} else if key == "servers" {
if res.servers.is_some() {
return Err(Error::duplicate_field("servers"));
}
res.parameters = Some(map.next_value()?);
} else if key.starts_with("x-") {
if extensions.contains_key(key.clone().as_str()) {
return Err(Error::custom(format!("duplicate field '{key}'")));
}
extensions.insert(key, map.next_value()?);
} else {
let key = key.to_lowercase();
if operations.contains_key(key.as_str()) {
return Err(Error::custom(format!("duplicate field '{key}'")));
}
operations.insert(key, map.next_value()?);
}
}
if !operations.is_empty() {
res.operations = Some(operations);
}
if !extensions.is_empty() {
res.extensions = Some(extensions);
}
Ok(res)
}
}
deserializer.deserialize_struct("PathItem", FIELDS, PathItemVisitor)
}
}
impl ValidateWithContext<Spec> for PathItem {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
if let Some(operations) = &self.operations {
for (method, operation) in operations.iter() {
operation.validate_with_context(ctx, format!("{path}.{method}"));
}
}
if let Some(servers) = &self.servers {
for (i, server) in servers.iter().enumerate() {
server.validate_with_context(ctx, format!("{path}.servers[{i}]"));
}
}
if let Some(parameters) = &self.parameters {
for (i, parameter) in parameters.iter().enumerate() {
parameter.validate_with_context(ctx, format!("{path}.parameters[{i}]"));
}
}
}
}