Skip to content

Commit

Permalink
support v3.1 spec
Browse files Browse the repository at this point in the history
  • Loading branch information
SVilgelm committed Mar 19, 2024
1 parent b7297c9 commit 53c9b96
Show file tree
Hide file tree
Showing 35 changed files with 10,099 additions and 24 deletions.
15 changes: 8 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "roas"
version = "0.2.3"
version = "0.3.0"
edition = "2021"
authors = ["Sergey Vilgelm <[email protected]>"]
description = "Rust OpenAPI Specification"
Expand All @@ -16,14 +16,15 @@ include = ["src", "Cargo.toml", "README.md", "LICENSE-APACHE", "LICENSE-MIT", "d
dependencies-license-file = "dependencies-license.json"

[features]
default = ["v3_0"]
default = ["v3_1"]
v2 = []
v3_0 = []
v3_1 = []

[dependencies]
serde = { version = "1.0.197", features = ["derive"] }
serde_json = { version = "1.0.114" }
regex = { version = "1.10.3" }
enumset = { version = "1.1.3" }
thiserror = { version = "1.0.58" }
enumset = "1.1.3"
monostate = "0.1.11"
regex = "1.10.3"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
thiserror = "1.0.58"
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ Parsing and generating OpenAPI Specification:

* [x] OpenAPI Specification v2.0
* [x] OpenAPI Specification v3.0.X
* [ ] OpenAPI Specification v3.0.0
* [x] OpenAPI Specification v3.1.X

## Usage

Add this to your `Cargo.toml`:

```toml
[dependencies]
roas = { version = "0.2", features = ["v3_0"] }
roas = "0.3"
```

## Examples

```rust
use roas::v3_0::spec::Spec;
use roas::v3_1::spec::Spec;
use roas::validation::{Options, Validate};

...
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ pub mod v2;

#[cfg(feature = "v3_0")]
pub mod v3_0;

#[cfg(feature = "v3_1")]
pub mod v3_1;
141 changes: 141 additions & 0 deletions src/v3_1/callback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use std::collections::BTreeMap;
use std::fmt;

use serde::de::{Error, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::common::helpers::{Context, ValidateWithContext};
use crate::v3_1::path_item::PathItem;
use crate::v3_1::spec::Spec;

/// A map of possible out-of band callbacks related to the parent operation.
/// Each value in the map is a Path Item Object that describes a set of requests
/// that may be initiated by the API provider and the expected responses.
/// The key value used to identify the path item object is an expression, evaluated at runtime,
/// that identifies a URL to use for the callback operation.
///
/// Specification example:
///
/// ```yaml
/// onData:
/// # when data is sent, it will be sent to the `callbackUrl` provided
/// # when making the subscription PLUS the suffix `/data`
/// '{$request.query.callbackUrl}/data':
/// post:
/// requestBody:
/// description: subscription payload
/// content:
/// application/json:
/// schema:
/// type: object
/// properties:
/// timestamp:
/// type: string
/// format: date-time
/// userData:
/// type: string
/// responses:
/// '202':
/// description: |
/// Your server implementation should return this HTTP status code
/// if the data was received successfully
/// '204':
/// description: |
/// Your server should return this HTTP status code if no longer interested
/// in further updates
/// ```
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Callback {
/// A Path Item Object used to define a callback request and expected responses.
pub paths: BTreeMap<String, PathItem>,

/// This object MAY be extended with Specification Extensions.
/// The field name MUST begin with `x-`, for example, `x-internal-id`.
/// The value can be null, a primitive, an array or an object.
pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}

impl Serialize for Callback {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut len = self.paths.len();
if let Some(ext) = &self.extensions {
len += ext.len();

Check warning on line 66 in src/v3_1/callback.rs

View check run for this annotation

Codecov / codecov/patch

src/v3_1/callback.rs#L66

Added line #L66 was not covered by tests
}
let mut map = serializer.serialize_map(Some(len))?;

for (k, v) in &self.paths {
map.serialize_entry(&k, &v)?;
}

if let Some(ext) = &self.extensions {
for (k, v) in ext {
if k.starts_with("x-") {
map.serialize_entry(&k, &v)?;
}

Check warning on line 78 in src/v3_1/callback.rs

View check run for this annotation

Codecov / codecov/patch

src/v3_1/callback.rs#L75-L78

Added lines #L75 - L78 were not covered by tests
}
}

map.end()
}
}

impl<'de> Deserialize<'de> for Callback {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
const FIELDS: &[&str] = &["<path name>", "x-<ext name>"];

struct CallbackVisitor;

impl<'de> Visitor<'de> for CallbackVisitor {
type Value = Callback;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Callback")
}

Check warning on line 100 in src/v3_1/callback.rs

View check run for this annotation

Codecov / codecov/patch

src/v3_1/callback.rs#L99-L100

Added lines #L99 - L100 were not covered by tests

fn visit_map<V>(self, mut map: V) -> Result<Callback, V::Error>
where
V: MapAccess<'de>,
{
let mut res = Callback {
paths: BTreeMap::new(),
..Default::default()
};
let mut extensions: BTreeMap<String, serde_json::Value> = BTreeMap::new();
while let Some(key) = map.next_key::<String>()? {
if key.starts_with("x-") {
if extensions.contains_key(key.as_str()) {
return Err(Error::custom(format_args!("duplicate field `{}`", key)));
}
extensions.insert(key, map.next_value()?);

Check warning on line 116 in src/v3_1/callback.rs

View check run for this annotation

Codecov / codecov/patch

src/v3_1/callback.rs#L116

Added line #L116 was not covered by tests
} else {
if res.paths.contains_key(key.as_str()) {
return Err(Error::custom(format_args!("duplicate field `{}`", key)));
}
res.paths.insert(key, map.next_value()?);
}
}
if !extensions.is_empty() {
res.extensions = Some(extensions);

Check warning on line 125 in src/v3_1/callback.rs

View check run for this annotation

Codecov / codecov/patch

src/v3_1/callback.rs#L125

Added line #L125 was not covered by tests
}
Ok(res)
}
}

deserializer.deserialize_struct("Callback", FIELDS, CallbackVisitor)
}
}

impl ValidateWithContext<Spec> for Callback {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
for (name, path_item) in &self.paths {
path_item.validate_with_context(ctx, format!("{}[{}]", path, name));
}
}
}
Loading

0 comments on commit 53c9b96

Please sign in to comment.